Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
c91b4ad
* Added example, lights the string one globe at a time until all are
jpwarren Aug 14, 2013
3d69687
* Added emacs backup file pattern (*~) to .gitignore
jpwarren Aug 14, 2013
036ed9d
Merge remote-tracking branch 'upstream/master'
jpwarren Oct 20, 2013
99ee801
* Added multi-Holiday simulator using PyGame and SecretLabs UDP API
jpwarren Oct 29, 2013
9815ba8
Merge remote-tracking branch 'upstream/master'
jpwarren Oct 29, 2013
2783483
* Added ability to select orientation of string displays: vertical
jpwarren Oct 30, 2013
85b2d54
* chmod 755 on phloston.py
jpwarren Oct 30, 2013
bf36f62
* Changed string color in simgame to be a darker grey, so it doesn't
jpwarren Oct 31, 2013
60cb8b0
* Updated holiscreen to support horizontal orientation of Holidays.
jpwarren Nov 1, 2013
3ce8e78
* Support for switchback mode in led_scroller.py
jpwarren Nov 1, 2013
1469bd9
* Informative error message from holiscreen if you don't specify
jpwarren Nov 2, 2013
38b498e
* led_scroller font anti-aliasing is now optional.
jpwarren Nov 2, 2013
75c3937
* Added ability to specify IP address of remote Holidays to led_scrol…
jpwarren Dec 22, 2013
4ef9755
* FIXME comment added to led_scroller
jpwarren Dec 22, 2013
96fb5c2
* Added workaround for multiple Holidays all rendering at once, as
jpwarren Dec 22, 2013
f4f69e2
* Make a Holiday twinkle like multi-coloured stars
jpwarren Dec 22, 2013
e2c26d1
* Adjusted twinkle.py saturation minimum and startup brightness.
jpwarren Dec 22, 2013
c9f63d5
* Updated holibeats colours to be have richer yellow and red
jpwarren Dec 23, 2013
b6d223d
* Added some alternate colour schemes for holibeats.
jpwarren Dec 23, 2013
c05e62d
* Updates to twinkle.py to keep it bright and colorful.
jpwarren Dec 23, 2013
3fa8fe4
* Added REST API support to the simgame multi-Holiday simulator
jpwarren Dec 24, 2013
4fcf29d
* Added options to twinkle.py to set basecolor and max difference
jpwarren Dec 25, 2013
1d58fba
* Moved twinkle saturation limits to full range now that options for
jpwarren Dec 25, 2013
f900653
* Added ability to set initial light pattern to twinkle.py via JSON f…
jpwarren Dec 26, 2013
63b4080
* Added example twinkle.py Xmas themed JSON light patterns
jpwarren Dec 26, 2013
a2b667b
* Twinkle now supports simplex noise twinkling, which looks a bit
jpwarren Dec 26, 2013
a35474f
* Added missing simplexnoise.py file.
jpwarren Dec 26, 2013
68afd9a
* Added option to twinkle.py to control amount of simplex noise,
jpwarren Dec 26, 2013
349f7bb
* Added switchback mode to phloston.
jpwarren Dec 27, 2013
7ebda8b
* Abstracted the Holiday API into a separate package that uses
jpwarren Dec 30, 2013
a9c8e23
* Fixed incorrect compose.fifo formatting in API
jpwarren Dec 31, 2013
05c7fea
* Fixed hue wraparound bug in phlostonapp
jpwarren Dec 31, 2013
2fdb883
* Tweaked led_scroller to work better on switchback'd Holidays
jpwarren Jan 1, 2014
3d7e3f8
* Added working twinkleapp code, including some sample animation patt…
jpwarren Jan 1, 2014
2dbb74a
* Lowered simplex noise damper setting in twinkle so it twinkles more.
jpwarren Jan 2, 2014
8693e83
* Tweaked the timing variables to make the display more interesting.
jpwarren Jan 25, 2014
170fd87
* Added the 'oz' green and gold colour scheme for Australia Day 2014.
jpwarren Jan 25, 2014
fe6a47a
* Added the 'throb' animation to twinkle. Pulses entires string
jpwarren Feb 14, 2014
37c5140
* simgame now uses a PyGame event to handle refresh rate, so it
jpwarren Feb 21, 2014
5faf577
* Added holivid.py, which displays videos on an array of Holidays
jpwarren Feb 26, 2014
7389e30
* Using CUBIC interpolation in holivid for improved colour.
jpwarren Feb 26, 2014
487afb6
* Updated simgame with 'flipped' mode, for reversing the install dire…
jpwarren Feb 27, 2014
ec76334
* Tuned the holibeats frequency bucket algorithm to provide a more
jpwarren Mar 1, 2014
19eb084
* holibeats now uses a configurable moving average for the max value
jpwarren Mar 2, 2014
7710ead
* Tweaked default number of buckets to fit with where music frequencies
jpwarren Mar 3, 2014
78e8bf5
Fixed incorrect --chase parameters.
jpwarren Dec 3, 2016
17f8a43
Added candela.sh as an example twinkling candles.
jpwarren Dec 3, 2016
3056f4d
* Fixed chase to be available as a twinkle algo option.
jpwarren Dec 11, 2016
6caf012
Pulling in changes made upstream.
jpwarren Dec 21, 2016
f232678
version bump
jpwarren Dec 21, 2016
b8a1ef3
Added changes from production Holiday missing from repo.
jpwarren Dec 21, 2016
016b761
Added API support for starting a named buttonapp.
jpwarren Dec 22, 2016
4fd39f5
Added support to simgame for a local named pipe for local testing of
jpwarren Dec 22, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.DS_Store
*.pyc

