-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dec 30 daily effect, and multicontrol, and show_text
- Loading branch information
1 parent
c1736c5
commit 1f66efd
Showing
4 changed files
with
248 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
__version__ = '0.1.30' | ||
__version__ = '0.1.31' | ||
|
||
_classifiers = [ | ||
'Development Status :: 4 - Beta', | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
""" | ||
xled_plus.multicontrol | ||
~~~~~~~~~~~~~~~~~~~~~~ | ||
Handle multiple connected lights as one unit. | ||
Create a MultiHighControlInterface by giving it a list of ip-addresses to | ||
the devices that are connected, with the master device first. Then, use it | ||
in the same way as a normal HighControlInterface. | ||
""" | ||
|
||
from __future__ import absolute_import | ||
|
||
import io | ||
import struct | ||
import binascii | ||
import time | ||
import uuid | ||
|
||
import struct | ||
import base64 | ||
|
||
from xled.control import ControlInterface | ||
from xled_plus.highcontrol import HighControlInterface | ||
|
||
|
||
class MultiHighControlInterface(HighControlInterface): | ||
""" | ||
High level interface to control specific device | ||
""" | ||
|
||
def __init__(self, hostlst): | ||
super(MultiHighControlInterface, self).__init__(hostlst[0]) | ||
self.ctrlst = [self] + [ControlInterface(ip) for ip in hostlst[1:]] | ||
info = self.get_device_info() | ||
self.family = info["fw_family"] if "fw_family" in info else "D" | ||
self.led_bytes = info["bytes_per_led"] if "bytes_per_led" in info else 3 | ||
self.led_profile = info["led_profile"] if "led_profile" in info else "RGB" | ||
self.version = tuple(map(int, self.firmware_version()["version"].split("."))) | ||
self.nledslst = [ctr.get_device_info()["number_of_led"] for ctr in self.ctrlst] | ||
for ctr in self.ctrlst: | ||
ctr._udpclient = self.udpclient | ||
self.num_leds = sum(self.nledslst) | ||
self.string_config = [{'first_led_id': 0, 'length': self.num_leds}] | ||
self.layout = False | ||
self.layout_bounds = False | ||
self.last_mode = None | ||
self.last_rt_time = 0 | ||
self.curr_mode = self.get_mode()["mode"] | ||
|
||
def split_movie(self, movie): | ||
# return a list of one movie per connected device | ||
lst = [io.BytesIO() for ctr in self.ctrlst] | ||
blens = [nleds * self.led_bytes for nleds in self.nledslst] | ||
totlen = self.num_leds * self.led_bytes | ||
num = movie.seek(0, 2) // totlen | ||
movie.seek(0) | ||
for i in range(num): | ||
for mov, blen in zip(lst, blens): | ||
mov.write(movie.read(blen)) | ||
for mov in lst: | ||
mov.seek(0) | ||
return lst | ||
|
||
def show_movie(self, movie_or_id, fps=None): | ||
""" | ||
Either starts playing an already uploaded movie with the provided id, | ||
or uploads a new movie and starts playing it at the provided frames-per-second. | ||
Note: if the movie do not fit in the remaining capacity, the old movie list is cleared. | ||
Switches to movie mode if necessary. | ||
The movie is an object suitable created with to_movie or make_func_movie. | ||
:param movie_or_id: either an integer id or a file-like object that points to movie | ||
:param fps: frames per second, or None if a movie id is given | ||
""" | ||
if isinstance(movie_or_id, int) and fps is None: | ||
if self.family == "D" or self.version < (2, 5, 6): | ||
if movie_or_id != 0: | ||
return False | ||
else: | ||
movies = self.get_movies()["movies"] | ||
if movie_or_id in [entry["id"] for entry in movies]: | ||
self.set_movies_current(movie_or_id) | ||
else: | ||
return False | ||
if self.curr_mode != "movie": | ||
self.set_mode("movie") | ||
else: | ||
assert fps | ||
self.set_mode("off") | ||
self.upload_movie(movie_or_id, fps, force=True) | ||
self.set_mode("movie") | ||
return True | ||
|
||
def upload_movie(self, movie, fps, force=False): | ||
""" | ||
Uploads a new movie with the provided frames-per-second. | ||
Note: if the movie does not fit in the remaining capacity, and force is | ||
not set to True, the function just returns False, in which case the user | ||
can try clear_movies first. | ||
Does not switch to movie mode, use show_movie instead for that. | ||
The movie is an object suitable created with to_movie or make_func_movie. | ||
Returns the new movie id, which can be used in calls to show_movie or | ||
show_playlist. | ||
:param movie: a file-like object that points to movie | ||
:param fps: frames per second, or None if a movie id is given | ||
:param bool force: if remaining capacity is too low, previous movies will be removed | ||
:rtype: int | ||
""" | ||
numframes = movie.seek(0, 2) // (self.led_bytes * self.num_leds) | ||
movielst = self.split_movie(movie) | ||
if self.family == "D" or self.version < (2, 5, 6): | ||
for ctr, mov, nled in zip(self.ctrlst, movielst, self.nledslst): | ||
ctr.set_led_movie_config(1000 // fps, numframes, nled) | ||
ctr.set_led_movie_full(mov) | ||
return 0 | ||
else: | ||
res = self.get_movies() | ||
capacity = res["available_frames"] - 1 | ||
if numframes > capacity or len(res["movies"]) > 15: | ||
if force: | ||
if self.curr_mode == "movie" or self.curr_mode == "playlist": | ||
self.set_mode("effect") | ||
self.delete_movies() | ||
else: | ||
return False | ||
if self.curr_mode == "movie": | ||
oldid = self.get_movies_current()["id"] | ||
for ctr, mov, nled in zip(self.ctrlst, movielst, self.nledslst): | ||
res = ctr.set_movies_new( | ||
"", | ||
str(uuid.uuid4()), | ||
self.led_profile.lower() + "_raw", | ||
nled, | ||
numframes, | ||
fps, | ||
) | ||
ctr.set_movies_full(mov) | ||
if self.curr_mode == "movie": | ||
self.set_movies_current(oldid) # Dont change currently shown movie | ||
return res["id"] | ||
|
||
def show_rt_frame(self, frame): | ||
""" | ||
Uploads a frame as the next real time frame, and shows it. | ||
Switches to rt mode if necessary. | ||
The frame is either a pattern or a one-frame movie | ||
:param frame: a pattern or file-like object representing the frame | ||
""" | ||
if self.is_pattern(frame): | ||
frame = self.to_movie(frame) | ||
framelst = self.split_movie(frame) | ||
if self.curr_mode != "rt" or self.last_rt_time + 50.0 < time.time(): | ||
for ctr in self.ctrlst: | ||
ctr.set_mode("rt") | ||
else: | ||
self.last_rt_time = time.time() | ||
for ctr, mov, nled in zip(self.ctrlst, framelst, self.nledslst): | ||
self.udpclient.destination_host = ctr.host | ||
if self.family == "D": | ||
ctr.set_rt_frame_socket(mov, 1, nled) | ||
elif self.version < (2, 4, 14): | ||
ctr.set_rt_frame_socket(mov, 2) | ||
else: | ||
ctr.set_rt_frame_socket(mov, 3) | ||
self.udpclient.destination_host = self.host | ||
|
||
def clear_movies(self): | ||
""" | ||
Removes all uploaded movies and any playlist. | ||
If the current mode is 'movie' or 'playlist' it switches mode to 'effect' | ||
""" | ||
if self.curr_mode == "movie" or self.curr_mode == "playlist": | ||
self.set_mode("effect") | ||
if self.family == "D" or self.version < (2, 5, 6): | ||
# No list of movies to remove in this version, | ||
# but disable movie mode until new movie is uploaded | ||
for i, ctr in enumerate(self.ctrlst): | ||
ctr.set_led_movie_config(1000, 0, self.nledslst[i]) | ||
else: | ||
# The playlist is removed automatically when movies are removed | ||
for ctr in self.ctrlst: | ||
ctr.delete_movies() | ||
|
||
def get_led_layout(self): | ||
res = super(MultiHighControlInterface, self).get_led_layout() | ||
for ctr in self.ctrlst[1:]: | ||
tmp = ctr.get_led_layout() | ||
res["coordinates"].extend(tmp["coordinates"]) | ||
return res | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
""" | ||
Day 30 - Random walk in colorspace with random gradient direction | ||
Another variant of the Day 25 effect. Here the colors form a gradient that | ||
moves over the leds at a slowly and randomly changing angle and change rate. | ||
This is again a continuous effect. Aspect ratio is important as usual, so adjust below. | ||
However, dimensionality or density of layout should matter less. | ||
""" | ||
|
||
from xled_plus.samples.sample_setup import * | ||
|
||
ctr = setup_control() | ||
ctr.adjust_layout_aspect(1.0) # How many times wider than high is the led installation? | ||
eff = MeanderingSequence(ctr) | ||
oldmode = ctr.get_mode()["mode"] | ||
eff.launch_rt() | ||
print("Started continuous effect - press Return to stop it") | ||
input() | ||
eff.stop_rt() | ||
ctr.set_mode(oldmode) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from xled.discover import discover | ||
from xled_plus.highcontrol import HighControlInterface | ||
from xled_plus.ledcolor import * | ||
from xled_plus.shapes import * | ||
from sys import argv | ||
import re | ||
|
||
def isipaddress(txt): | ||
return re.match("([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)", txt) | ||
|
||
def makecolorlist(n): | ||
gs = (1.25**0.5 - 0.5) | ||
return [hsl_color((0.5 + gs * i) % 1.0, 1.0, 0.0) for i in range(n)] | ||
|
||
if len(argv) == 3 and isipaddress(argv[1]): | ||
host = argv[1] | ||
txt = argv[2] | ||
elif len(argv) == 3 and isipaddress(argv[2]): | ||
host = argv[2] | ||
txt = argv[1] | ||
elif len(argv) == 2: | ||
txt = argv[1] | ||
host = discover().ip_address | ||
else: | ||
print('Usage: python -m xled_plus.show_text [ip-address] "text"') | ||
quit() | ||
|
||
ctr = HighControlInterface(host) | ||
ctr.adjust_layout_aspect(1.0) | ||
eff = RunningText(ctr, txt.upper(), makecolorlist(len(txt)), size=0.6, speed=0.5) | ||
eff.launch_movie() |