Merge branch 'masto-notifications' into 'master'

Audio and Masto Rewrites

See merge request bhowell/the-screenless-office!1
workspace
Brendan Howell 6 years ago
commit 2756a9a894

@ -1,13 +1,13 @@
import sys import sys
from . import officemgr from . import mgmt
def main(args=None): def main(args=None):
if args is None: if args is None:
args = sys.argv[1:] args = sys.argv[1:]
print("starting Screenless Office...") print("initializing Screenless Office...")
mgr = officemgr.OfficeManager() mgr = mgmt.Management()
print("starting Office Manager...") print("starting Management...")
mgr.run() mgr.run()

@ -1,4 +1,4 @@
import subprocess import vlc
from bureau import Bureau, add_command from bureau import Bureau, add_command
@ -17,7 +17,7 @@ class Audio(Bureau):
Bureau.__init__(self) Bureau.__init__(self)
self.urldb = self.open_db("urldb") self.urldb = self.open_db("urldb")
subprocess.call(["mocp", "-S"]) self.player = vlc.MediaPlayer()
@add_command("p", "Play an album, track or a live stream.") @add_command("p", "Play an album, track or a live stream.")
def play(self, data): def play(self, data):
@ -31,44 +31,58 @@ class Audio(Bureau):
url = self.urldb.get(shortcode) url = self.urldb.get(shortcode)
self.log.debug(" playing url " + url) self.log.debug(" playing url " + url)
subprocess.call(["mocp", "-c"]) self.player.set_mrl(url)
subprocess.call(["mocp", "-a", url]) self.player.play()
subprocess.call(["mocp", "-p"])
@add_command("stop", "Halt audio playback.") @add_command("stop", "Halt audio playback.")
def stop(self): def stop(self):
""" """
Stops all audio currently playing audio output. Stops all audio currently playing audio output.
""" """
subprocess.call(["mocp", "-P"]) self.player.pause()
@add_command("resu", "Resume playback.") @add_command("resu", "Resume playback.")
def resume(self): def resume(self):
""" """
Resume playback of paused audio. Resume playback of paused audio.
""" """
subprocess.call(["mocp", "-U"]) self.player.play()
@add_command("next", "Play the next song.") @add_command("next", "Play the next song.")
def play_next(self): def play_next(self):
""" """
Skip to the next song in the playlist or album. Skip to the next song in the playlist or album.
""" """
subprocess.call(["mocp", "-f"]) #subprocess.call(["mocp", "-f"])
# TODO
pass
@add_command("prev", "Play the previous song.") @add_command("prev", "Play the previous song.")
def play_prev(self): def play_prev(self):
""" """
Skip to the previous song in the playlist or album. Skip to the previous song in the playlist or album.
""" """
subprocess.call(["mocp", "-r"]) #subprocess.call(["mocp", "-r"])
# TODO
pass
@add_command("nowp", "Now Playing") @add_command("nowp", "Now Playing")
def now_playing(self): def now_playing(self):
""" """
Prints the currently playing song or stream on the small printer. Prints the currently playing song or stream on the small printer.
""" """
out = subprocess.check_output(["mocp", "-i"]).decode("utf-8") #out = subprocess.check_output(["mocp", "-i"]).decode("utf-8")
# TODO: sort out how to do this with
out = "Now Playing: "
out += self.player.get_media().get_meta(vlc.Meta.Title) + "\n"
nowplaying = self.player.get_media().get_meta(vlc.Meta.NowPlaying)
if nowplaying == "":
out += "by " + self.player.get_media().get_meta(vlc.Meta.Title) + "\n"
out += "from the album '" + self.player.get_media().get_meta(vlc.Meta.Album) \
+ "'\n"
else:
out += nowplaying + "\n"
self.log.debug("info output:" + out) self.log.debug("info output:" + out)
self.print_small(out) self.print_small(out)

