diff --git a/flickerstrip_py/discovery.py b/flickerstrip_py/discovery.py index 5d0a118..fd69576 100644 --- a/flickerstrip_py/discovery.py +++ b/flickerstrip_py/discovery.py @@ -1,12 +1,13 @@ from ssdpy import SSDPClient import re import json -from flickerstrip import Flickerstrip +from flickerstrip_py.flickerstrip import Flickerstrip class FlickerstripDiscoveryClient: - def __init__(self): - self.client = SSDPClient(timeout=15) + def __init__(self, max_attempts=3): + self.max_attempts = max_attempts + self.client = SSDPClient() def __discovered(self, device): loc = device["location"] @@ -15,13 +16,16 @@ class FlickerstripDiscoveryClient: return Flickerstrip(ip_address) def discover(self): - print("Discovering devices...") - devices = self.client.m_search("ssdp:all") - print(f"Discovered {len(devices)} device(s).") - filtered = [d for d in devices if "Flickerstrip" in d["server"]] - print(f"Discovered {len(filtered)} flickerstrip(s).") - mapped = [self.__discovered(d) for d in filtered] - return mapped + for i in range(self.max_attempts): + print(f"Discovering devices... (Attempt {i + 1})") + devices = self.client.m_search("ssdp:all") + print(f"Discovered {len(devices)} device(s).") + filtered = [d for d in devices if "Flickerstrip" in d["server"]] + print(f"Discovered {len(filtered)} flickerstrip(s).") + if len(filtered) > 0: + mapped = [self.__discovered(d) for d in filtered] + return mapped + return [] if __name__ == "__main__": diff --git a/flickerstrip_py/flickerstrip.py b/flickerstrip_py/flickerstrip.py index 83f8d45..8e17369 100644 --- a/flickerstrip_py/flickerstrip.py +++ b/flickerstrip_py/flickerstrip.py @@ -1,6 +1,6 @@ import requests -from status import Status -from pattern import PatternMeta +from flickerstrip_py.status import Status +from flickerstrip_py.pattern import PatternMeta, PatternBuilder class Flickerstrip: @@ -20,9 +20,17 @@ class Flickerstrip: response (requests.Response): The response from the request. Returns: - boolean: If the request was successful + bool: If the request was successful """ data = response.json() + + if "type" not in data: + if "id" in data: # create pattern response. + return True + print("ERROR: Unable to get response type!") + print(data) + return False + respType = data["type"] if respType == "error": message = data["message"] @@ -35,6 +43,7 @@ class Flickerstrip: return True else: print(f"ERROR: Unrecognized response type: {respType}.") + print(data) return False def __get_request(self, path, params=None): @@ -46,32 +55,34 @@ class Flickerstrip: request. Defaults to None. Returns: - boolean: If the request was successful + bool: If the request was successful """ resp = requests.get(f"http://{self.ip_address}/{path}", params=params) return self.__check_response(resp) - def __post_request(self, path, json=None, data=None): + def __post_request(self, path, json=None, params=None, data=None): """Send a POST request to the strip. Args: path (string): The path for the URL to request. json (dictionary, optional): The JSON encodable body for the request. Defaults to None. + params (dictionary, optional): The query string params for the + request. Defaults to None. data (string, optional): The raw data body. Defaults to None. Returns: - boolean: If the request was successful + bool: If the request was successful """ resp = requests.post(f"http://{self.ip_address}/{path}", - json=json, data=data) + params=params, json=json, data=data) return self.__check_response(resp) def force_update(self): """Force updates the status of the strip. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("status") @@ -79,7 +90,7 @@ class Flickerstrip: """Trigger the strip to turn the lights on. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("power/on") @@ -87,7 +98,7 @@ class Flickerstrip: """Trigger the strip to turn the lights off. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("power/off") @@ -95,7 +106,7 @@ class Flickerstrip: """Trigger the strip to toggle the power status. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("power/toggle") @@ -103,7 +114,7 @@ class Flickerstrip: """Trigger the strip to switch to the next pattern in memory. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("pattern/next") @@ -114,18 +125,19 @@ class Flickerstrip: frame (int): The frame number to freeze on. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("pattern/frame", {"value": frame}) def select_pattern(self, index): """Change the current pattern to the pattern at the specified index. +s of the flickerstrip. Args: index (int): The index of the pattern. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("pattern/select", {"index": index}) @@ -143,6 +155,40 @@ class Flickerstrip: raise TypeError( "Deleting a pattern requires one of the three args.") + def create_pattern(self, pattern, preview=True): + """Create a pattern on the flickerstrip. + + Args: + pattern (PatternBuilder): The pattern to send. + preview (bool, optional): Whether to only preview the pattern. + Defaults to True. + + Returns: + bool: If the request was succesful + """ + return self.__post_request("pattern/create", params={ + "name": pattern.name, + "fps": pattern.fps, + "frames": pattern.frame_count, + "pixels": pattern.pixel_count, + "preview": preview + }, data=pattern.to_binary_string()) + + def set_color(self, r, g, b): + """Sets the strip to a solid color (given as an RGB value). + + Args: + r (int): The red value (0 - 255) + g (int): The green value (0 - 255) + b (int): The blue value (0 - 255) + + Returns: + bool: If the request was successful + """ + pattern = PatternBuilder("Solid") + pattern.add_pixel(r, g, b) + return self.create_pattern(pattern) + def set_brightness(self, value): """Set the brightness of the flickerstrip. @@ -150,7 +196,7 @@ class Flickerstrip: value (int): The brightness level as an int from 0 to 100. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("brightness", {"value": value}) @@ -161,7 +207,7 @@ class Flickerstrip: value (string): The new name. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__post_request("config/name", {"name": value}) @@ -172,7 +218,7 @@ class Flickerstrip: value (string): The new group. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__post_request("config/group", {"name": value}) @@ -184,7 +230,7 @@ class Flickerstrip: Value is handled in seconds. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("config/cycle", {"value": value}) @@ -196,7 +242,7 @@ class Flickerstrip: Value is handled in seconds. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("config/fade", {"value": value}) @@ -207,7 +253,7 @@ class Flickerstrip: value (int): The length of the strip in pixels. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("config/length", {"value": value}) @@ -221,7 +267,7 @@ class Flickerstrip: value (int): The new first pixel of the strip. Returns: - boolean: If the request was successful. + bool: If the request was successful. """ return self.__get_request("config/start", {"value": value}) @@ -235,7 +281,7 @@ class Flickerstrip: value (int): The new last pixel of the strip. Returns: - boolean: If the request was successful. + bool: If the request was successful. """ return self.__get_request("config/end", {"value": value}) @@ -243,11 +289,11 @@ class Flickerstrip: """Set the reversed state of the flickerstrip. Args: - value (boolean): If the flickerstrip should animate + value (bool): If the flickerstrip should animate patterns in reverse. Returns: - boolean: If the request was succesful + bool: If the request was succesful """ return self.__get_request("config/reversed", {"value": 1 if value else 0}) diff --git a/flickerstrip_py/pattern.py b/flickerstrip_py/pattern.py index 7cb4921..4a3018a 100644 --- a/flickerstrip_py/pattern.py +++ b/flickerstrip_py/pattern.py @@ -19,3 +19,30 @@ class PatternMeta: json["id"], json["name"], json["frames"], json["pixels"], json["flags"], json["fps"] ) + + +class PatternBuilder: + def __init__(self, name, fps=1): + self.pixels = [] + self.name = name + self.fps = fps + self.frame_count = 1 + self.pixel_count = 0 + self.frame_pixels = 0 + + def add_pixel(self, r, g, b): + if self.frame_count == 1: + self.pixel_count += 1 + + self.frame_pixels += 1 + self.pixels += [r, g, b] + + def next_frame(self): + self.frame_count += 1 + self.frame_pixels = 0 + + def is_valid(self): + self.frame_pixels == self.pixel_count + + def to_binary_string(self): + return ''.join([chr(item) for item in self.pixels]) diff --git a/flickerstrip_py/status.py b/flickerstrip_py/status.py index b0319b8..055bb30 100644 --- a/flickerstrip_py/status.py +++ b/flickerstrip_py/status.py @@ -1,4 +1,4 @@ -from pattern import PatternMeta +from flickerstrip_py.pattern import PatternMeta class MemoryUsage: