2017-04-17 13:07:25 +02:00
|
|
|
#!/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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
2017-04-18 10:43:33 +02:00
|
|
|
def kitchentext(delay=200, skip_if_off=False, poweron=False, restore=False,
|
|
|
|
wait=False, verbose=False, debug=False):
|
2017-04-17 13:07:25 +02:00
|
|
|
|
|
|
|
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.
|
2017-04-18 10:43:33 +02:00
|
|
|
if restore or skip_if_off:
|
2017-04-17 13:07:25 +02:00
|
|
|
c4 = C4Interface()
|
|
|
|
saved_state = c4.pull([kl.topic, kl.powertopic])
|
|
|
|
|
2017-04-18 10:43:33 +02:00
|
|
|
if skip_if_off:
|
|
|
|
# Stop here if kitchenlight is turned off.
|
2017-04-18 18:38:32 +02:00
|
|
|
for state in saved_state:
|
|
|
|
if state.topic == kl.powertopic and state.payload == b'\x00':
|
|
|
|
verbose and print("Found Kitchenlight turned off and '-i' flag given. Exiting.")
|
|
|
|
return
|
2017-04-18 10:43:33 +02:00
|
|
|
|
2017-04-17 13:07:25 +02:00
|
|
|
# We want to be able to restore the saved Kitchenlight if we receive a
|
2017-04-18 18:38:32 +02:00
|
|
|
# SIGERM signal. We do this by creating and registering a custom exception
|
2017-04-17 13:07:25 +02:00
|
|
|
# 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 * <delay> 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")
|
2017-04-18 10:43:33 +02:00
|
|
|
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)")
|
2017-04-17 13:07:25 +02:00
|
|
|
parser.add_argument(
|
|
|
|
"-v", "--verbose", action="store_true",
|
|
|
|
help="be more verbose")
|
2017-04-18 10:43:33 +02:00
|
|
|
parser.add_argument(
|
|
|
|
"-w", "--wait", action="store_true", default=False,
|
|
|
|
help="wait until the text has been displayed")
|
2017-04-17 13:07:25 +02:00
|
|
|
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,
|
2017-04-18 10:43:33 +02:00
|
|
|
skip_if_off=args.skip_if_off,
|
2017-04-17 13:07:25 +02:00
|
|
|
poweron=args.power_on,
|
2017-04-18 10:43:33 +02:00
|
|
|
restore=args.restore,
|
|
|
|
wait=args.wait,
|
2017-04-17 13:07:25 +02:00
|
|
|
verbose=args.verbose,
|
|
|
|
debug=args.debug)
|
|
|
|
|