diff options
Diffstat (limited to 'NeoRuntime/Runtime/neo_runtime.py')
-rw-r--r-- | NeoRuntime/Runtime/neo_runtime.py | 264 |
1 files changed, 264 insertions, 0 deletions
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...") |