diff options
Diffstat (limited to 'NeoRuntime/Runtime')
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/__init__.py | 4 | ||||
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 146 | ||||
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/strip.py | 2 | ||||
-rw-r--r-- | NeoRuntime/Runtime/neo_runtime.py | 143 |
4 files changed, 239 insertions, 56 deletions
diff --git a/NeoRuntime/Runtime/luxcena_neo/__init__.py b/NeoRuntime/Runtime/luxcena_neo/__init__.py index dfec639..606f365 100644 --- a/NeoRuntime/Runtime/luxcena_neo/__init__.py +++ b/NeoRuntime/Runtime/luxcena_neo/__init__.py @@ -1,2 +1,2 @@ -from .neo_behaviour import NeoBehaviour, VariableType -import luxcena_neo.color_utils as utils
\ No newline at end of file +from .neo_behaviour import NeoBehaviour, VariableType, ColorVariable, FloatVariable, IntegerVariable, BooleanVariable +import luxcena_neo.color_utils as utils diff --git a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py index 9920ca4..dc4609c 100644 --- a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -1,6 +1,8 @@ import json from os import path from enum import Enum +from .strip import detect_format_convert_color +from .color_utils import rgb_from_24bit, hex_from_24bit class NeoBehaviour: """ @@ -14,7 +16,11 @@ class NeoBehaviour: THIS METHOD SHOULD NOT BE OVERIDDEN! Use onStart if you want a setup-method!!! Contains basic setup """ - self.vars = Variables(package_path) + self.var = Variables(package_path) + + self.declare = self.var.declare + self.declare_variables() + del self.declare def declare_variables(self): """ This should be overridden, and ALL variables should be declared here. """ @@ -47,14 +53,15 @@ class NeoBehaviour: class VariableType(Enum): TEXT = 1 INT = 2 - RANGE = 3 + FLOAT = 3 COLOR = 4 + BOOL = 5 class Variables: def __init__(self, package_path): self.__vars = {} - self.__vars_save_file = f"{package_path}/variables.json" + self.__vars_save_file = f"{package_path}/state.json" self.__saved_variables = {} self.read_saved_variables() @@ -66,8 +73,6 @@ class Variables: def __setattr__(self, name, value): if name in ["_Variables__vars", "_Variables__vars_save_file", "_Variables__saved_variables"]: super(Variables, self).__setattr__(name, value) - elif type(value) == Variable: - self.__vars[name] = value else: self.__vars[name].value = value @@ -101,12 +106,15 @@ class Variables: def __iter__(self): return iter(self.__vars.items()) - def declare(self, name: str, default: any, var_type: VariableType, on_change = None, **kwargs): + def declare(self, variable): """ Declare a new variable. """ - if name in self.__saved_variables: - default = self.__saved_variables[name] - var = Variable(self.save_variables, name, default, var_type, on_change, **kwargs) - self.__setattr__(name, var) + if variable.name in self.__vars: + raise Exception(f"Variable with name {variable.name} already defined.") + if variable.name in self.__saved_variables: + variable.value = self.__saved_variables[variable.name] + + variable.set_save_func(self.save_variables) + self.__vars[variable.name] = variable def read_saved_variables(self): """ Read saved variable values from file. """ @@ -123,17 +131,20 @@ class Variables: with open(self.__vars_save_file, "w") as f: f.write(json.dumps(self.__saved_variables)) + def to_dict(self): + return {x.name: x.to_dict() for x in self.__vars.values()} + class Variable: - def __init__(self, save_func, key, default, var_type, on_change): - self.__save_func = save_func - self.__key = key + def __init__(self, name, default, var_type: VariableType, on_change = None): + self.__name = name self.__value = default self.__var_type = var_type self.__on_change = on_change + self.__save_func = None @property - def key(self): return self.__key + def name(self): return self.__name @property def value(self): return self.__value @@ -141,9 +152,114 @@ class Variable: @value.setter def value(self, value): self.__value = value - self.__save_func() + if self.__save_func is not None: + self.__save_func() if self.__on_change is not None: self.__on_change(self.value) @property def var_type(self): return self.__var_type.name + + def to_dict(self): + return {"name": self.name, "value": self.value, "type": self.var_type} + + def __str__(self): + return f"{self.name}: {self.value}" + + def set_save_func(self, save_func): + self.__save_func = save_func + +class ColorVariable(Variable): + + def __init__(self, name: str, *color, **kwargs): + if not self.verify_color(*color): + raise Exception(f"Invalid color {color}") + super().__init__(name, self.extract_interesting(*color), VariableType.COLOR, **kwargs) + + @Variable.value.setter + def value(self, *color): + if not self.verify_color(*color): + print(f"Attempting to set {self.name} to invalid value {color}") + return + super(ColorVariable, type(self)).value.fset(self, self.extract_interesting(*color)) + + def verify_color(self, *color): + if (len(color) == 1) and (isinstance(color[0], str)): + return True + if (len(color) == 1) and (isinstance(color[0], tuple)): + if len(color[0]) != 3: return False + for c in color[0]: + if not (0 <= c <= 255): return False + return True + if (len(color) == 1) and (isinstance(color[0], int)): + return True + if (len(color) == 3): + for c in color: + if not isinstance(c, int): return False + if not (0 <= c <= 255): return False + return True + return False + + def extract_interesting(self, *color): + if (len(color) == 1): return color[0] + return color + +class IntegerVariable(Variable): + + def __init__(self, name: str, default: int = 0, min_val: int = 0, max_val: int = 255, **kwargs): + self.__min = min_val + self.__max = max_val + super().__init__(name, default, VariableType.INT) + + @Variable.value.setter + def value(self, value): + try: + value = int(value) + if (self.__min <= value <= self.__max): + super(ColorVariable, type(self)).value.fset(self, value) + else: + print(f"Attempted to set {self.name} to {value} but range is [{self.__min},{self.__max}].") + except ValueError: + print(f"Attempted to set {self.name} to \"{value}\", which is not a valid integer...") + + def to_dict(self): + return {"name": self.name, "value": self.value, "type": self.var_type, "min": self.__min, "max": self.__max} + + +class FloatVariable(Variable): + + def __init__(self, name: str, default: float = 0.0, min_val: float = 0.0, max_val: float = 255.0, **kwargs): + self.__min = min_val + self.__max = max_val + super().__init__(name, default, VariableType.FLOAT) + + @Variable.value.setter + def value(self, value): + try: + value = float(value) + if (self.__min <= value <= self.__max): + super(ColorVariable, type(self)).value.fset(self, value) + else: + print(f"Attempted to set {self.name} to {value} but range is [{self.__min},{self.__max}].") + except ValueError: + print(f"Attempted to set {self.name} to \"{value}\", which is not a valid float...") + + def __str__(self): + return round(self.value, 2) + + def to_dict(self): + return {"name": self.name, "value": self.value, "type": self.var_type, "min": self.__min, "max": self.__max} + + + +class BooleanVariable(Variable): + + def __init__(self, name: str, default: bool, **kwargs): + super().__init__(name, default, VariableType.BOOL) + + @Variable.value.setter + def value(self, value): + try: + value = bool(value) + except: + print(f"Attempted to set {self.name} to \"{value}\", which is not a valid bool...") diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py index a65b3f0..bfe2bbc 100644 --- a/NeoRuntime/Runtime/luxcena_neo/strip.py +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -59,7 +59,7 @@ class Strip: self.__brightness = 255 self.__actual_brightness = self.__brightness - self.__globvars_path = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "globvars.json") + self.__globvars_path = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "state.json") if path.exists(self.__globvars_path): try: with open(self.__globvars_path, "r") as f: diff --git a/NeoRuntime/Runtime/neo_runtime.py b/NeoRuntime/Runtime/neo_runtime.py index e5941e2..d132bff 100644 --- a/NeoRuntime/Runtime/neo_runtime.py +++ b/NeoRuntime/Runtime/neo_runtime.py @@ -11,7 +11,8 @@ import time import threading import select import traceback -from os import path +import socket +from os import path, remove from luxcena_neo.strip import Strip @@ -41,7 +42,6 @@ def init_package(package_path, entry_module, strip): # Make the strip instance available in our modules setattr(module, "strip", strip) - module_entry_instance.declare_variables() return module_entry_instance def exec_module(module_executor_loop_func): @@ -53,11 +53,13 @@ def exec_module(module_executor_loop_func): class NeoRuntime: - def __init__(self, package_path, entry_module, strip_config_file): + def __init__(self, package_path, entry_module, strip_config_file, socket_file): self.__strip = init_strip(strip_config_file) self.__module_entry_instance = init_package(package_path, entry_module, self.__strip) self.__module_th = None - + self.__socket_file = socket_file + self.__send_strip_buffer = False + def start(self): # The mode is starting in it's own thread @@ -66,49 +68,111 @@ class NeoRuntime: # This will run in this thread. print("> Starting to listen on stdin") + self.__s = None try: - self.__command_listener_loop() + self.__bind_socket() + self.__socket_listener() except KeyboardInterrupt: print("Exiting...") except Exception as e: traceback.print_exc() - + finally: + self.__close_socket() + + def __bind_socket(self): + if path.exists(self.__socket_file): + remove(self.__socket_file) + + self.__s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.__s.bind(self.__socket_file) + self.__s.listen(1) - def __command_listener_loop(self): + def __socket_listener(self): + self.__s_clients = [] last_send = time.perf_counter() + while True: if not self.__module_th.is_alive(): break - while sys.stdin in select.select([sys.stdin], [], [], 0)[0]: - line = sys.stdin.readline() - if line: - line = line.replace("\n", "") - if (line[0:10] == ":::setvar:"): - name, value = (line.split(" ", 1)[1]).replace("\"", "").split(":", 1) - if name in self.__module_entry_instance.vars: - self.__module_entry_instance.vars[name] = value - elif (line[0:11] == ":::setglob:"): - name, value = (line.split(" ", 1)[1]).replace("\"", "").split(":", 1) - if name == "brightness": - self.__strip.brightness = int(value) - elif name == "power_on": - self.__strip.power_on = value == "true" - else: - print(f"Unknown globvar \"{name}\"") - else: - if (time.perf_counter() - last_send) > 0.5: - _vars = "{" - for name, var in self.__module_entry_instance.vars: - _vars += f" \"{name}\" : {{ \"value\": \"{var.value}\", \"var_type\": \"{var.var_type}\" }}, " - if len(_vars) > 2: - _vars = _vars[0:-2] - _vars += "}" - globvars = "{ \"power_on\": " + str(self.__strip.power_on).lower() + ", " - globvars += " \"brightness\":" + str(self.__strip.brightness) + " }" - print(f"{{ \":::data:\": {{ \"variables\": {_vars}, \"globvars\": {globvars} }} }}") - last_send = time.perf_counter() + r, w, e = select.select([self.__s, *self.__s_clients], self.__s_clients, [], 0) + + if (time.perf_counter() - last_send) > 0.5: + states = { + "variables": self.__module_entry_instance.var.to_dict(), + "globvars": { + "power_on": self.__strip.power_on, + "brightness": self.__strip.brightness + } + } + buf = bytes([1]) + bytes(json.dumps(states), "ascii") + + for ws in w: + try: + ws.send(buf) + except BrokenPipeError: + self.__s_clients.remove(ws) + ws.close() + + last_send = time.perf_counter() + + for rs in r: + if rs is self.__s: + c, a = self.__s.accept() + self.__s_clients.append(c) + else: + data = rs.recv(128) + if not data: + self.__s_clients.remove(rs) + rs.close() + else: + self.__execute_command(data) + + def __close_socket(self): + if (self.__s is None): return + r, w, e = select.select([self.__s, *self.__s_clients], self.__s_clients, [], 0) + for ws in w: + try: + ws.shutdown(socket.SHUT_RDWR) + except BrokenPipeError: + ws.close() + self.__s_clients.remove(ws) + ws.close() + self.__s.close() + + + + def __execute_command(self, command): + """ + command should be of type bytes + first byte indicates command type (currently setglob or setvar) + + for command type 1 + byte 1 indicates which globvar + byte 2 indicates value + for command type 2 + first 32 bytes are the var name + + """ + # print(command.hex(" ")) + if command[0] == 0: + if command[1] == 0: + self.__strip.power_on = (command[2] == 1) + elif command[1] == 1: + self.__strip.brightness = command[2] + else: + print(f"Unknown globvar {command[1]}.") + elif command[0] == 1: + name = command[3:3+command[1]].decode("ascii") + value = command[3+command[1]:3+command[1]+command[2]].decode("ascii") + if name in self.__module_entry_instance.var: + self.__module_entry_instance.var[name] = value + else: + print(f"Unknown variable {name}") + elif command[0] == 2: + self.__send_strip_buffer = (command[1] == 1) + else: + print("UNKNOWN COMMAND") - def __module_loop(self): self.__module_entry_instance.on_start() @@ -153,11 +217,14 @@ if __name__ == "__main__": parser.add_argument('--strip-config', help='Path to the strip config file.') parser.add_argument('--mode-path', help='Path of the folder the mode is in.') parser.add_argument('--mode-entry', help='Path of the module that is the entry-point of the module.') + parser.add_argument('--socket-file', help='The socket file the runtime will use to allow communication [default: /tmp/neo_runtime.sock].', default='/tmp/neo_runtime.sock') + parser.add_argument('--socket-enable', help='Wether to enable socket communication [default: true].', default=True) args = parser.parse_args() args.strip_config = args.strip_config.replace("\"", "") args.mode_path = args.mode_path.replace("\"", "") args.mode_entry = args.mode_entry.replace("\"", "") + args.socket_file = args.socket_file.replace("\"", "") if not path.exists(args.strip_config): print(f"Strip config not found ({args.strip_config}).") sys.exit(1) @@ -167,11 +234,11 @@ if __name__ == "__main__": if not path.exists(f"{args.mode_path}/{args.mode_entry}.py"): print(f"Mode entry not found in mode path ({args.mode_path}/{args.mode_entry}).") sys.exit(1) - + print(f"StripConfig: {args.strip_config}") print(f"Module : {args.mode_path}/{args.mode_entry}") print(f"> Starting \"{args.mode_path}\" in NeoRuntime.") - runtime = NeoRuntime(args.mode_path, args.mode_entry, args.strip_config) + runtime = NeoRuntime(args.mode_path, args.mode_entry, args.strip_config, args.socket_file) runtime.start() print ("> NeoRuntime exited...") |