diff options
Diffstat (limited to 'NeoRuntime')
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/__init__.py | 2 | ||||
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/color_utils.py | 81 | ||||
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/matrix.py | 73 | ||||
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py | 266 | ||||
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/power_calc.py | 6 | ||||
-rw-r--r-- | NeoRuntime/Runtime/luxcena_neo/strip.py | 187 | ||||
-rw-r--r-- | NeoRuntime/Runtime/neo_runtime.py | 264 | ||||
-rw-r--r-- | NeoRuntime/Runtime/requirements.txt | 1 | ||||
-rw-r--r-- | NeoRuntime/builtin/fade/script.py | 28 | ||||
-rw-r--r-- | NeoRuntime/builtin/static/script.py | 17 | ||||
-rw-r--r-- | NeoRuntime/builtin/strandtest/script.py | 74 | ||||
-rw-r--r-- | NeoRuntime/special/template_base/script.py | 11 |
12 files changed, 1010 insertions, 0 deletions
diff --git a/NeoRuntime/Runtime/luxcena_neo/__init__.py b/NeoRuntime/Runtime/luxcena_neo/__init__.py new file mode 100644 index 0000000..606f365 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/__init__.py @@ -0,0 +1,2 @@ +from .neo_behaviour import NeoBehaviour, VariableType, ColorVariable, FloatVariable, IntegerVariable, BooleanVariable +import luxcena_neo.color_utils as utils diff --git a/NeoRuntime/Runtime/luxcena_neo/color_utils.py b/NeoRuntime/Runtime/luxcena_neo/color_utils.py new file mode 100644 index 0000000..3b7ece4 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/color_utils.py @@ -0,0 +1,81 @@ +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_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 + g = (color & 0x0000FF00) >> 8 + b = (color & 0x000000FF) + return (r, g, b) + +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. """ + print(hex_color) + value = hex_color.lstrip('#') + 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: + """ + 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])) 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 [<segment_num>, <reversed>] + # 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..4da3093 --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/neo_behaviour.py @@ -0,0 +1,266 @@ +import json +from os import path +from enum import Enum +from .strip import detect_format_convert_color +from .color_utils import rgb_from_twentyfour_bit, hex_from_twentyfour_bit + +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.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. """ + 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 + FLOAT = 3 + COLOR = 4 + BOOL = 5 + +class Variables: + + def __init__(self, package_path): + self.__vars = {} + self.__vars_save_file = "{}/state.json".format(package_path) + 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) + 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, variable): + """ Declare a new variable. """ + if variable.name in self.__vars: + raise Exception("Variable with name {} already defined.".format(variable.name)) + 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. """ + 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)) + + def to_dict(self): + return {x.name: x.to_dict() for x in self.__vars.values()} + +class Variable: + + 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 name(self): return self.__name + + @property + def value(self): return self.__value + + @value.setter + def value(self, value): + self.__value = value + 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 "{}: {}".format(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("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("Attempting to set {} to invalid value {}".format(self.name, 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(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: + 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} + + +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(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: + print("Attempted to set {} to \"{}\", which is not a valid float...".format(self.name, self.value)) + + 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) + super(BooleanVariable, type(self)).value.fset(self, value) + except: + print("Attempted to set {} to \"{}\", which is not a valid bool...".format(self.name, value)) 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..bc2e2be --- /dev/null +++ b/NeoRuntime/Runtime/luxcena_neo/strip.py @@ -0,0 +1,187 @@ +import json +from os import path +import rpi_ws281x as ws +from .matrix import Matrix, get_segment_range +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_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 + + # 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)] + + # 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.__set_brightness, + self.LED_CHANNEL, + strip_type=ws.WS2812_STRIP + ) + 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.") + + # 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: + 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.__set_brightness + })) + + @property + def power_on(self): + return self.__power_on + + @power_on.setter + def power_on(self, value: bool): + self.__power_on = value + self._set_brightness(self.__set_brightness if self.power_on else 0) + self.save_globvars() + + @property + def brightness(self): + return self.__actual_brightness + + @brightness.setter + def brightness(self, value: int): + if 0 <= value <= 255: + self.__set_brightness = value + if (self.power_on): + 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() + + 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, 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). + """ + 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[0], green=rgb[1], 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.") diff --git a/NeoRuntime/Runtime/neo_runtime.py b/NeoRuntime/Runtime/neo_runtime.py new file mode 100644 index 0000000..b028530 --- /dev/null +++ b/NeoRuntime/Runtime/neo_runtime.py @@ -0,0 +1,264 @@ +# This is the entry-point for all Luxcena-Neo python-scripts +# The script should be in the same folder as this, and be named "script.py" +# In the future you could possibly have more files and stuff alongside the "script.py"-file as well +import sys +import json +import importlib +import datetime +import argparse +import configparser +import time +import threading +import select +import traceback +import socket +from os import path, remove +from inspect import signature + +from luxcena_neo.strip import Strip + +def init_strip(strip_config_file): + """ Initialize a strip object with a config file path. """ + print("> Loading pixel-configuration...") + 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_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 + +def init_package(package_path, entry_module, strip): + """ Initialize the package we are going to run. """ + print ("> Initializing package (mode)...") + sys.path.append(package_path) + module = importlib.import_module(entry_module) + + # 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): + """ Create and start a new thread to run the package loop. """ + th = threading.Thread(target=module_executor_loop_func, daemon=True) + th.start() + return th + +class NeoRuntime: + + + 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 + print("> Running the mode...") + self.__module_th = exec_module(self.__module_loop) + + # This will run in this thread. + print("> Starting to listen on stdin") + self.__s = None + try: + 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 __socket_listener(self): + self.__s_clients = [] + last_send = time.perf_counter() + + while True: + if not self.__module_th.is_alive(): break + + 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: + try: + self.__execute_command(data) + except Exception as e: + traceback.print_exc() + + 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("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("Unknown variable ".format(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() + + self.__module_last_tick = time.perf_counter() + self.__module_last_second = time.perf_counter() + self.__module_last_minute = self.__module_last_second + self.__module_last_hour = self.__module_last_second + self.__module_last_day = self.__module_last_second + + while True: + c_time = time.perf_counter() + try: + self.__module_tick(c_time, c_time - self.__module_last_tick) + except Exception as e: + traceback.print_exc() + self.__module_last_tick = time.perf_counter() + + + def __module_tick(self, runningtime, deltatime): + if (len(signature(self.__module_entry_instance.each_tick).parameters) == 2): + self.__module_entry_instance.each_tick(deltatime) + else: + self.__module_entry_instance.each_tick() + + if (runningtime - self.__module_last_second > 1): + if (len(signature(self.__module_entry_instance.each_second).parameters) == 2): + self.__module_entry_instance.each_second(time.perf_counter() - self.__module_last_second) + else: + self.__module_entry_instance.each_second() + self.__module_last_second = time.perf_counter() + + if (((runningtime - self.__module_last_minute) % 60) >= 1): + if (len(signature(self.__module_entry_instance.each_minute).parameters) == 2): + self.__module_entry_instance.each_minute(time.perf_counter() - self.__module_last_minute) + else: + self.__module_entry_instance.each_minute() + self.__module_last_minute = time.perf_counter() + + if (((runningtime - self.__module_last_hour) % 3600) >= 1): + if (len(signature(self.__module_entry_instance.each_hour).parameters) == 2): + self.__module_entry_instance.each_hour(time.perf_counter() - self.__module_last_hour) + else: + self.__module_entry_instance.each_hour() + self.__module_last_hour = time.perf_counter() + + if (((runningtime - self.__module_last_day) % 86400) >= 1): + if (len(signature(self.__module_entry_instance.each_day).parameters) == 2): + self.__module_entry_instance.each_day(time.perf_counter() - self.__module_last_day) + else: + self.__module_entry_instance.each_day() + self.__module_last_day = time.perf_counter() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + 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("Strip config not found ({})".format(args.strip_config)) + sys.exit(1) + if not path.exists(args.mode_path): + print("Mode path not found ({})".format(args.mode_path)) + sys.exit(1) + 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("StripConfig: ".format(args.strip_config)) + print("Module : ".format(args.mode_path, args.mode_entry)) + + 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/NeoRuntime/Runtime/requirements.txt b/NeoRuntime/Runtime/requirements.txt new file mode 100644 index 0000000..7a38911 --- /dev/null +++ b/NeoRuntime/Runtime/requirements.txt @@ -0,0 +1 @@ +websockets
\ No newline at end of file diff --git a/NeoRuntime/builtin/fade/script.py b/NeoRuntime/builtin/fade/script.py new file mode 100644 index 0000000..8fe902a --- /dev/null +++ b/NeoRuntime/builtin/fade/script.py @@ -0,0 +1,28 @@ +from luxcena_neo import NeoBehaviour, IntegerVariable + +def wheel(pos): + """Generate rainbow colors across 0-255 positions.""" + if pos < 85: + return (pos * 3, 255 - pos * 3, 0) + elif pos < 170: + pos -= 85 + return (255 - pos * 3, 0, pos * 3) + else: + pos -= 170 + return (0, pos * 3, 255 - pos * 3) + +class Main(NeoBehaviour): + + def declare_variables(self): + self.declare(IntegerVariable("speed", 2, min_val=1, max_val=50)) + + def on_start(self): + """ Execute when mode is selected. """ + self.i = 0 + + def each_tick(self): + self.i += self.var.speed + if self.i > 255: self.i = 0 + for i in range(strip.num_pixels()): + strip.set_pixel_color(i, wheel(self.i)) + strip.show() diff --git a/NeoRuntime/builtin/static/script.py b/NeoRuntime/builtin/static/script.py new file mode 100644 index 0000000..8916106 --- /dev/null +++ b/NeoRuntime/builtin/static/script.py @@ -0,0 +1,17 @@ +from luxcena_neo import NeoBehaviour, ColorVariable, utils + +class Main(NeoBehaviour): + + def declare_variables(self): + self.declare(ColorVariable("color", "#fafafa", on_change=self.set_color)) + + def on_start(self): + print("Script started, color: {}".format(self.var.color)) + + def set_color(self, value): + 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() + diff --git a/NeoRuntime/builtin/strandtest/script.py b/NeoRuntime/builtin/strandtest/script.py new file mode 100644 index 0000000..50bac1e --- /dev/null +++ b/NeoRuntime/builtin/strandtest/script.py @@ -0,0 +1,74 @@ +from luxcena_neo import NeoBehaviour, utils +import time + +def colorWipe(*color, wait_ms=50): + """Wipe color across display a pixel at a time.""" + for i in range(strip.num_pixels()): + strip.set_pixel_color(i, *color) + strip.show() + time.sleep(wait_ms/1000.0) + +def theaterChase(*color, wait_ms=50, iterations=10): + """Movie theater light style chaser animation.""" + for j in range(iterations): + for q in range(3): + for i in range(0, strip.num_pixels(), 3): + strip.set_pixel_color(i+q, *color) + strip.show() + time.sleep(wait_ms/1000.0) + for i in range(0, strip.num_pixels(), 3): + strip.set_pixel_color(i+q, 0) + +def wheel(pos): + """Generate rainbow colors across 0-255 positions.""" + if pos < 85: + return (pos * 3, 255 - pos * 3, 0) + elif pos < 170: + pos -= 85 + return (255 - pos * 3, 0, pos * 3) + else: + pos -= 170 + return (0, pos * 3, 255 - pos * 3) + +def rainbow(wait_ms=20, iterations=1): + """Draw rainbow that fades across all pixels at once.""" + for j in range(256*iterations): + for i in range(strip.num_pixels()): + strip.set_pixel_color(i, wheel((i+j) & 255)) + strip.show() + time.sleep(wait_ms/1000.0) + +def rainbowCycle(wait_ms=20, iterations=5): + """Draw rainbow that uniformly distributes itself across all pixels.""" + for j in range(256*iterations): + for i in range(strip.num_pixels()): + strip.set_pixel_color(i, wheel(int((i * 256 // strip.num_pixels()) + j) & 255)) + strip.show() + time.sleep(wait_ms/1000.0) + +def theaterChaseRainbow(wait_ms=50): + """Rainbow movie theater light style chaser animation.""" + for j in range(256): + for q in range(3): + for i in range(0, strip.num_pixels(), 3): + strip.set_pixel_color(i+q, wheel((i+j) % 255)) + strip.show() + time.sleep(wait_ms/1000.0) + for i in range(0, strip.num_pixels(), 3): + strip.set_pixel_color(i+q, 0) + +class Main(NeoBehaviour): + + def on_start(self): + colorWipe(*(255, 0, 0)) # Red wipe + colorWipe(*(0, 255, 0)) # Blue wipe + colorWipe(*(0, 0, 255)) # Green wipe + + while True: + theaterChase(*(127, 127, 127)) # White theater chase + theaterChase(*(127, 0, 0)) # Red theater chase + theaterChase(*( 0, 0, 127)) # Blue theater chase + + rainbow() + rainbowCycle() + theaterChaseRainbow() diff --git a/NeoRuntime/special/template_base/script.py b/NeoRuntime/special/template_base/script.py new file mode 100644 index 0000000..208a924 --- /dev/null +++ b/NeoRuntime/special/template_base/script.py @@ -0,0 +1,11 @@ +from luxcena_neo import NeoBehaviour + +class Main(NeoBehaviour): + + def on_start(self): + """ Execute when mode is selected. """ + print("Script started") + + def each_second(self): + """ Execute once every second. """ + print("A second has passed")
\ No newline at end of file |