From 7bdce37fd3f18e2712e18c4e2c64cac69af0aca1 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 19 Sep 2021 19:43:11 +0200 Subject: :boom: Introduce new UI based on svelte, and rewrite a lot of the node app and the NeoRuntime --- NeoRuntime/Runtime/luxcena_neo/__init__.py | 2 + NeoRuntime/Runtime/luxcena_neo/color_utils.py | 21 +++ NeoRuntime/Runtime/luxcena_neo/matrix.py | 73 ++++++++++ NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 149 +++++++++++++++++++ NeoRuntime/Runtime/luxcena_neo/power_calc.py | 6 + NeoRuntime/Runtime/luxcena_neo/strip.py | 184 ++++++++++++++++++++++++ 6 files changed, 435 insertions(+) create mode 100644 NeoRuntime/Runtime/luxcena_neo/__init__.py create mode 100644 NeoRuntime/Runtime/luxcena_neo/color_utils.py create mode 100644 NeoRuntime/Runtime/luxcena_neo/matrix.py create mode 100644 NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py create mode 100644 NeoRuntime/Runtime/luxcena_neo/power_calc.py create mode 100644 NeoRuntime/Runtime/luxcena_neo/strip.py (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/__init__.py b/NeoRuntime/Runtime/luxcena_neo/__init__.py new file mode 100644 index 0000000..dfec639 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/__init__.py @@ -0,0 +1,2 @@ +from .neo_behaviour import NeoBehaviour, VariableType +import luxcena_neo.color_utils as utils \ No newline at end of file diff --git a/NeoRuntime/Runtime/luxcena_neo/color_utils.py b/NeoRuntime/Runtime/luxcena_neo/color_utils.py new file mode 100644 index 0000000..a17d8e4 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/color_utils.py @@ -0,0 +1,21 @@ +def hex_to_rgb(value: str) -> tuple: + """ Convert hex color to rgb tuple. """ + value = value.lstrip("#") + lv = len(value) + return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3)) + +def rgb_to_hex(rgb: tuple) -> str: + """ Convert rgb color to hex string. """ + return '#%02x%02x%02x' % rgb + +def rgb_from_24bit(color: int) -> tuple: + """ Convert 24-bit color value into a tuple representing the rgb values. """ + # w = (color & 0xFF000000) >> 24 + r = (color & 0x00FF0000) >> 16 + g = (color & 0x0000FF00) >> 8 + b = (color & 0x000000FF) + return (r, g, b) + +def hex_from_24bit(color: int) -> str: + """ Convert 24-bit color value into hex str. """ + rgb_to_hex(rgb_from_24bit(color)) diff --git a/NeoRuntime/Runtime/luxcena_neo/matrix.py b/NeoRuntime/Runtime/luxcena_neo/matrix.py new file mode 100644 index 0000000..b842789 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/matrix.py @@ -0,0 +1,73 @@ +def get_segment_range(segments, n): + """ Return a list of all the actual led-numbers in a segment """ + # Sum all the segments prior to the one we are looking for + i = 0 + start = 0 + while True: + if i >= n: break + start += segments[i] # Add number of leds in this segment to the start-index + i += 1 + + # Add all numbers in the segment we are looking for to a list + i = start + breakPoint = i + segments[n] + range = [] + while True: + range.append(i) + i += 1 + if i >= breakPoint: break + return range + + +class Matrix: + + def __init__(self, segments, matrix): + self.matrix = [] # Holds the matrix + self.x_len = 0 # The width of the matrix + self.y_len = len(matrix) # The heigth of the matrix + + for y_pos in range(len(matrix)): + y_val = matrix[y_pos] + + this_y = [] + for x_pos in range(len(y_val)): + # This gets the range of segment n + segment_range = get_segment_range(segments, matrix[y_pos][x_pos][0]) + + # This adds the range to the current row's list + # if in the config [, ] + # reversed == true, revese the list before adding it + this_y += reversed(segment_range) if matrix[y_pos][x_pos][1] else segment_range + + # This just finds the longest row in the matrix + if (len(this_y) > self.x_len): + self.x_len = len(this_y) + + self.matrix.append(this_y) + + def get(self, x, y): + """ Return the value of a place in the matrix given x and y coordinates """ + return self.matrix[y][x] + + def dump(self): + n_spacers = (self.x_len*6) // 2 - 6 + print( ("=" * n_spacers) + "Matrix dump" + ("=" * n_spacers) ) + + for y in self.matrix: + this_y_line = "" + for x in y: + this_y_line += ( ' ' * (5 - len(str(x))) ) + str(x) + ' ' + print(this_y_line) + + print("=" * (self.x_len*6)) + + +if __name__ == "__main__": + test_matrix = Matrix( + [2, 2, 2, 2, 2, 2, 2, 2, 2], + [ + [[0, False], [1, True], [2, False]], + [[3, True], [4, False], [5, True]], + [[6, False], [7, True], [8, False]] + ] + ) diff --git a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py new file mode 100644 index 0000000..9920ca4 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -0,0 +1,149 @@ +import json +from os import path +from enum import Enum + +class NeoBehaviour: + """ + This is the base-class "main" should inherit from! + All methods are blocking :) This means that you could potentially loose a "tick" + For example, if "eachSecond" is taking up the thread, and the clock goes from 11:58 to 12:02, "eachHour", will not be called. + """ + + def __init__(self, package_path): + """ + THIS METHOD SHOULD NOT BE OVERIDDEN! Use onStart if you want a setup-method!!! + Contains basic setup + """ + self.vars = Variables(package_path) + + def declare_variables(self): + """ This should be overridden, and ALL variables should be declared here. """ + return + + def on_start(self): + """ This method will be run right after __init__ """ + return + + def each_tick(self): + """ This method will be run every tick, that means every time the program has time """ + return + + def each_second(self): + """ This method is called every second (on the clock), given that the thread is open """ + return + + def each_minute(self): + """ This method is called every mintue (on the clock), given that the thread is open """ + return + + def each_hour(self): + """ This method is called every whole hour (on the clock), given that the thread is open """ + return + + def each_day(self): + """ This method is called every day at noon, given that the thread is open """ + return + +class VariableType(Enum): + TEXT = 1 + INT = 2 + RANGE = 3 + COLOR = 4 + +class Variables: + + def __init__(self, package_path): + self.__vars = {} + self.__vars_save_file = f"{package_path}/variables.json" + self.__saved_variables = {} + self.read_saved_variables() + + def __getattr__(self, name): + if name in ["_Variables__vars", "_Variables__vars_save_file", "_Variables__saved_variables"]: + return super(Variables, self).__getattr__(name) + return self.__vars[name].value + + 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 + + def __delattr__(self, name): + if name in ["_Variables__vars", "_Variables__vars_save_file", "_Variables__saved_variables"]: + super(Variables, self).__delattr__(name) + else: + del self.__vars[name] + + def __getitem__(self, name): + return self.__vars[name] + + def __setitem__(self, name, value): + self.__vars[name].value = value + + def __contains__(self, name): + return name in self.__vars + + def __repr__(self): + return json.dumps({name: var.value for name, var in self.__vars.items()}) + + def __str__(self): + return repr(self) + + def __dir__(self): + return super(Variables, self).__dir__() + [name for name in self.__vars.keys()] + + def __len__(self): + return len(self.__vars) + + def __iter__(self): + return iter(self.__vars.items()) + + def declare(self, name: str, default: any, var_type: VariableType, on_change = None, **kwargs): + """ 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) + + def read_saved_variables(self): + """ Read saved variable values from file. """ + if not path.exists(self.__vars_save_file): return + try: + with open(self.__vars_save_file, "r") as f: + self.__saved_variables = json.load(f) + except: + print("Could not load saved variables") + + def save_variables(self): + """ Save variable values to file. """ + self.__saved_variables = {name: var.value for name, var in self.__vars.items()} + with open(self.__vars_save_file, "w") as f: + f.write(json.dumps(self.__saved_variables)) + +class Variable: + + def __init__(self, save_func, key, default, var_type, on_change): + self.__save_func = save_func + self.__key = key + self.__value = default + self.__var_type = var_type + self.__on_change = on_change + + @property + def key(self): return self.__key + + @property + def value(self): return self.__value + + @value.setter + def value(self, value): + self.__value = value + 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 diff --git a/NeoRuntime/Runtime/luxcena_neo/power_calc.py b/NeoRuntime/Runtime/luxcena_neo/power_calc.py new file mode 100644 index 0000000..4f6a6d6 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/power_calc.py @@ -0,0 +1,6 @@ + + +def calcCurrent(pixels): + current = 0 + for pixel in pixels: + break diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py new file mode 100644 index 0000000..a65b3f0 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -0,0 +1,184 @@ +import json +from os import path +from .neopixel import * +from .matrix import Matrix, get_segment_range +from .power_calc import calcCurrent + + +class Strip: + + def __init__(self, strip_conf): + self.SEGMENTS = strip_conf["segments"] + + self.LED_FREQ_HZ = strip_conf["led_freq_hz"] # LED signal frequency in hertz (usually 800khz) + self.LED_CHANNEL = strip_conf["led_channel"] # Set to '1' for GPIOs 13, 19, 41, 45, 53 + self.LED_INVERT = strip_conf["led_invert"] # True to invert the signal, (when using NPN transistor level shift) + self.LED_PIN = strip_conf["led_pin"] # 18 uses PWM, 10 uses SPI /dev/spidev0.0 + self.LED_DMA = strip_conf["led_dma"] # DMA channel for generating the signal, on the newer ones, try 10 + self.LED_COUNT = sum(self.SEGMENTS) # Number of LEDs in strip + + if ("color_calibration" in strip_conf) and (strip_conf["color_calibration"] != ""): + self.COLOR_CALIBRATION = strip_conf["led_calibration"] + else: + self.COLOR_CALIBRATION = [(1,1,1) for x in range(self.LED_COUNT)] + + self.TMPCOLORSTATE = [0 for x in range(self.LED_COUNT)] + self.COLORSTATE = [0 for x in range(self.LED_COUNT)] + + self.LED_BRIGHTNESS = 255 + + self.strip = Adafruit_NeoPixel( + self.LED_COUNT, + self.LED_PIN, + self.LED_FREQ_HZ, + self.LED_DMA, + self.LED_INVERT, + self.LED_BRIGHTNESS, + self.LED_CHANNEL + ) + + self.strip.begin() + + # Blank out all the LEDs + i = 0 + while True: + self.strip.setPixelColor(i, 0) + i += 1 + if (i > self.LED_COUNT): break + self.strip.show() + + # Setup matrix + print(" * Generating matrix") + # try: + self.pixelMatrix = Matrix(self.SEGMENTS, strip_conf["matrix"]) + self.pixelMatrix.dump() + # except: + # print("Something went wrong while setting up your self-defined matrix.") + + self.__power_on = True + self.__brightness = 255 + self.__actual_brightness = self.__brightness + + self.__globvars_path = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "globvars.json") + if path.exists(self.__globvars_path): + try: + with open(self.__globvars_path, "r") as f: + globvars = json.load(f) + self.power_on = globvars["power_on"] + self.brightness = globvars["brightness"] + except: + print("Could not load saved globvars...") + + + def save_globvars(self): + with open(self.__globvars_path, "w") as f: + f.write(json.dumps({ + "power_on": self.__power_on, + "brightness": self.__brightness + })) + + @property + def power_on(self): + return self.__power_on + + @power_on.setter + def power_on(self, value: bool): + self.__power_on = value + if (self.power_on): + self.__actual_brightness = self.__brightness + # self.strip.setBrightness(self.__brightness) + else: + self.__actual_brightness = 0 + # self.strip.setBrightness(0) + self.save_globvars() + + @property + def brightness(self): + # return self.strip.getBrightness() + return self.__actual_brightness + + @brightness.setter + def brightness(self, value: int): + if 0 <= value <= 255: + self.__brightness = value + if (self.power_on): + self.__actual_brightness = value + # self.strip.setBrightness(value) + self.save_globvars() + else: + raise Exception(f"Value ({value}) outside allowed range (0-255)") + + def show(self): + """Update the display with the data from the LED buffer.""" + self.COLORSTATE = self.TMPCOLORSTATE + self.strip.show() + + def set_pixel_color(self, n, *color): + """Set LED at position n to the provided 24-bit color value (in RGB order). + """ + c = detect_format_convert_color(*color) + self.TMPCOLORSTATE[n] = c + # self.strip.setPixelColor(n, ) + + def set_pixel_color_XY(self, x, y, *color): + """Set LED at position n to the provided 24-bit color value (in RGB order). + """ + self.set_pixel_color(self.pixelMatrix.get(x, y), *color) + + def set_segment_color(self, segment, *color): + """Set a whole segment to the provided red, green and blue color. + Each color component should be a value from 0 to 255 (where 0 is the + lowest intensity and 255 is the highest intensity).""" + for n in get_segment_range(self.SEGMENTS, segment): + self.set_pixel_color(n, *color) + + def get_pixels(self): + """Return an object which allows access to the LED display data as if + it were a sequence of 24-bit RGB values. + """ + return self.strip.getPixels() + + def num_pixels(self): + """Return the number of pixels in the display.""" + return self.LED_COUNT + + def get_pixel_color(self, n): + """Get the 24-bit RGB color value for the LED at position n.""" + return self.strip.getPixelColor(n) + + +def color_from_rgb(red, green, blue, white=0): + """ + Convert the provided red, green, blue color to a 24-bit color value. + Each color component should be a value 0-255 + where 0 is the lowest intensity and 255 is the highest intensity. + """ + return (white << 24) | (red << 16) | (green << 8) | blue + + +def color_from_hex(hex_color: str): + """ Convert the provided hex code to a 24-bit color value. """ + value = hex_color.lstrip('#') + lv = len(value) + rgb = tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3)) + return color_from_rgb(red=rgb[1], green=rgb[0], blue=rgb[2]) + + +def detect_format_convert_color(*color) -> int: + """ + Detect format of a color and return its 24-bit color value. + + If parameter is only a str, it will be treated as a hex value. + If parameter is a tuple, the first three items in that tuple will be treated as a rgb value. + If parameter is a int, it will be treated as a 24-bit color value. + If there are 3 parameters, these will be treated as a rgb value. + """ + if (len(color) == 1) and (isinstance(color[0], str)): + return color_from_hex(color[0]) + if (len(color) == 1) and (isinstance(color[0], tuple)): + return color_from_rgb(*(color[0])) + if (len(color) == 1) and (isinstance(color[0], int)): + return color[0] + if (len(color) == 3): + return color_from_rgb(*color) + raise ValueError("Invalid parameters provided, check documentation.") \ No newline at end of file -- cgit v1.2.3 From 076c967a8aaac929735694f295ade5adaf8c9ff3 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 3 Oct 2021 16:41:40 +0200 Subject: :sparkles: Add actual different variable types, this also changes slightly how they are defined --- NeoRuntime/Runtime/luxcena_neo/__init__.py | 2 +- NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 131 +++++++++++++++++++++--- 2 files changed, 117 insertions(+), 16 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/__init__.py b/NeoRuntime/Runtime/luxcena_neo/__init__.py index dfec639..fbbc670 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 +from .neo_behaviour import NeoBehaviour, VariableType, ColorVariable, FloatVariable, IntegerVariable import luxcena_neo.color_utils as utils \ No newline at end of file diff --git a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py index 9920ca4..66678c4 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,14 @@ class NeoBehaviour: class VariableType(Enum): TEXT = 1 INT = 2 - RANGE = 3 + FLOAT = 3 COLOR = 4 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 +72,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 +105,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 +130,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 +151,100 @@ 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} -- cgit v1.2.3 From 5cc8e0a8ed605a15b95b707b9d1b805f32271e3f Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 3 Oct 2021 16:44:59 +0200 Subject: :building_construction: Use UNIX socket for IPC instead of stdin/out --- NeoRuntime/Runtime/luxcena_neo/strip.py | 2 +- NeoRuntime/Runtime/neo_runtime.py | 141 +++++++++++----- src/NeoRuntimeManager/IPC.js | 178 +++++++++++++++++++++ src/NeoRuntimeManager/RuntimeProcess.js | 78 ++------- src/NeoRuntimeManager/index.js | 34 ++-- src/UserData/index.js | 3 + .../MainControls/ControlComponents.svelte | 2 +- 7 files changed, 321 insertions(+), 117 deletions(-) create mode 100644 src/NeoRuntimeManager/IPC.js (limited to 'NeoRuntime/Runtime/luxcena_neo') 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..4ecbc97 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,10 +53,12 @@ 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): @@ -66,49 +68,113 @@ 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) + print(f"Strip power: {self.__strip.power_on}") + elif command[1] == 1: + self.__strip.brightness = command[2] + print(f"Strip brightness: {self.__strip.brightness}") + 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 +219,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) @@ -172,6 +241,6 @@ if __name__ == "__main__": 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...") diff --git a/src/NeoRuntimeManager/IPC.js b/src/NeoRuntimeManager/IPC.js new file mode 100644 index 0000000..817c049 --- /dev/null +++ b/src/NeoRuntimeManager/IPC.js @@ -0,0 +1,178 @@ +/** + * This module is used to communicate with a python NeoRuntime instance. + * + * @author jakobst1n. + * @since 3.10.2021 + */ + +const net = require("net"); +let logger = require(__basedir + "/src/logger"); + +/** @type {int} How long wait between each reconnection attempt */ +const RECONNECT_INTERVAL = 1000; +/** @type {Object} ENUM-ish for command that can be sent to neoruntime */ +const COMMAND = Object.freeze({SET_GLOB : 0, + SET_VAR : 1, + SET_SEND_STRIP_BUF: 2}); +/** @type {Object} ENUM-ish for globvars */ +const GLOBVAR = Object.freeze({POWER_ON : 0, + BRIGHTNESS: 1}); +/** @type {Object} ENUM-ish for what type of data neoruntime sends */ +const DATATYPE = Object.freeze({STATES : 1, + STRIP_BUF: 2}); + +/** + * class that will keep a active connection to a socket if possible, and + * automatically reconnect. It will emit events when data is received, + * and it will send commands to the process. */ +class IPC { + + constructor(_socketFile, _eventEmitter) { + this.socketFile = _socketFile; + this.eventEmitter = _eventEmitter; + + this.client; + this.connected = false; + this.reconnectInterval = false; + + this.globvars = {}; + this.variables = {}; + + this.reconnect(); + } + + /** + * If we are not already attempting to reconnect, this will start a + * interval that tries to reconnect. */ + reconnect() { + if (this.reconnectInterval === false) { + this.reconnectInterval = setInterval(this.tryOpenSocketConnection.bind(this), RECONNECT_INTERVAL); + } + } + + /** + * This will attempt to connect to the socket, and then setup all listeners + * if it succedes. */ + tryOpenSocketConnection() { + // logger.info("Attempting to start IPC"); + + this.client = net.createConnection(this.socketFile) + .on('connect', () => { + clearInterval(this.reconnectInterval); + this.reconnectInterval = false; + // logger.info("IPC Connected."); + }) + .on("ready", () => { + this.connected = true; + }) + .on('data', (data) => { + switch (data[0]) { + case DATATYPE.STATES: + let json_data; + try { + json_data = JSON.parse(data.toString("ascii", 1)); + } catch (e) { + logger.warning("Could not parse json data from neoruntime"); + return; + } + + if (json_data.hasOwnProperty("globvars")) { + forEachDiff(json_data["globvars"], this.globvars, (key, newVal) => { + this.eventEmitter.emit("change", key, newVal); + }); + this.globvars = json_data["globvars"]; + } + if (json_data.hasOwnProperty("variables")) { + forEachDiff(json_data["variables"], this.variables, (key, newVal) => { + this.eventEmitter.emit("change", `variable/${key}`, newVal); + }); + this.variables = json_data["variables"]; + } + break; + + default: + logger.info(data); + } + + }) + .on("timeout", () => { + logger.info("IPC Timeout"); + }) + .on("close", (hadError) => { + // logger.info("IPC Close, hadError: ", hadError); + this.connected = false; + this.reconnect(); + }) + .on("end", () => { + // logger.info("IPC End"); + this.connected = false; + }) + .on('error', (data) => { + // logger.info('IPC Server not active.'); + this.connected = false; + this.reconnect(); + }) + ; + } + + /** + * Will send a command to the socket if we have a active connection, + * if not it will just drop the command. there is no queue implemented + * for such events. */ + sendCommand(commandType, name, value) { + if (this.connected) { + let buf = Buffer.allocUnsafe(128); // It's fine, we know what we are doing + // let buf = Buffer.alloc(128); + + switch (commandType) { + case (COMMAND.SET_GLOB): + buf[1] = name; + buf[2] = value; + break; + case (COMMAND.SET_VAR): + if (name.length > 32) { return {success: false, reason: "name too long", detail: "max size of name is 32 bytes"}; } + if (name.length > 93) { return {success: false, reason: "value too long", detail: "max size of value is 93 bytes"}; } + buf[1] = name.length; + buf[2] = value.length; + buf.write(name, 3, name.length, "ascii"); + buf.write(value, 3+name.length, value.length, "ascii"); + break; + case (COMMAND.SET_SEND_STRIP_BUF): + buf[1] = (name) ? 1 : 0; + default: + logger.info(`IPC UNKNOWN COMMANDTYPE ${commandType}`) + return; + } + + buf[0] = commandType; + this.client.write(buf); + return {success: true} + } + return {success: false, reason: "socket not connected", detail: "This usually means the python script is not running"}; + } + +} + +const isObject = v => v && typeof v === 'object'; + +/** + * Will call callback on all the differences between the dicts + */ +function forEachDiff(dict1, dict2, callback) { + for (const key of new Set([...Object.keys(dict1), ...Object.keys(dict2)])) { + if (isObject(dict1[key]) && isObject(dict2[key])) { + if (dict1[key].value !== dict2[key].value) { + callback(key, dict1[key]); + } + } else if (dict1[key] !== dict2[key]) { + if (isObject(dict2[key]) && (dict1[key] == null)) { + dict2[key].value = null; + callback(key, dict2[key]) + } else { + callback(key, dict1[key]); + } + } + } +} + +module.exports = {IPC, COMMAND, GLOBVAR}; \ No newline at end of file diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js index 60f6a28..60c1de9 100644 --- a/src/NeoRuntimeManager/RuntimeProcess.js +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -3,10 +3,11 @@ let spawn = require("child_process"); class RuntimeProcess { - constructor(_modePath, _onVarChange, _eventEmitter) { + constructor(_modePath, _eventEmitter) { this.modePath = _modePath; this.logfile = `${this.modePath}/mode.log`; - + this.errfile = `${this.modePath}/mode.error`; + this.stdout = ""; this.stderr = ""; @@ -16,9 +17,6 @@ class RuntimeProcess { this.isRunning = false; this.exitCode = null; - this.variables = {}; - this.globvars = {}; - this.onVarChange = _onVarChange; this.eventEmitter = _eventEmitter; } @@ -29,7 +27,7 @@ class RuntimeProcess { } this.isRunning = true; this.proc = spawn.spawn( - "python3", + "python3", [ "-u", // This makes us able to get real-time output `${__basedir}/NeoRuntime/Runtime/neo_runtime.py`, @@ -44,28 +42,13 @@ class RuntimeProcess { }); fs.ensureFileSync(this.logfile); + fs.ensureFileSync(this.errfile); this.eventEmitter.emit("proc:start"); this.proc.stdout.on('data', (_stdout) => { let stdout_str = _stdout.toString(); - - let regex = /{ ":::data:": { (.*) } }/gi; - let data = stdout_str.match(regex); - stdout_str = stdout_str.replace(regex, () => ""); - - if ((data != null) && (data.length > 0)) { - try { - this.processVarData(data) - } catch {} - } - - if (stdout_str.replace("\n", "").replace(" ", "") == "") { - // In this case, we want to ignore the data. - } else { - // stdout_str = stdout_str.replace(/\n$/, "") - fs.appendFile(this.logfile, "\n====stdout====\n" + stdout_str); - this.eventEmitter.emit("proc:stdout", stdout_str); - } + fs.appendFile(this.logfile, `[${timestamp()}]: ` + stdout_str); + this.eventEmitter.emit("proc:stdout", stdout_str); }); this.proc.stdout.on('end', () => { @@ -73,9 +56,8 @@ class RuntimeProcess { }); this.proc.stderr.on('data', (_stderr) => { - // let stderr_str = _stderr.toString().replace(/\n$/, "") - let stderr_str = _stderr.toString() - fs.appendFile(this.logfile, "\n====stderr====\n" + stderr_str); + let stderr_str = _stderr.toString(); + fs.appendFile(this.errfile, `[${timestamp()}]: ` + stderr_str); this.eventEmitter.emit("proc:stderr", stderr_str); }); @@ -85,7 +67,7 @@ class RuntimeProcess { this.proc.on('close', (code) => { if (code) { - fs.appendFile(this.logfile, "\n====close====\nScript exited with code " + code.toString()); + fs.appendFile(this.logfile, `[${timestamp()}]: ` + "Script exited with code " + code.toString()); } this.eventEmitter.emit("proc:exit", 0); this.isRunning = false; @@ -106,45 +88,15 @@ class RuntimeProcess { console.log(err); } } - - processVarData(data) { - data = JSON.parse(data)[":::data:"]; - if (data.hasOwnProperty("globvars")) { - forEachDiff(data["globvars"], this.globvars, (key, newVal) => { - this.onVarChange("globvars", key, newVal); - }); - this.globvars = data["globvars"]; - } - if (data.hasOwnProperty("variables")) { - forEachDiff(data["variables"], this.variables, (key, newVal) => { - this.onVarChange("variables", key, newVal); - }); - this.variables = data["variables"]; - } - } - } -const isObject = v => v && typeof v === 'object'; - /** - * Will call callback on all the differences between the dicts + * Creates and returns a timestamp that can be used in logfiles. + * + * @return {string} timestamp */ -function forEachDiff(dict1, dict2, callback) { - for (const key of new Set([...Object.keys(dict1), ...Object.keys(dict2)])) { - if (isObject(dict1[key]) && isObject(dict2[key])) { - if (dict1[key].value !== dict2[key].value) { - callback(key, dict1[key]); - } - } else if (dict1[key] !== dict2[key]) { - if (isObject(dict2[key]) && (dict1[key] == null)) { - dict2[key].value = null; - callback(key, dict2[key]) - } else { - callback(key, dict1[key]); - } - } - } +function timestamp() { + return (new Date()).toISOString(); } module.exports = RuntimeProcess; diff --git a/src/NeoRuntimeManager/index.js b/src/NeoRuntimeManager/index.js index 62acb8a..6238323 100644 --- a/src/NeoRuntimeManager/index.js +++ b/src/NeoRuntimeManager/index.js @@ -8,6 +8,7 @@ const fs = require("fs"); const fsPromises = fs.promises; const RuntimeProcess = require("./RuntimeProcess"); +const IPC = require("./IPC"); let logger = require(__basedir + "/src/logger"); const EventEmitter = require('events'); @@ -20,6 +21,8 @@ let modeId = null; let modeExitCode = 0; /** @type {RuntimeProcess} This is the current RuntimeProcess instance */ let runtimeProcess = null; +/** @type {IPC} The IPC instance, used to communicate with the script */ +let ipc = null; /** @type {EventEmitter} This is used to emit events when things change */ const eventEmitter = new EventEmitter(); /** @type {boolean} If this is true, we will not do things the usual way */ @@ -83,13 +86,6 @@ function setMode(_modeId) { return {success: false, reason: "unknown modeId"}; } logger.info(`Changing mode to "${_modeId}".`); - - let globvarsTmp = {}; - let variablesTmp = {}; - if (runtimeProcess != null) { - globvarsTmp = runtimeProcess.globvars; - variablesTmp = runtimeProcess.variables; - } stopMode(); @@ -97,10 +93,9 @@ function setMode(_modeId) { neoModules.userData.config.activeMode = modeId; eventEmitter.emit("change", "mode", modeId); - runtimeProcess = new RuntimeProcess(getModePath(_modeId), onVariableChange, eventEmitter); - runtimeProcess.globvars = globvarsTmp; - runtimeProcess.variables = variablesTmp; + runtimeProcess = new RuntimeProcess(getModePath(_modeId), eventEmitter); startMode(); + return {success: true} }; @@ -194,7 +189,7 @@ function onVariableChange(location, name, newValue) { */ function getGlobvars() { if (!modeRunning()) { return {}; } - return runtimeProcess.globvars; + return ipc.globvars; } /** @@ -207,8 +202,15 @@ function getGlobvars() { */ function setGlobvar(name, value) { if (!modeRunning()) { return; } - runtimeProcess.proc.stdin.write(`:::setglob: ${name}:${value}\n`); - return {success: true} + + switch(name) { + case "power_on": + return ipc.sendCommand(IPC.COMMAND.SET_GLOB, IPC.GLOBVAR.POWER_ON, (value) ? 1 : 0); + case "brightness": + return ipc.sendCommand(IPC.COMMAND.SET_GLOB, IPC.GLOBVAR.BRIGHTNESS, value); + default: + return {success: false, reason: "unknown globvar", detail: name}; + } } /** @@ -218,7 +220,7 @@ function setGlobvar(name, value) { */ function getVariables() { if (!modeRunning()) { return {}; } - return runtimeProcess.variables; + return ipc.variables; } /** @@ -231,8 +233,7 @@ function getVariables() { */ function setVariable(name, value) { if (!modeRunning()) { return; } - runtimeProcess.proc.stdin.write(`:::setvar: ${name}:${value}\n`); - return {success: true} + return ipc.sendCommand(IPC.COMMAND.SET_VAR, name, value); } /** @@ -281,6 +282,7 @@ function stopDebugger() { module.exports = (_neoModules) => { neoModules = _neoModules; + ipc = new IPC.IPC(neoModules.userData.config.neoRuntimeIPC.socketFile, eventEmitter); return { event: eventEmitter, modes: listModes, diff --git a/src/UserData/index.js b/src/UserData/index.js index e5318c9..704c5d5 100644 --- a/src/UserData/index.js +++ b/src/UserData/index.js @@ -40,6 +40,9 @@ function ensureMainConfig() { if (config.DiscoveryServer.address == null) { config.DiscoveryServer.address = "https://erj46s.deta.dev"; } if (config.DiscoveryServer.broadcastSelf == null) { config.DiscoveryServer.broadcastSelf = false; } + if (config.neoRuntimeIPC == null) { config.neoRuntimeIPC = {}; } + if (config.neoRuntimeIPC.socketFile == null) { config.neoRuntimeIPC.socketFile = "/tmp/neo_runtime.sock"; } + fse.writeFileSync(__datadir + "/config/config.ini", ini.encode(config)) } diff --git a/src_frontend/Components/MainControls/ControlComponents.svelte b/src_frontend/Components/MainControls/ControlComponents.svelte index 5f6d165..65bd1c4 100644 --- a/src_frontend/Components/MainControls/ControlComponents.svelte +++ b/src_frontend/Components/MainControls/ControlComponents.svelte @@ -45,7 +45,7 @@ } name = name.replace("variable/", ""); - switch (value.var_type) { + switch (value.type) { case "COLOR": if (value.value == null) { delete colorVariables[name]; -- cgit v1.2.3 From 4850c11d87df287beacf5a5bd9012f7b54f13566 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 3 Oct 2021 20:01:31 +0200 Subject: :sprakles: Add BooleanVariable --- NeoRuntime/Runtime/luxcena_neo/__init__.py | 4 ++-- NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 15 +++++++++++++++ NeoRuntime/Runtime/neo_runtime.py | 14 ++++++-------- 3 files changed, 23 insertions(+), 10 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/__init__.py b/NeoRuntime/Runtime/luxcena_neo/__init__.py index fbbc670..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, ColorVariable, FloatVariable, IntegerVariable -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 66678c4..dc4609c 100644 --- a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -55,6 +55,7 @@ class VariableType(Enum): INT = 2 FLOAT = 3 COLOR = 4 + BOOL = 5 class Variables: @@ -248,3 +249,17 @@ class FloatVariable(Variable): 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/neo_runtime.py b/NeoRuntime/Runtime/neo_runtime.py index 4ecbc97..d132bff 100644 --- a/NeoRuntime/Runtime/neo_runtime.py +++ b/NeoRuntime/Runtime/neo_runtime.py @@ -59,7 +59,7 @@ class NeoRuntime: 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 @@ -82,7 +82,7 @@ class NeoRuntime: 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) @@ -126,7 +126,7 @@ class NeoRuntime: 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) @@ -145,22 +145,20 @@ class NeoRuntime: """ 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) - print(f"Strip power: {self.__strip.power_on}") elif command[1] == 1: self.__strip.brightness = command[2] - print(f"Strip brightness: {self.__strip.brightness}") else: print(f"Unknown globvar {command[1]}.") elif command[0] == 1: @@ -236,7 +234,7 @@ 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}") -- cgit v1.2.3 From 3af8c73910997a0a6b766a3eb1d624a327356bbd Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Wed, 6 Oct 2021 17:29:58 +0200 Subject: :sparkles: Add Color helper class (should be made the default for ColorVariable) --- NeoRuntime/Runtime/luxcena_neo/color_utils.py | 65 ++++++++++++++++++++++++- NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 2 +- 2 files changed, 64 insertions(+), 3 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/color_utils.py b/NeoRuntime/Runtime/luxcena_neo/color_utils.py index a17d8e4..ab29092 100644 --- a/NeoRuntime/Runtime/luxcena_neo/color_utils.py +++ b/NeoRuntime/Runtime/luxcena_neo/color_utils.py @@ -8,7 +8,7 @@ def rgb_to_hex(rgb: tuple) -> str: """ Convert rgb color to hex string. """ return '#%02x%02x%02x' % rgb -def rgb_from_24bit(color: int) -> tuple: +def rgb_from_twentyfour_bit(color: int) -> tuple: """ Convert 24-bit color value into a tuple representing the rgb values. """ # w = (color & 0xFF000000) >> 24 r = (color & 0x00FF0000) >> 16 @@ -16,6 +16,67 @@ def rgb_from_24bit(color: int) -> tuple: b = (color & 0x000000FF) return (r, g, b) -def hex_from_24bit(color: int) -> str: +def hex_from_twentyfour_bit(color: int) -> str: """ Convert 24-bit color value into hex str. """ rgb_to_hex(rgb_from_24bit(color)) + +def twentyfour_bit_from_rgb(red, green, blue, white=0): + """ + Convert the provided red, green, blue color to a 24-bit color value. + Each color component should be a value 0-255 + where 0 is the lowest intensity and 255 is the highest intensity. + """ + return (white << 24) | (red << 16) | (green << 8) | blue + + +def twentyfour_bit_from_hex(hex_color: str): + """ Convert the provided hex code to a 24-bit color value. """ + value = hex_color.lstrip('#') + lv = len(value) + rgb = tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3)) + return twentyfour_bit_from_rgb(red=rgb[1], green=rgb[0], blue=rgb[2]) + + +def detect_format_convert_color(*color) -> int: + """ + Detect format of a color and return its 24-bit color value. + + If parameter is only a str, it will be treated as a hex value. + If parameter is a tuple, the first three items in that tuple will be treated as a rgb value. + If parameter is a int, it will be treated as a 24-bit color value. + If there are 3 parameters, these will be treated as a rgb value. + """ + if (len(color) == 1) and (isinstance(color[0], str)): + return twentyfour_bit_from_hex(color[0]) + if (len(color) == 1) and (isinstance(color[0], tuple)): + return twentyfour_bit_from_rgb(*(color[0])) + if (len(color) == 1) and (isinstance(color[0], int)): + return color[0] + if (len(color) == 3): + return twentyfour_bit_from_rgb(*color) + raise ValueError("Invalid parameters provided, check documentation.") + + +class Color: + + def __init__(self, *color): + self.__color = detect_format_convert_color(*color) + + @property + def hex(self): return hex_from_twentyfour_bit(self.__color) + + @property + def rgb(self): return rgb_from_twentyfour_bit(self.__color) + + def __repr__(self): + return self.__color + + def __str__(self): + return str(repr(self)) + + def __int__(self): + return self.__color + + def __invert__(self): + rgb_color = self.rgb + return Color((255-rgb_color[0], 255-rgb_color[1], 255-rgb_color[2])) \ No newline at end of file diff --git a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py index dc4609c..5c89ca0 100644 --- a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -2,7 +2,7 @@ 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 +from .color_utils import rgb_from_twentyfour_bit, hex_from_twentyfour_bit class NeoBehaviour: """ -- cgit v1.2.3 From 72ad29efeb4709572e789a57aa94d00a0eaeb97d Mon Sep 17 00:00:00 2001 From: jakobst1n Date: Sun, 10 Oct 2021 20:33:48 +0000 Subject: :construction: Make python 3.5 compatible and fix some weird bugs --- NeoRuntime/Runtime/luxcena_neo/color_utils.py | 7 +++-- NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 20 +++++++------- NeoRuntime/Runtime/luxcena_neo/strip.py | 36 ++++++++++++++----------- NeoRuntime/Runtime/neo_runtime.py | 35 ++++++++++++------------ src/NeoRuntimeManager/RuntimeProcess.js | 1 + 5 files changed, 52 insertions(+), 47 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/color_utils.py b/NeoRuntime/Runtime/luxcena_neo/color_utils.py index ab29092..3b7ece4 100644 --- a/NeoRuntime/Runtime/luxcena_neo/color_utils.py +++ b/NeoRuntime/Runtime/luxcena_neo/color_utils.py @@ -31,10 +31,9 @@ def twentyfour_bit_from_rgb(red, green, blue, white=0): def twentyfour_bit_from_hex(hex_color: str): """ Convert the provided hex code to a 24-bit color value. """ + print(hex_color) value = hex_color.lstrip('#') - lv = len(value) - rgb = tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3)) - return twentyfour_bit_from_rgb(red=rgb[1], green=rgb[0], blue=rgb[2]) + return (int(value[0:2], 16) << 16) | (int(value[2:4], 16) << 8) | (int(value[4:6], 16)) def detect_format_convert_color(*color) -> int: @@ -79,4 +78,4 @@ class Color: def __invert__(self): rgb_color = self.rgb - return Color((255-rgb_color[0], 255-rgb_color[1], 255-rgb_color[2])) \ No newline at end of file + return Color((255-rgb_color[0], 255-rgb_color[1], 255-rgb_color[2])) diff --git a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py index 5c89ca0..ce0fb62 100644 --- a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -61,7 +61,7 @@ class Variables: def __init__(self, package_path): self.__vars = {} - self.__vars_save_file = f"{package_path}/state.json" + self.__vars_save_file = "{}/state.json".format(package_path) self.__saved_variables = {} self.read_saved_variables() @@ -109,7 +109,7 @@ class Variables: def declare(self, variable): """ Declare a new variable. """ if variable.name in self.__vars: - raise Exception(f"Variable with name {variable.name} already defined.") + raise Exception("Variable with name {} already defined.".format(variable.name)) if variable.name in self.__saved_variables: variable.value = self.__saved_variables[variable.name] @@ -164,7 +164,7 @@ class Variable: return {"name": self.name, "value": self.value, "type": self.var_type} def __str__(self): - return f"{self.name}: {self.value}" + return "{}: {}".format(self.name, self.value) def set_save_func(self, save_func): self.__save_func = save_func @@ -173,13 +173,13 @@ class ColorVariable(Variable): def __init__(self, name: str, *color, **kwargs): if not self.verify_color(*color): - raise Exception(f"Invalid color {color}") + raise Exception("Invalid color {}".format(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}") + print("Attempting to set {} to invalid value {}".format(self.name, color)) return super(ColorVariable, type(self)).value.fset(self, self.extract_interesting(*color)) @@ -218,9 +218,9 @@ class IntegerVariable(Variable): 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}].") + print("Attempted to set {} to {} but range is [{},{}].".format(self.name, value, self.__min, self.__max)) except ValueError: - print(f"Attempted to set {self.name} to \"{value}\", which is not a valid integer...") + print("Attempted to set {} to \"{}\", which is not a valid integer...".format(self.name, value)) def to_dict(self): return {"name": self.name, "value": self.value, "type": self.var_type, "min": self.__min, "max": self.__max} @@ -240,9 +240,9 @@ class FloatVariable(Variable): 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}].") + print("Attempted to set {} to {} but range is [{},{}].".format(self.name, value, self.__min, self.__max)) except ValueError: - print(f"Attempted to set {self.name} to \"{value}\", which is not a valid float...") + print("Attempted to set {} to \"{}\", which is not a valid float...".format(self.name, self.value)) def __str__(self): return round(self.value, 2) @@ -262,4 +262,4 @@ class BooleanVariable(Variable): try: value = bool(value) except: - print(f"Attempted to set {self.name} to \"{value}\", which is not a valid bool...") + print("Attempted to set {} to \"{}\", which is not a valid bool...".format(self.name, value)) diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py index bfe2bbc..32380da 100644 --- a/NeoRuntime/Runtime/luxcena_neo/strip.py +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -1,6 +1,6 @@ import json from os import path -from .neopixel import * +import rpi_ws281x as ws from .matrix import Matrix, get_segment_range from .power_calc import calcCurrent @@ -10,11 +10,11 @@ class Strip: def __init__(self, strip_conf): self.SEGMENTS = strip_conf["segments"] - self.LED_FREQ_HZ = strip_conf["led_freq_hz"] # LED signal frequency in hertz (usually 800khz) - self.LED_CHANNEL = strip_conf["led_channel"] # Set to '1' for GPIOs 13, 19, 41, 45, 53 + self.LED_FREQ_HZ = int(strip_conf["led_freq_hz"]) # LED signal frequency in hertz (usually 800khz) + self.LED_CHANNEL = int(strip_conf["led_channel"]) # Set to '1' for GPIOs 13, 19, 41, 45, 53 self.LED_INVERT = strip_conf["led_invert"] # True to invert the signal, (when using NPN transistor level shift) - self.LED_PIN = strip_conf["led_pin"] # 18 uses PWM, 10 uses SPI /dev/spidev0.0 - self.LED_DMA = strip_conf["led_dma"] # DMA channel for generating the signal, on the newer ones, try 10 + self.LED_PIN = int(strip_conf["led_pin"]) # 18 uses PWM, 10 uses SPI /dev/spidev0.0 + self.LED_DMA = int(strip_conf["led_dma"]) # DMA channel for generating the signal, on the newer ones, try 10 self.LED_COUNT = sum(self.SEGMENTS) # Number of LEDs in strip if ("color_calibration" in strip_conf) and (strip_conf["color_calibration"] != ""): @@ -26,15 +26,16 @@ class Strip: self.COLORSTATE = [0 for x in range(self.LED_COUNT)] self.LED_BRIGHTNESS = 255 - - self.strip = Adafruit_NeoPixel( + + self.strip = ws.Adafruit_NeoPixel( self.LED_COUNT, self.LED_PIN, self.LED_FREQ_HZ, self.LED_DMA, self.LED_INVERT, self.LED_BRIGHTNESS, - self.LED_CHANNEL + self.LED_CHANNEL, + strip_type=ws.WS2812_STRIP ) self.strip.begin() @@ -86,15 +87,17 @@ class Strip: self.__power_on = value if (self.power_on): self.__actual_brightness = self.__brightness - # self.strip.setBrightness(self.__brightness) + self.strip.setBrightness(self.__brightness) + self.strip.show() else: self.__actual_brightness = 0 - # self.strip.setBrightness(0) + self.strip.setBrightness(0) + self.strip.show() self.save_globvars() @property def brightness(self): - # return self.strip.getBrightness() + #return self.strip.getBrightness() return self.__actual_brightness @brightness.setter @@ -103,10 +106,11 @@ class Strip: self.__brightness = value if (self.power_on): self.__actual_brightness = value - # self.strip.setBrightness(value) + self.strip.setBrightness(value) + self.strip.show() self.save_globvars() else: - raise Exception(f"Value ({value}) outside allowed range (0-255)") + raise Exception("Value ({}) outside allowed range (0-255)".format(value)) def show(self): """Update the display with the data from the LED buffer.""" @@ -118,7 +122,7 @@ class Strip: """ c = detect_format_convert_color(*color) self.TMPCOLORSTATE[n] = c - # self.strip.setPixelColor(n, ) + self.strip.setPixelColor(n, c) def set_pixel_color_XY(self, x, y, *color): """Set LED at position n to the provided 24-bit color value (in RGB order). @@ -161,7 +165,7 @@ def color_from_hex(hex_color: str): value = hex_color.lstrip('#') lv = len(value) rgb = tuple(int(value[i:i+lv//3], 16) for i in range(0, lv, lv//3)) - return color_from_rgb(red=rgb[1], green=rgb[0], blue=rgb[2]) + return color_from_rgb(red=rgb[0], green=rgb[1], blue=rgb[2]) def detect_format_convert_color(*color) -> int: @@ -181,4 +185,4 @@ def detect_format_convert_color(*color) -> int: return color[0] if (len(color) == 3): return color_from_rgb(*color) - raise ValueError("Invalid parameters provided, check documentation.") \ No newline at end of file + raise ValueError("Invalid parameters provided, check documentation.") diff --git a/NeoRuntime/Runtime/neo_runtime.py b/NeoRuntime/Runtime/neo_runtime.py index 6f84763..b028530 100644 --- a/NeoRuntime/Runtime/neo_runtime.py +++ b/NeoRuntime/Runtime/neo_runtime.py @@ -23,13 +23,13 @@ def init_strip(strip_config_file): strip_config_obj = configparser.ConfigParser() strip_config_obj.read(args.strip_config) strip_config = dict(strip_config_obj.items("DEFAULT")) - strip_config["matrix"] = json.loads(strip_config["matrix"].replace('"', "")) - strip_config["segments"] = [int(x) for x in strip_config["segments"].split(" ")] - strip_config["led_channel"] = int(strip_config["led_channel"]) - strip_config["led_dma"] = int(strip_config["led_dma"]) - strip_config["led_freq_hz"] = int(strip_config["led_freq_hz"]) - strip_config["led_invert"] = (strip_config["led_invert"] == "false") - strip_config["led_pin"] = int(strip_config["led_pin"]) + strip_config["matrix"] = json.loads(strip_config_obj.get("DEFAULT", "matrix").replace('"', "")) + strip_config["segments"] = [int(x) for x in strip_config_obj.get("DEFAULT", "segments").split(" ")] + strip_config["led_channel"] = strip_config_obj.getint("DEFAULT", "led_channel") + strip_config["led_dma"] = strip_config_obj.getint("DEFAULT", "led_dma") + strip_config["led_freq_hz"] = strip_config_obj.getint("DEFAULT", "led_freq_hz") + strip_config["led_invert"] = strip_config_obj.getboolean("DEFAULT", "led_invert") + strip_config["led_pin"] = strip_config_obj.getint("DEFAULT", "led_pin") strip = Strip(strip_config) return strip @@ -38,11 +38,12 @@ def init_package(package_path, entry_module, strip): print ("> Initializing package (mode)...") sys.path.append(package_path) module = importlib.import_module(entry_module) - module_entry_instance = module.Main(package_path) # Make the strip instance available in our modules setattr(module, "strip", strip) + module_entry_instance = module.Main(package_path) + return module_entry_instance def exec_module(module_executor_loop_func): @@ -164,14 +165,14 @@ class NeoRuntime: elif command[1] == 1: self.__strip.brightness = command[2] else: - print(f"Unknown globvar {command[1]}.") + print("Unknown globvar {}.".format(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}") + print("Unknown variable ".format(name)) elif command[0] == 2: self.__send_strip_buffer = (command[1] == 1) else: @@ -245,19 +246,19 @@ if __name__ == "__main__": 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}).") + print("Strip config not found ({})".format(args.strip_config)) sys.exit(1) if not path.exists(args.mode_path): - print(f"Mode path not found ({args.mode_path}).") + print("Mode path not found ({})".format(args.mode_path)) sys.exit(1) - 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}).") + if not path.exists("{}/{}.py".format(args.mode_path, args.mode_entry)): + print("Mode entry not found in mode path ({}/{})".format(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("StripConfig: ".format(args.strip_config)) + print("Module : ".format(args.mode_path, args.mode_entry)) - print(f"> Starting \"{args.mode_path}\" in NeoRuntime.") + print("> Starting \"{}\" in NeoRuntime.".format(args.mode_path)) runtime = NeoRuntime(args.mode_path, args.mode_entry, args.strip_config, args.socket_file) runtime.start() print ("> NeoRuntime exited...") diff --git a/src/NeoRuntimeManager/RuntimeProcess.js b/src/NeoRuntimeManager/RuntimeProcess.js index 24614fa..c5c4749 100644 --- a/src/NeoRuntimeManager/RuntimeProcess.js +++ b/src/NeoRuntimeManager/RuntimeProcess.js @@ -28,6 +28,7 @@ class RuntimeProcess { this.isRunning = true; this.proc = spawn.spawn( `${__appdir}/NeoRuntime/Runtime/venv/bin/python`, + //"python", [ "-u", // This makes us able to get real-time output `${__appdir}/NeoRuntime/Runtime/neo_runtime.py`, -- cgit v1.2.3 From 76cd8292c5b80749ece1ab6558f3ed410a618f0d Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 10 Oct 2021 23:27:32 +0200 Subject: :hammer: Update some of the builtin scripts --- NeoRuntime/Runtime/luxcena_neo/strip.py | 14 ++++++++----- NeoRuntime/builtin/static/script.py | 36 +++++++-------------------------- NeoRuntime/builtin/strandtest/script.py | 2 +- app.js | 21 ++++++++++++------- 4 files changed, 31 insertions(+), 42 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py index 32380da..e8bf476 100644 --- a/NeoRuntime/Runtime/luxcena_neo/strip.py +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -1,6 +1,10 @@ import json from os import path +<<<<<<< Updated upstream import rpi_ws281x as ws +======= +# from .neopixel import * +>>>>>>> Stashed changes from .matrix import Matrix, get_segment_range from .power_calc import calcCurrent @@ -20,7 +24,7 @@ class Strip: if ("color_calibration" in strip_conf) and (strip_conf["color_calibration"] != ""): self.COLOR_CALIBRATION = strip_conf["led_calibration"] else: - self.COLOR_CALIBRATION = [(1,1,1) for x in range(self.LED_COUNT)] + self.COLOR_CALIBRATION = [0xffffffff for x in range(self.LED_COUNT)] self.TMPCOLORSTATE = [0 for x in range(self.LED_COUNT)] self.COLORSTATE = [0 for x in range(self.LED_COUNT)] @@ -70,14 +74,14 @@ class Strip: except: print("Could not load saved globvars...") - + def save_globvars(self): with open(self.__globvars_path, "w") as f: f.write(json.dumps({ "power_on": self.__power_on, "brightness": self.__brightness })) - + @property def power_on(self): return self.__power_on @@ -115,7 +119,7 @@ class Strip: def show(self): """Update the display with the data from the LED buffer.""" self.COLORSTATE = self.TMPCOLORSTATE - self.strip.show() + # self.strip.show() def set_pixel_color(self, n, *color): """Set LED at position n to the provided 24-bit color value (in RGB order). @@ -152,7 +156,7 @@ class Strip: def color_from_rgb(red, green, blue, white=0): - """ + """ Convert the provided red, green, blue color to a 24-bit color value. Each color component should be a value 0-255 where 0 is the lowest intensity and 255 is the highest intensity. diff --git a/NeoRuntime/builtin/static/script.py b/NeoRuntime/builtin/static/script.py index 505ab67..8916106 100644 --- a/NeoRuntime/builtin/static/script.py +++ b/NeoRuntime/builtin/static/script.py @@ -1,39 +1,17 @@ from luxcena_neo import NeoBehaviour, ColorVariable, utils -import time class Main(NeoBehaviour): - + def declare_variables(self): self.declare(ColorVariable("color", "#fafafa", on_change=self.set_color)) - self.declare(ColorVariable("color2", "#fafafa", on_change=self.set_color)) def on_start(self): - print(f"Script started, color: {self.var.color}") - self.set_color(self.var.color) - strip.power_on = True - self.current_color = self.var.color - + print("Script started, color: {}".format(self.var.color)) + def set_color(self, value): - print(f"Color var changed: {value}") - # transition_color(self.current_color, value, 1) - -def lerp(a, b, u): - return (1 - u) * a + u * b - -def transition_color(start_color, end_color, duration): - start_color = utils.hex_to_rgb(start_color) - end_color = utils.hex_to_rgb(end_color) - interval = 10 - steps = duration / interval - step_u = 1.0 / steps - u = 0 - - while u < 1: - r = round(lerp(start_color[0], end_color[0], u)) - g = round(lerp(start_color[1], end_color[1], u)) - b = round(lerp(start_color[2], end_color[2], u)) - for i in len(strip.LED_COUNT): - strip.set_pixel_color(i, (r, g, b)) + print("Color var changed: {}".format(value)) + print(utils.detect_format_convert_color(value)) + for i in range(strip.LED_COUNT): + strip.set_pixel_color(i, value) strip.show() - time.sleep(interval / 100) \ No newline at end of file diff --git a/NeoRuntime/builtin/strandtest/script.py b/NeoRuntime/builtin/strandtest/script.py index f7d013a..6d263ab 100644 --- a/NeoRuntime/builtin/strandtest/script.py +++ b/NeoRuntime/builtin/strandtest/script.py @@ -60,7 +60,7 @@ def theaterChaseRainbow(wait_ms=50): class Main(NeoBehaviour): - def onStart(self): + def on_start(self): # Do an endless loop with some default ixel test patterns while True: colorWipe(*(255, 0, 0)) # Red wipe diff --git a/app.js b/app.js index 715108b..044f68a 100644 --- a/app.js +++ b/app.js @@ -78,16 +78,24 @@ function getNetworkAddress() { } return results[Object.keys(results)[0]][0] } +let http = require("http"); function tryBroadcastSelf() { if (neoModules.userData.config.DiscoveryServer.broadcastSelf) { + let address = neoModules.userData.config.DiscoveryServer.address; + let port = 443; + if (address.includes(":")) { + address = address.split(":"); + port = parseInt(address[1]); + address = address[0]; + } const data = JSON.stringify({ address: `https://${getNetworkAddress()}:${neoModules.userData.config.HTTP.port}`, name: neoModules.userData.config.instanceName, widgetaddr: "/#/widget" }) const options = { - hostname: `${neoModules.userData.config.DiscoveryServer.address}`, - port: 443, + hostname: address, + port: port, path: "/HEY", method: "POST", headers: { @@ -95,12 +103,11 @@ function tryBroadcastSelf() { "Content-length": data.length } }; - let req = https.request(options, res => { + let req = http.request(options, res => { if (res.statusCode != 200) { res.on("data", (d) => logger.warning(d.toString())); } else { - res.on("data", (d) => logger.info(d.toString())); - logger.info("Broadcasted self") + // res.on("data", (d) => logger.info(d.toString())); } }); req.on("error", (error) => logger.warning(error.toString())) @@ -108,7 +115,7 @@ function tryBroadcastSelf() { req.end(); } } -// setInterval(tryBroadcastSelf, 30000); -// tryBroadcastSelf(); +setInterval(tryBroadcastSelf, 30000); +tryBroadcastSelf(); // setInterval(() => { logger.notice("I feel FANTASTIC, an I'm still alive. Uptime: " + process.uptime()); }, 600000); -- cgit v1.2.3 From 7e4490432db60c13971f9938b445e0a207d49d49 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl Date: Sun, 10 Oct 2021 23:30:58 +0200 Subject: :hammer: Fix merge conflict --- NeoRuntime/Runtime/luxcena_neo/strip.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py index e8bf476..e973ed2 100644 --- a/NeoRuntime/Runtime/luxcena_neo/strip.py +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -1,10 +1,6 @@ import json from os import path -<<<<<<< Updated upstream import rpi_ws281x as ws -======= -# from .neopixel import * ->>>>>>> Stashed changes from .matrix import Matrix, get_segment_range from .power_calc import calcCurrent -- cgit v1.2.3 From ef4d96d3c74e67e6652f85711b05075e91c4760e Mon Sep 17 00:00:00 2001 From: Jakob Stendahl <14180120+JakobST1n@users.noreply.github.com> Date: Mon, 11 Oct 2021 08:20:45 +0200 Subject: :zap: Improve code structure somewhat --- NeoRuntime/Runtime/luxcena_neo/strip.py | 55 ++++++++++++++++----------------- 1 file changed, 27 insertions(+), 28 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/strip.py b/NeoRuntime/Runtime/luxcena_neo/strip.py index e973ed2..bc2e2be 100644 --- a/NeoRuntime/Runtime/luxcena_neo/strip.py +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -8,36 +8,44 @@ from .power_calc import calcCurrent class Strip: def __init__(self, strip_conf): + # Read in all config options self.SEGMENTS = strip_conf["segments"] - self.LED_FREQ_HZ = int(strip_conf["led_freq_hz"]) # LED signal frequency in hertz (usually 800khz) self.LED_CHANNEL = int(strip_conf["led_channel"]) # Set to '1' for GPIOs 13, 19, 41, 45, 53 - self.LED_INVERT = strip_conf["led_invert"] # True to invert the signal, (when using NPN transistor level shift) + self.LED_INVERT = strip_conf["led_invert"] # True to invert the signal, (when using NPN transistor level shift) self.LED_PIN = int(strip_conf["led_pin"]) # 18 uses PWM, 10 uses SPI /dev/spidev0.0 self.LED_DMA = int(strip_conf["led_dma"]) # DMA channel for generating the signal, on the newer ones, try 10 - self.LED_COUNT = sum(self.SEGMENTS) # Number of LEDs in strip - + self.LED_COUNT = sum(self.SEGMENTS) # Number of LEDs in strip + + # Setup the color calibration array if ("color_calibration" in strip_conf) and (strip_conf["color_calibration"] != ""): self.COLOR_CALIBRATION = strip_conf["led_calibration"] else: self.COLOR_CALIBRATION = [0xffffffff for x in range(self.LED_COUNT)] + # Setup some buffers we can use to keep track of what will be displayed + # and what is displayed (could use rpi_ws281x functions for this maybe) self.TMPCOLORSTATE = [0 for x in range(self.LED_COUNT)] self.COLORSTATE = [0 for x in range(self.LED_COUNT)] - self.LED_BRIGHTNESS = 255 + # Keeping the state of the strip power + self.__power_on = True + # Keeping what the brightness is set to + self.__set_brightness = 255 + # Keeping what the brightness actually is + self.__actual_brightness = self.__set_brightness + # Setup the strip instance self.strip = ws.Adafruit_NeoPixel( self.LED_COUNT, self.LED_PIN, self.LED_FREQ_HZ, self.LED_DMA, self.LED_INVERT, - self.LED_BRIGHTNESS, + self.__set_brightness, self.LED_CHANNEL, strip_type=ws.WS2812_STRIP ) - self.strip.begin() # Blank out all the LEDs @@ -55,11 +63,8 @@ class Strip: self.pixelMatrix.dump() # except: # print("Something went wrong while setting up your self-defined matrix.") - - self.__power_on = True - self.__brightness = 255 - self.__actual_brightness = self.__brightness - + + # Read in state file, so we can revoces the last state. self.__globvars_path = path.join(path.split(path.dirname(path.abspath(__file__)))[0], "state.json") if path.exists(self.__globvars_path): try: @@ -70,12 +75,11 @@ class Strip: except: print("Could not load saved globvars...") - def save_globvars(self): with open(self.__globvars_path, "w") as f: f.write(json.dumps({ "power_on": self.__power_on, - "brightness": self.__brightness + "brightness": self.__set_brightness })) @property @@ -85,37 +89,32 @@ class Strip: @power_on.setter def power_on(self, value: bool): self.__power_on = value - if (self.power_on): - self.__actual_brightness = self.__brightness - self.strip.setBrightness(self.__brightness) - self.strip.show() - else: - self.__actual_brightness = 0 - self.strip.setBrightness(0) - self.strip.show() + self._set_brightness(self.__set_brightness if self.power_on else 0) self.save_globvars() @property def brightness(self): - #return self.strip.getBrightness() return self.__actual_brightness @brightness.setter def brightness(self, value: int): if 0 <= value <= 255: - self.__brightness = value + self.__set_brightness = value if (self.power_on): - self.__actual_brightness = value - self.strip.setBrightness(value) - self.strip.show() + self._set_brightness(value) self.save_globvars() else: raise Exception("Value ({}) outside allowed range (0-255)".format(value)) + + def _set_brightness(self, value): + self.__actual_brightness = value + self.strip.setBrightness(value) + self.show() def show(self): """Update the display with the data from the LED buffer.""" self.COLORSTATE = self.TMPCOLORSTATE - # self.strip.show() + self.strip.show() def set_pixel_color(self, n, *color): """Set LED at position n to the provided 24-bit color value (in RGB order). -- cgit v1.2.3 From 14335646c5f31047e51cbaa038fc3cec92e49b90 Mon Sep 17 00:00:00 2001 From: Jakob Stendahl <14180120+JakobST1n@users.noreply.github.com> Date: Mon, 11 Oct 2021 09:07:02 +0200 Subject: :construction: Add actual classtype to super calls --- NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'NeoRuntime/Runtime/luxcena_neo') diff --git a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py index ce0fb62..4da3093 100644 --- a/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -216,7 +216,7 @@ class IntegerVariable(Variable): try: value = int(value) if (self.__min <= value <= self.__max): - super(ColorVariable, type(self)).value.fset(self, value) + super(IntegerVariable, type(self)).value.fset(self, value) else: print("Attempted to set {} to {} but range is [{},{}].".format(self.name, value, self.__min, self.__max)) except ValueError: @@ -238,7 +238,7 @@ class FloatVariable(Variable): try: value = float(value) if (self.__min <= value <= self.__max): - super(ColorVariable, type(self)).value.fset(self, value) + super(FloatVariable, type(self)).value.fset(self, value) else: print("Attempted to set {} to {} but range is [{},{}].".format(self.name, value, self.__min, self.__max)) except ValueError: @@ -261,5 +261,6 @@ class BooleanVariable(Variable): def value(self, value): try: value = bool(value) + super(BooleanVariable, type(self)).value.fset(self, value) except: print("Attempted to set {} to \"{}\", which is not a valid bool...".format(self.name, value)) -- cgit v1.2.3