@ -7,9 +7,9 @@ import os.path
import random import random
import string import string
import subprocess import subprocess
import sys
import tempfile import tempfile
import textwrap import textwrap
#import traceback
import threading import threading
import lmdb import lmdb
@ -60,6 +60,19 @@ def add_api(apistr, name=""):
return decorator return decorator
#def log_traceback(func):
# """ this is a decorator that catches tracebacks for logging"""
# def wrapper(*args):
# my_bureau = args[0]
# try:
# func(*args)
# except Exception as e:
# my_bureau.log.error("CRASH TRACE: {0}".format(my_bureau.name),
# exc_info=e)
# raise
# return wrapper
class LogPrinter(logging.Handler): class LogPrinter(logging.Handler):
""" """
LogPrinter prints logs on a receipt printer for screenless debugging. LogPrinter prints logs on a receipt printer for screenless debugging.
@ -151,11 +164,6 @@ class Bureau(object):
self.api = {} self.api = {}
modpath = os.path.dirname(__file__) modpath = os.path.dirname(__file__)
#slimerjs = os.path.join(modpath, "..", "lib", "slimerjs", "slimerjs")
#renderer = os.path.join(modpath, "..", "slimerjs", "rasterize.js")
#self.slimerjs = os.path.abspath(slimerjs)
#self.html2pdf = self.slimerjs + " --headless " + \
# os.path.abspath(renderer) + " "
mypath = inspect.getfile(self.__class__) mypath = inspect.getfile(self.__class__)
self.mdir = os.path.dirname(mypath) self.mdir = os.path.dirname(mypath)
@ -181,7 +189,6 @@ class Bureau(object):
log_format = logging.Formatter('LOG ${levelname} $name: $message', style='$') log_format = logging.Formatter('LOG ${levelname} $name: $message', style='$')
log_printer.setFormatter(log_format) log_printer.setFormatter(log_format)
self.log.addHandler(log_printer) self.log.addHandler(log_printer)
sys.excepthook = self._log_exception
# setup a dir to store files and data # setup a dir to store files and data
self.datadir = os.path.join(basepath, self.prefix) self.datadir = os.path.join(basepath, self.prefix)
@ -200,9 +207,9 @@ class Bureau(object):
self.log.debug("commands: ") self.log.debug("commands: ")
self.log.debug(str(self.commands)) self.log.debug(str(self.commands))
def _log_exception(typ, value, tb): # def _log_exception(typ, value, tb):
self.log.error("CRASH TRACE: {0}".format(str(value)), exc_info=(typ, value, tb)) # self.log.error("CRASH TRACE: {0}".format(str(value)), exc_info=(typ, value, tb))
sys.__excepthook__(typ, value, tb) # sys.__excepthook__(typ, value, tb)
def open_db(self, name): def open_db(self, name):
""" """
@ -361,6 +368,16 @@ class Bureau(object):
print(("hi! testing. " + self.name + " bureau seems to work!")) print(("hi! testing. " + self.name + " bureau seems to work!"))
return "seems to work." return "seems to work."
def _run_io(self):
"""
wrapper for run_io so that we can catch threaded exceptions and log
"""
try:
self.run_io()
except Exception as err:
self.log.exception("%s CRASHED with %s\n", self.name, err)
raise
def run_io(self): def run_io(self):
"""process hardware or timed input """process hardware or timed input
@ -375,6 +392,16 @@ class Bureau(object):
pass pass
def run(self): def run(self):
"""
wrapper running the main loop and logging all exceptions
"""
try:
self._run()
except Exception as err:
self.log.exception("%s CRASHED with %s\n", self.name, err)
raise
def _run(self):
""" """
main loop for processing messages main loop for processing messages
@ -382,7 +409,7 @@ class Bureau(object):
""" """
# start the hardware input handler # start the hardware input handler
io_handler = threading.Thread(target=self.run_io) io_handler = threading.Thread(target=self._run_io)
io_handler.start() io_handler.start()
# register commands and api methods # register commands and api methods
@ -398,10 +425,6 @@ class Bureau(object):
msg = self._recv.recv_string(flags=zmq.NOBLOCK) msg = self._recv.recv_string(flags=zmq.NOBLOCK)
else: else:
continue continue
# msg = self._recv.recv_string(flags=zmq.NOBLOCK)
#except zmq.ZMQError:
# time.sleep(0.05) # don't waste CPU
# continue
try: try:
self.log.debug("got message:" + msg) self.log.debug("got message:" + msg)
dot = msg.find(".") dot = msg.find(".")
@ -411,7 +434,6 @@ class Bureau(object):
self.log.debug("dot at %d", dot) self.log.debug("dot at %d", dot)
# TODO: maybe trim off the trailing "." for convenience # TODO: maybe trim off the trailing "." for convenience
data = msg[dot + 1:] data = msg[dot + 1:]
# data = str(data) # force to be a string
else: else:
data = None data = None
self.log.debug("data: " + str(data)) self.log.debug("data: " + str(data))

