|
|
# bureau
|
|
|
import functools
|
|
|
import json
|
|
|
import os.path
|
|
|
import subprocess
|
|
|
import tempfile
|
|
|
import threading
|
|
|
|
|
|
import zmq
|
|
|
from mako.template import Template
|
|
|
|
|
|
|
|
|
def update_commands(cls):
|
|
|
for name, method in cls.__dict__.items():
|
|
|
print("name %s method %s" % (name, method))
|
|
|
if hasattr(method, "command"):
|
|
|
comstr = method.command
|
|
|
cls.commands[comstr] = method
|
|
|
return cls
|
|
|
|
|
|
|
|
|
def add_command(comstr, name=""):
|
|
|
def decorator(func):
|
|
|
@functools.wraps(func)
|
|
|
def func_wrap(*args, **kwargs):
|
|
|
return func(*args, **kwargs)
|
|
|
func_wrap.command = comstr
|
|
|
func_wrap.name = name
|
|
|
return func_wrap
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
def add_api(apistr, name=""):
|
|
|
def decorator(func):
|
|
|
@functools.wraps(func)
|
|
|
def func_wrap(*args, **kwargs):
|
|
|
return func(*args, **kwargs)
|
|
|
func_wrap.api = apistr
|
|
|
func_wrap.name = name
|
|
|
return func_wrap
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
# @update_commands
|
|
|
class Bureau:
|
|
|
name = "TEST"
|
|
|
prefix = "00"
|
|
|
version = 0
|
|
|
|
|
|
config = {}
|
|
|
|
|
|
def __init__(self):
|
|
|
""" set up ZeroMQ connections and register commands"""
|
|
|
self.commands = {}
|
|
|
self.api = {}
|
|
|
self.context = zmq.Context()
|
|
|
self._recv = self.context.socket(zmq.SUB)
|
|
|
self._recv.connect("tcp://localhost:10101")
|
|
|
self._recv.setsockopt_string(zmq.SUBSCRIBE, self.prefix)
|
|
|
print(("bureau " + self.name + " waiting for messages"))
|
|
|
|
|
|
self._send = self.context.socket(zmq.PUB)
|
|
|
self._send.connect("tcp://localhost:10100")
|
|
|
|
|
|
# update_commands(self)
|
|
|
# self.registerCommands()
|
|
|
|
|
|
print("commands: ")
|
|
|
print(self.commands)
|
|
|
|
|
|
def send(self, message, data=None):
|
|
|
if data:
|
|
|
message += json.dumps(data)
|
|
|
self._send.send_string(message)
|
|
|
|
|
|
def _publish_methods(self):
|
|
|
# register bureau with Inhuman Resources
|
|
|
bureau_json = json.dumps({"name": self.name, "prefix": self.prefix,
|
|
|
"desc": self.__doc__})
|
|
|
self.send("IRaddbureau." + bureau_json)
|
|
|
|
|
|
# find and store all published methods
|
|
|
for member in dir(self):
|
|
|
method = getattr(self, member)
|
|
|
# ignore anything that is not a method with command or api details
|
|
|
if not (callable(method) and (hasattr(method, "command") or
|
|
|
hasattr(method, "api"))):
|
|
|
continue
|
|
|
if hasattr(method, "command"):
|
|
|
self.commands[method.command] = method
|
|
|
cmd_json = json.dumps({"cmdname": method.name,
|
|
|
"prefix": self.prefix,
|
|
|
"cmd": method.command,
|
|
|
"desc": method.__doc__})
|
|
|
self.send("IRaddcommand." + cmd_json)
|
|
|
elif hasattr(method, "api"):
|
|
|
self.api[method.api] = method
|
|
|
cmd_json = json.dumps({"apiname": method.name,
|
|
|
"prefix": self.prefix,
|
|
|
"api": method.api,
|
|
|
"desc": method.__doc__})
|
|
|
self.send("IRaddapi." + cmd_json)
|
|
|
|
|
|
print("registered:")
|
|
|
print(self.commands)
|
|
|
print(self.api)
|
|
|
|
|
|
def print_full(self, template, **kwargs):
|
|
|
"""print a full page (A4) document """
|
|
|
# TODO: look up the printer LPR name
|
|
|
|
|
|
lpname = kwargs.get("printer", "default")
|
|
|
templ = Template(filename=template)
|
|
|
|
|
|
texfile, texfilepath = tempfile.mkstemp(".tex")
|
|
|
texfile.write(templ.render_unicode(
|
|
|
**kwargs).encode('utf-8', 'replace'))
|
|
|
|
|
|
texdir = os.path.dirname(texfilepath)
|
|
|
|
|
|
subprocess.call("cd " + texdir + "; xelatex " + texfilepath)
|
|
|
|
|
|
pdffile = texfilepath[0:-4] + ".pdf"
|
|
|
|
|
|
subprocess.call("lpr -P " + lpname + " " + pdffile)
|
|
|
|
|
|
def print_small(self, text, printer="/dev/usb/lp1"):
|
|
|
lp = open(printer, "w")
|
|
|
text += "\r\n" * 10
|
|
|
text += ".d0"
|
|
|
lp.write(text)
|
|
|
lp.close()
|
|
|
|
|
|
@add_command("test")
|
|
|
def test(self):
|
|
|
# stupid test to see if modules work
|
|
|
print(("hi! testing. " + self.name + " bureau seems to work!"))
|
|
|
|
|
|
def run_io(self):
|
|
|
"""process hardware or timed input
|
|
|
|
|
|
This method can be ignored for most Bureaus.
|
|
|
It should be overloaded for any services that need to independently
|
|
|
generate messages as this is just a placeholder.
|
|
|
It is run in a separate thread and should handle any input or
|
|
|
timed events. Messages are then sent to other Bureaus via the
|
|
|
self.send connection to the OfficeManager. Don't forget to
|
|
|
to consier thread safety issues when accessing data!
|
|
|
"""
|
|
|
pass
|
|
|
|
|
|
def run(self):
|
|
|
"""main loop for processing messages
|
|
|
|
|
|
This runs all relelvant event processing loops.
|
|
|
"""
|
|
|
|
|
|
# start the hardware input handler
|
|
|
io = threading.Thread(target=self.run_io)
|
|
|
io.start()
|
|
|
|
|
|
# register commands and api methods
|
|
|
self._publish_methods()
|
|
|
|
|
|
while True:
|
|
|
msg = self._recv.recv_string()
|
|
|
try:
|
|
|
dot = msg.find(".")
|
|
|
ref = msg[2:dot]
|
|
|
if (dot < len(msg) - 1) and (dot > 0):
|
|
|
data = json.loads(msg[dot + 1:])
|
|
|
else:
|
|
|
data = None
|
|
|
print("data: " + str(data))
|
|
|
except IndexError as e:
|
|
|
print("invalid message: ", e)
|
|
|
continue
|
|
|
print(("got method: " + ref))
|
|
|
|
|
|
if (ref in self.commands) or (ref in self.api):
|
|
|
# catch TypeErrors for case of bogus params
|
|
|
try:
|
|
|
if data:
|
|
|
self.api[ref](data)
|
|
|
else:
|
|
|
self.commands[ref]()
|
|
|
except TypeError as e:
|
|
|
print(e)
|
|
|
print("invalid data for command '{}': {}".format(ref, data))
|
|
|
except KeyError as e:
|
|
|
print(e)
|
|
|
print("You are calling a command as an API or vice-versa.")
|
|
|
else:
|
|
|
print("error! Command/API %s not found", ref)
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
test = Bureau("test", "00")
|
|
|
test.run()
|