#!/usr/bin/env python3 # # kitchentext: Read text from stdin and put it on the Kitchenlight. # # Author: Shy # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . def kitchentext(delay=200, skip_if_off=False, poweron=False, restore=False, wait=False, verbose=False, debug=False): import sys, signal from c4ctrl import C4Interface, Kitchenlight charwidth = { # Width of characters. 'a' : 5, 'A' : 5, 'b' : 4, 'B' : 5, 'c' : 3, 'C' : 5, 'd' : 4, 'D' : 5, 'e' : 4, 'E' : 5, 'f' : 3, 'F' : 5, 'g' : 3, 'G' : 5, 'h' : 3, 'H' : 5, 'i' : 1, 'I' : 5, 'j' : 2, 'J' : 5, 'k' : 3, 'K' : 5, 'l' : 3, 'L' : 5, 'm' : 5, 'M' : 5, 'n' : 4, 'N' : 5, 'o' : 3, 'O' : 5, 'p' : 3, 'P' : 5, 'q' : 3, 'Q' : 5, 'r' : 3, 'R' : 5, 's' : 4, 'S' : 5, 't' : 3, 'T' : 5, 'u' : 3, 'U' : 5, 'v' : 3, 'V' : 5, 'w' : 5, 'W' : 5, 'x' : 3, 'X' : 5, 'y' : 3, 'Y' : 5, 'z' : 3, 'Z' : 5, '0' : 5, '1' : 4, '2' : 5, '3' : 5, '4' : 5, '5' : 5, '6' : 5, '7' : 5, '8' : 5, '9' : 5, '@' : 5, '=' : 3, '!' : 1, '"' : 3, '_' : 5, '-' : 3, '.' : 2, ',' : 2, '*' : 5, ':' : 2, '\'' : 1, '/' : 5, '(' : 2, ')' : 2, '{' : 3, '}' : 3, '[' : 2, ']' : 2, '<' : 3, '>' : 4, '+' : 5, '#' : 5, '$' : 5, '%' : 5, '$' : 5, '~' : 5, '?' : 3, ';' : 2, '\\' : 5, '^' : 3, '|' : 1, '`' : 2, ' ' : 3, '\t' : 5 } C4Interface.debug = debug kl = Kitchenlight(autopower=poweron) # Enforce wait=True if restore=True wait = wait or restore # Store previous state. if restore or skip_if_off: c4 = C4Interface() saved_state = c4.pull([kl.topic, kl.powertopic]) if skip_if_off: # Stop here if kitchenlight is turned off. if saved_state[1].payload == b'\x00': verbose and print("Found Kitchenlight turned off and '-i' flag given. Exiting.") return # We want to be able to restore the saved Kitchenlight if we receive a # SIGERM signal. We do this by creating an registering a custom exception # class. class KitchenSignalError(Exception): def __init__(self, signal, frame): self.signal = signal self.frame = frame def signal_handler(signal, frame): raise KitchenSignalError(signal, frame) try: signal.signal(signal.SIGTERM, signal_handler) except: pass try: while True: try: text = sys.stdin.readline() except KeyboardInterrupt: # Exit a bit more graceful on CTRL-C. verbose and print("\nInterrupted by user.", file=sys.stderr) sys.exit(1) if text == "\n": # Empty line. verbose and print("Info: skipping empty line") continue elif text == "": # EOF. break # Strip chars Kitchenlight can not display. text = text.rstrip('\n').encode("ascii", "ignore").decode("ascii") try: # We might well get interrupted while waiting. kl.text(text, delay) if wait: from time import sleep # How long shall we wait? Kitchenlight has 30 columns, each # char uses 2-5 columns followed by an empty one. The # traversal of one column takes 30 * ms. text_width = 0 for c in text: try: text_width += (charwidth[c] + 1) except KeyError: # No width specified for this charachter. Let's use # 4 as default. text_width += 4 waiting_time = ((30 * delay) + (text_width * delay)) / 1000 verbose and print("Waiting for {} seconds ...".format(waiting_time)) sleep(waiting_time) except KeyboardInterrupt: verbose and print("\nInterrupted by user.", file=sys.stderr) sys.exit(1) except KitchenSignalError as error: verbose and print("\nInterrupted by signal {}".format(error.signal), file=sys.stderr) sys.exit(1) finally: # Always restore privious state if "restore" is set. if restore: re = [] for top in saved_state: re.append((top.topic, top.payload)) c4.push(re) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser( description="Read multiple lines of text from stdin and put them on \ the Kitchenlight.") parser.add_argument( "--debug", action="store_true", help="display what would be send to the MQTT broker, but do not \ actually connect") parser.add_argument( "-d", "--delay", type=int, default=200, help="delay in ms (speed of the text, default is 200)") parser.add_argument( "-f", "--fork", action="store_true", help="fork to background") parser.add_argument( "-F", action="store_true", help="like '-f', but print PID of forked process to stdout") parser.add_argument( "-i", "--skip-if-off", action="store_true", default=False, help="do nothing if the kitchenlight is turned off") parser.add_argument( "-p", "--power-on", action="store_true", default=False, help="turn on Kitchenlight if it is powered off") parser.add_argument( "-r", "--restore", action="store_true", default=False, help="restore the Kitchenlight to its prior state after the text has \ been displayed (implies --wait)") parser.add_argument( "-v", "--verbose", action="store_true", help="be more verbose") parser.add_argument( "-w", "--wait", action="store_true", default=False, help="wait until the text has been displayed") args = parser.parse_args() if args.fork or args.F: import sys from os import fork if sys.stdin.isatty(): print("Error: cannot fork when stdin is connected to a terminal!", file=sys.stderr) sys.exit(1) child_pid = fork() if child_pid != 0: if args.F: print(child_pid) elif args.verbose: print("Forked to PID", child_pid) sys.exit() kitchentext(delay=args.delay, skip_if_off=args.skip_if_off, poweron=args.power_on, restore=args.restore, wait=args.wait, verbose=args.verbose, debug=args.debug)