def run_xset(content: str) -> None: """ Processes a command """ try: payload: t.Dict[str, t.Any] = json.loads(content) except ValueError: log.error("Received malformed JSON '%s'", content) else: command = payload.get("command", None) if command: display = payload.get("display", CONFIG[CONF_KEY_DEFAULT_DISPLAY]) display = f"-display {display}" if display else "" # strip out commands that follow for security command = command.split(";")[0].split("&")[0].split("|")[0].strip() command = f"xset {display} {command}" log.debug("Running command '%s'", command) publish_queue.put_nowait( PublishMessage(path="xset/command", content=command)) result = subprocess.run(command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) log.trace("Command '%s' result: %s", command, result.stdout) publish_queue.put_nowait( PublishMessage(path="xset/stdout", content=result.stdout)) publish_queue.put_nowait( PublishMessage(path="xset/stderr", content=result.stderr)) else: log.error("No command provided in message '%s'", content)
def begin(self) -> bool: """ Setup the LED array **SUBCLASSES MUST SUPER() THIS METHOD** """ self._anim_cancel = threading.Event() self._anim_soft_cancel = threading.Event() self._anim_queue = queue.Queue() self._anim_manager = threading.Thread( name=f"manager_{self._name}", target=self._anim_queue_manager, daemon=False, ) self._anim_manager.start() common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/count", content=self._count, mqtt_retained=True)) common.publish_queue.put_nowait( PublishMessage( path=f"{self.id}/leds-per-pixel", content=self._per_pixel, mqtt_retained=True, )) common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/fps", content=self._fps, mqtt_retained=True)) return True
def _anim_loop(self, anim_args: t.Dict[str, t.Any]) -> None: """ **DO NOT OVERRIDE THIS METHOD** Internal animation thread loop method """ anim: str = anim_args[ANIM_KEY_NAME] repeat: int = anim_args[ANIM_KEY_REPEAT] remain: int = repeat or 1 error_count: int = 0 kwargs = { # args to pass into anim function k: anim_args[k] for k in anim_args if k not in [ANIM_KEY_NAME, ANIM_KEY_PRIORITY, ANIM_KEY_REPEAT] } while remain > 0 and not self._anim_cancel.is_set(): if self._anim_soft_cancel.is_set(): break try: common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/animation", content=anim)) self.anims[anim].__globals__[ "FRAME_MS"] = self._frame_ms # type: ignore self.anims[anim](self, self._anim_cancel, **kwargs) except: self._log.debug( "An error occurred while running animation '%s' for array '%s'", anim, self._name, ) error_count += 1 if error_count > 2: self._log.error( "Animation '%s' for '%s' errored 3 times, aborting", anim, self._name, ) log_traceback(self._log) break else: remain -= 1 if repeat else 0 if self._anim_cancel.is_set() or (anim_args.get(ANIM_KEY_REPEAT, 1) > 1 and self._anim_soft_cancel.is_set()): self._log.debug("Cancelled animation '%s' for array '%s'", anim, self._name) else: self._log.debug("Finished animation '%s' for array '%s'", anim, self._name) common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/animation", content=""))
def setup(self) -> bool: """ Sets up the device and makes sure it is available on the bus. Returns ``True`` if device is available, ``False`` otherwise. Subclasses may override this method but should ``super()`` it. """ if self._bus.fd is None: self._log.error( "Bus '%s' not initialized for %s '%s'", self._bus_path, self._device, self._name, ) else: # check device is on the line try: self._bus.write_quick(self.address) common.publish_queue.put_nowait( PublishMessage( path=f"{self._id}/address", content=f"0x{self._address:02x}", mqtt_retained=True, ) ) return True except IOError: self._log.error( "No ack from %s '%s' at address 0x%02x on I2C bus '%s'", self._device, self._name, self._address, self._bus_path, ) return False
def start() -> None: """ Actions to be done in the subprocess before the loop starts """ log.debug("Setting up hardware") for pin in [k for k in pins]: if not pins[pin].setup(): del pins[pin] del pin_from_path[[ k for k in pin_from_path if pin_from_path[k] == pin ][0]] if CONFIG[CONF_KEY_POLL_INT] > 0: log.debug("Starting polling timer with interval of %ds", CONFIG[CONF_KEY_POLL_INT]) global polling_timer polling_timer = Timer(CONFIG[CONF_KEY_POLL_INT], polling_timer_callback) polling_timer.start() common.publish_queue.put_nowait( PublishMessage( path="gpio/polling-interval", content=CONFIG[CONF_KEY_POLL_INT], mqtt_retained=True, )) poll_all()
def publish_state(self) -> None: """ Publishes the current device state """ if self._setup: data = self._bus.read(self._address, 8) if data is None: self._log.error("%s: Failed to publish state, data read error", self.name) else: self._log.trace("%s: Read raw data 0x%s from device", self.name, data.hex()) itemp = int.from_bytes(data[:2], byteorder="little", signed=False) itemp = int(f"0b{itemp >> 4:08b}"[-8:], base=0) itemp = int.from_bytes(bytes([itemp]), "big", signed=True) ftemp = float(itemp) data = int.from_bytes(data, byteorder="little", signed=False) ftemp += 0.5000 * float(data & (1 << 3) > 0) ftemp += 0.2500 * float(data & (1 << 2) > 0) ftemp += 0.1250 * float(data & (1 << 1) > 0) ftemp += 0.0625 * float(data & (1 << 0) > 0) if self._unit == "F": ftemp = ftemp * 9 / 5 + 32 self._log.debug("%s: Read temperature %0.3f %s", self.name, ftemp, self._unit) common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/temperature", content=ftemp)) common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/unit", content=self._unit)) else: self._log.error("%s: Failed to publish state, device is not setup", self.name)
def interrupt(self, state: bool) -> None: """ Handles pin change interrupts """ state = state ^ self._invert self._log.trace( "%s edge trigger fired with state %s logic %s for '%s' on %s", "Rising" if state else "Falling", TEXT_STATE[state], Logic(int(state ^ self._invert)).name, self.name, self.pin_name, ) common.publish_queue.put_nowait( PublishMessage(path=self.path, content=TEXT_STATE[state]))
def setup(self) -> bool: """ Sets up the device and makes sure it is available on the bus. Returns ``True`` if device is available, ``False`` otherwise. Subclasses may override this method but should ``super()`` it. """ if self.address in self._bus.scan(): common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/address", content=self.address, mqtt_retained=True)) return True else: self._log.error("Device %s '%s' was not found on the bus", self.device, self.name) return False
def publish_state(self) -> None: """ Read the state from the pin and publish """ if self._setup: state = self.get() if state is not None: self._log.debug( "Read state %s logic %s from '%s' on %s", TEXT_STATE[state], Logic(int(state ^ self._invert)).name, self.name, self.pin_name, ) common.publish_queue.put_nowait( PublishMessage(path=self.path, content=TEXT_STATE[state]))
def report(self) -> None: """Report counter data""" end_ns = now().timestamp() self._timer = threading.Timer(self._interval, self.report) self._timer.start() start_ns = end_ns - self._interval self._log.trace("start: %s", start_ns) self._log.trace(" end: %s", end_ns) self._log.trace("self._data: %s", self._data) data = [ event[1] for event in self._data if event[0] <= end_ns and event[0] > start_ns ] self._log.trace("data: %s", data) if data: self._data_lock.acquire() try: # keep the last record selected for this report in case it is # needed to calculate delta for next event self._data = self._data[ len([event for event in self._data if event[0] <= end_ns]) - 1:] finally: self._data_lock.release() message = self._functions[self._function](data) else: if self._function == Function.RAW: message = self._functions[Function.RAW](data) else: message = 0 self._log.debug( "Selected %d events from '%s' on %s using %s gives: %s", len(data), self.name, self.pin_name, self._function.name, message, ) common.publish_queue.put_nowait( PublishMessage(path=self.path, content=message))
def begin(self) -> bool: """ Setup the LED array and hardware """ self._log.info( "Setting up '%s' on sACN %s with %d LEDs%s on universe%s%s", self._name, "multicast" if self._address is None else f"unicast to '{self._address}'", self._count, f" with {self._per_pixel} LEDs per pixel" if self._per_pixel > 1 else "", f" {self._universes[0]}" if len(self._universes) == 1 else f"s {self._universes[0]}-{self._universes[-1]}", f" with sync on universe {self._sync}" if self._sync is not None else "", ) super().begin() global sender if not sender: try: import sacn as libsacn except ModuleNotFoundError: self._log.error( "MQTTany's LED module requires 'sacn' to be installed, " "please see the wiki for instructions on how to install requirements" ) return False else: sender = libsacn.sACNsender( source_name="MQTTany", fps=self._fps, sync_universe=self._sync if self._sync is not None else 63999, ) self._log.trace("Loaded sACNsender") global sender_started if not sender_started: sender_started = True sender.start() self._log.trace("Started sACNsender") for universe in self._universes: self._log.trace("Activating universe %d for '%s'", universe, self.name) sender.activate_output(universe) if self._address is None: sender[universe].multicast = True else: sender[universe].destination = self._address common.publish_queue.put_nowait( PublishMessage( path=f"{self.id}/universe", content= f"{self._universes[0]}{f'-{self._universes[-1]}' if len(self._universes)>1 else ''}", mqtt_retained=True, )) common.publish_queue.put_nowait( PublishMessage( path=f"{self.id}/mode", content="Unicast" if self._address else "Multicast", mqtt_retained=True, )) if self._address is not None: common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/address", content=self._address, mqtt_retained=True)) if self._sync is not None: common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/sync", content=self._sync, mqtt_retained=True)) self._setup = True return True
def begin(self) -> bool: """Setup the LED array""" self._log.info( "Setting up '%s' on GPIO%02d with %d %s LEDS %s", self._name, self._pin, self._count, self._chip, f"with {self._per_pixel} LEDs per pixel" if self._per_pixel > 1 else "", ) try: import rpi_ws281x except ModuleNotFoundError: self._log.error( "MQTTany's LED module requires 'rpi-ws281x' to be installed, " "please see the wiki for instructions on how to install requirements" ) return False if self._pin != 10: if not os.access("/dev/mem", os.R_OK | os.W_OK, effective_ids=True): self._log.error( "No read/write access to '/dev/mem', try running with root privileges" ) return False if gpio.board.lock(self._pin, gpio.common.Mode.SOC): try: self._array = rpi_ws281x.PixelStrip( num=self._count * self._per_pixel, pin=self._pin, freq_hz=self._frequency * 1000, dma=DMA_CHANNEL[self._pin], invert=self._invert, brightness=self._init_brightness, channel=PWM_CHANNEL[self._pin], strip_type=LED_COLOR_ORDERS[self._order] + LED_TYPES[self._chip], ) self._array.begin() except: self._log.error("An error occured while setting up '%s'", self._name) logger.log_traceback(self._log) return False else: super().begin() common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/gpio", content=self._pin, mqtt_retained=True)) common.publish_queue.put_nowait( PublishMessage(path=f"{self.id}/chip", content=self._chip, mqtt_retained=True)) common.publish_queue.put_nowait( PublishMessage( path=f"{self.id}/frequency", content=self._frequency, mqtt_retained=True, )) common.publish_queue.put_nowait( PublishMessage( path=f"{self.id}/invert", content=self._invert, mqtt_retained=True, )) del self._init_brightness del self._chip del self._frequency del self._invert self._setup = True return self._setup
def publish_state(self) -> None: state = self.state_log common.publish_queue.put_nowait( PublishMessage(path=self._path, content=TEXT_STATE[state]))