*~
Empty file added api/__init__.py
Empty file.
156 changes: 156 additions & 0 deletions api/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#!/usr/bin/env python
"""
Base API classes for Holiday interfaces

Copyright (c) 2013 Justin Warren <justin@eigenmagic.com>
License: MIT (see LICENSE for details)
"""

__author__ = "Justin Warren"
__version__ = '0.02-dev'
__license__ = "MIT"

import os
import logging

# Set up logging
log = logging.getLogger('base')
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s: %(name)s [%(levelname)s]: %(message)s"))
log.addHandler(handler)
log.setLevel(logging.DEBUG)

class HolidayBase(object):
"""
The base Holiday class for the main API
"""
NUM_GLOBES = 50

# Local representation of globe state
globes = [ (0,0,0), ] * NUM_GLOBES

def setglobe(self, globenum, r, g, b):
# FIXME: This should be (self, globenum, color) where color is
# a tuple of (r g, b).
"""Set a globe"""
if (globenum < 0) or (globenum >= self.NUM_GLOBES):
return
self.globes[globenum] = (r, g, b)

def fill(self, r, g, b):
"""Sets the whole string to a particular colour"""
self.globes = [ (int(r), int(g), int(b)), ] * self.NUM_GLOBES
#for e in self.globes:
# e[0] = int(r)
# e[1] = int(g)
# e[2] = int(b)

def getglobe(self, globenum):
"""Return a tuple representing a globe's RGB color value"""
if (globenum < 0) or (globenum >= self.NUM_GLOBES):
# Fail hard, don't ignore errors
raise IndexError("globenum %d does not exist", globenum)
return self.globes[globenum]

def set_pattern(self, pattern):
"""
Set the entire string in one go
"""
if len(pattern) != self.NUM_GLOBES:
raise ValueError("pattern length incorrect: %d != %d" % ( len(pattern), self.NUM_GLOBES) )
self.globes = pattern[:]

def chase(self, direction=True):
"""
Rotate all globes around one step.
@param direction: Direction to rotate: up if True, down if False
"""
if direction:
# Rotate all globes around by one place
oldglobes = self.globes[:]
self.globes = oldglobes[1:]
self.globes.append(oldglobes[0])
pass
else:
oldglobes = self.globes[:]
self.globes = oldglobes[:-1]
self.globes.insert(0, oldglobes[-1])
pass
return

def rotate(self, newr, newg, newb, direction=True):
"""
Rotate all globes, just like chase, but replace the 'first'
globe with new color.
"""
self.chase(direction)
if direction:
self.globes[-1] = (newr, newg, newb)
pass
else:
self.globes[0] = (newr, newg, newb)
pass
pass

def render(self):
raise NotImplementedError

class ButtonHoliday(HolidayBase):
"""
Used when running on a physical Holiday.
"""
def __init__(self):
super(ButtonHoliday, self).__init__()
self.pid = os.getpid()
self.pipename = '/run/compose.fifo'
try:
self.pipe = open(self.pipename, "wb")
except:
print "Couldn't open the pipe! Oh no!"
raise
self.pipe = None
pass

def render(self):
"""
Render globe colours to local pipe
"""
rend = []
rend.append("0x000010\n")
rend.append("0x%06x\n" % self.pid)
for g in self.globes:
tripval = (g[0] * 65536) + (g[1] * 256) + g[2]
rend.append("0x%06X\n" % tripval)
pass
self.pipe.write(''.join(rend))
self.pipe.flush()

class ButtonApp(object):
"""
A ButtonApp runs on a physical Holiday using the button interface.
"""

def start(self):
"""
Do whatever is required to start up the app
"""
return

def stop(self):
"""
Do whatever is required to stop the app
"""
return

def up(self):
"""
Called when the Up button is pressed
"""
return

def down(self):
"""
Called when the Down button is pressed
"""
return

50 changes: 50 additions & 0 deletions api/restholiday.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python
"""
REST interface classes for Holiday

Copyright (c) 2013 Justin Warren <justin@eigenmagic.com>
License: MIT (see LICENSE for details)
"""

__author__ = "Justin Warren"
__version__ = '0.02-dev'
__license__ = "MIT"

import requests
import json

from base import HolidayBase

import logging