@ -292,6 +292,8 @@ class PublicRelations(Bureau):
prn.codepage = "cp437" prn.codepage = "cp437"
# TODO: add fancier formatting i.e. inverted text for username/handle # TODO: add fancier formatting i.e. inverted text for username/handle
# TODO: clean this up to use the built in formatting from escpos lib
# and make a print_status function to use for notifications too
toots = self.masto.timeline(limit=count) toots = self.masto.timeline(limit=count)
out = "" out = ""
for t in toots: for t in toots:
@ -324,8 +326,20 @@ class PublicRelations(Bureau):
tw_shortcode = self.short_tweet_id(str(t["id"])) tw_shortcode = self.short_tweet_id(str(t["id"]))
prn.barcode("PRmad." + tw_shortcode, "CODE128", function_type="B") prn.barcode("PRmad." + tw_shortcode, "CODE128", function_type="B")
prn.text("\r\n\r\n")
notifications = self.masto.notifications()
if len(notifications) > 0:
prn.set(text_type="B")
prn.text("NOTIFICATIONS:\r\n")
prn.set(text_type="NORMAL")
for note in notifications:
username = t.account.display_name.encode("cp437", "ignore") + \
b" (" + t.account.acct.encode("cp437", "ignore") + b")"
prn.text(note["type"] + " " + str(note["created_at"]) + " from ")
prn._raw(username)
prn.text(":\r\n" + str(note.keys()) + "\r\n")
prn.text("\r\n\r\n")
prn.cut() prn.cut()

@ -22,7 +22,7 @@ class Sales(Bureau):
pass # we've already got a db folder pass # we've already got a db folder
@add_command("p", "Play Media") @add_command("p", "Play Media")
def print_fortune(self, data): def play_media(self, data):
""" """
Shows media on a connected projector or plays audio. Shows media on a connected projector or plays audio.
""" """

@ -67,6 +67,7 @@ class TypingPool(Bureau):
def run_io(self): def run_io(self):
val = "" val = ""
upper = False upper = False
ctrl = False
#TODO: this is crap, needs to be multi-threaded and have one #TODO: this is crap, needs to be multi-threaded and have one
# such loop for each active device # such loop for each active device
self.active_devices[0].grab() self.active_devices[0].grab()
@ -74,7 +75,7 @@ class TypingPool(Bureau):
for ev in self.active_devices[0].read_loop(): for ev in self.active_devices[0].read_loop():
if ev.type == evdev.ecodes.EV_KEY: if ev.type == evdev.ecodes.EV_KEY:
data = evdev.categorize(ev) data = evdev.categorize(ev)
if data.keystate == 1: if data.keystate == 1: # key-down
if data.scancode == 28: if data.scancode == 28:
print("sending barcode:", val) print("sending barcode:", val)
self.send(val[0:2], val[2:]) self.send(val[0:2], val[2:])
@ -82,15 +83,27 @@ class TypingPool(Bureau):
else: else:
try: try:
new_key = KEYS[data.scancode] new_key = KEYS[data.scancode]
if new_key == "LSHFT" or new_key == "RSHFT": if ctrl and (new_key == "j"):
self.log.debug("ignoring line-feed")
elif new_key == "LSHFT" or new_key == "RSHFT":
upper = True upper = True
elif new_key == "LCTRL" or new_key == "RCTRL":
ctrl = True
else: else:
if upper: if upper:
new_key = new_key.upper() new_key = new_key.upper()
upper = False
val += new_key val += new_key
except KeyError: except KeyError:
print("Error invalid keycode:", data.scancode) print("Error invalid keycode:", data.scancode)
if data.keystate == 0: # key-up for mod-keys
try:
new_key = KEYS[data.scancode]
if new_key == "LSHFT" or new_key == "RSHFT":
upper = False
if new_key == "LCTRL" or new_key == "RCTRL":
ctrl = False
except KeyError:
print("Error invalid keycode:", data.scancode)
def main(): def main():

Loading…
Cancel
Save