# Set up logging
log = logging.getLogger('rest_holiday')
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s: %(name)s [%(levelname)s]: %(message)s"))
log.addHandler(handler)
log.setLevel(logging.DEBUG)

class RESTHoliday(HolidayBase):
"""
A remote Holiday we talk to over the JSON REST web interface
"""
def __init__(self, addr, scheme='http'):
"""
Initialise the REST Holiday remote address
@param addr: A string of the remote address of form <ipaddr>:<port>
"""
super(RESTHoliday, self).__init__()
self.scheme = scheme
self.addr = addr

def render(self):
"""
Render globe values via JSON to remote Holiday via REST interface
"""
hol_vals = [ "#%02x%02x%02x" % (x[0], x[1], x[2]) for x in self.globes ]
hol_msg = { "lights": hol_vals }
msg_str = json.dumps(hol_msg)
url_str = "%s://%s/device/light/setlights" % (self.scheme, self.addr)
r = requests.put(urlstr, data=msg_str)
pass

55 changes: 55 additions & 0 deletions api/udpholiday.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env python
"""
UDP interface classes for Holiday

Copyright (c) 2013 Justin Warren <justin@eigenmagic.com>
License: MIT (see LICENSE for details)
"""

__author__ = "Justin Warren"
__version__ = '0.02-dev'
__license__ = "MIT"

import sys
import socket
import array

from base import HolidayBase

import logging

# Set up logging
log = logging.getLogger('udp_holiday')
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s: %(name)s [%(levelname)s]: %(message)s"))
log.addHandler(handler)
log.setLevel(logging.DEBUG)

class UDPHoliday(HolidayBase):
"""
A remote Holiday we talk to over the fast UDP
"""
def __init__(self, ipaddr, port=9988):
"""
Initialise the REST Holiday remote address
@param addr: A string of the remote address of form <ipaddr>:<port>
"""
super(UDPHoliday, self).__init__()
self.ipaddr = ipaddr
self.port = port
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

def render(self):
"""
Render globe values via UDP to remote Holiday
"""
packet = array.array('B', [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) # initialize basic packet, ignore first 10 bytes
for g in self.globes:
packet.append(g[0])
packet.append(g[1])
packet.append(g[2])
pass
# Send the packet to the Holiday
self.sock.sendto(packet, (self.ipaddr, self.port))
pass

3 changes: 3 additions & 0 deletions examples/candela.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh

./twinkle.py $1 -H 0.01 -S 0.1 -V 0.00 -c 0.5 -b '#b0771f' --satdiff-max 0.05 --huediff-max 0.05
96 changes: 96 additions & 0 deletions examples/chase_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env python
"""
Demonstrate the chase function from holiday.py

Copyright (c) 2013 Justin Warren <justin@eigenmagic.com>
License: MIT (see LICENSE for details)
"""

__author__ = "Justin Warren"
__version__ = '0.01-dev'
__license__ = "MIT"

import sys
import time
import logging

import holiday

# Simulator default address
SIM_ADDR = "localhost:8080"

log = logging.getLogger(sys.argv[0])
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("%(asctime)s: %(name)s [%(levelname)s]: %(message)s"))
log.addHandler(handler)
log.setLevel(logging.DEBUG)

class Chaser(object):
"""
Implements a chaser
"""

def __init__(self, addr,
pattern=None,
reverse=False,
delay=0.1):
"""
Controls a single Holiday at addr

@param pattern: The starting pattern to chase around
"""
self.hol = holiday.Holiday(addr=addr,
remote=True)

if pattern is None:
pattern = [
(0x00, 0x00, 0x00),
] * self.hol.NUM_GLOBES
pattern[0] = (0xff, 0xff, 0xff)
pattern[10] = (0xff, 0x00, 0x00)
pattern[20] = (0x00, 0xff, 0x00)
pattern[30] = (0x00, 0x00, 0xff)
pattern[40] = (0xff, 0xff, 0x00)

# Make a copy so we don't clobber the original
self.globe_pattern = pattern[:]

self.reverse = reverse
self.delay = delay

def animate(self):
# Animation sequence is to start blank, then light each
# globe in sequence until all are lit, then start again.
while True:

# Move lights one step forwards or backwards,
# depending on the setting passed in
if self.reverse:
new_pattern = self.globe_pattern[1:]
new_pattern.append(self.globe_pattern[0])
pass

else:
new_pattern = self.globe_pattern[:-1]
new_pattern.insert(0, self.globe_pattern[-1])
pass

self.globe_pattern = new_pattern
self.hol.set_pattern(self.globe_pattern)
self.hol.render()
time.sleep(self.delay)
pass

if __name__ == '__main__':
if len(sys.argv) > 1:
hostname = sys.argv[1]
log.debug("hostname: %s", hostname)
else:
# Assume we're on the simulator
log.info("Using simulator: %s", SIM_ADDR)
hostname = SIM_ADDR
pass

obj = Chaser(hostname,
reverse=False)
obj.animate()
Loading