|
|
|
@ -1,4 +1,5 @@
|
|
|
|
|
# bureau
|
|
|
|
|
import configparser
|
|
|
|
|
import functools
|
|
|
|
|
import json
|
|
|
|
|
import os.path
|
|
|
|
@ -12,6 +13,7 @@ from mako.template import Template
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_commands(cls):
|
|
|
|
|
""" this is some internal magic to keep track of our commands """
|
|
|
|
|
for name, method in cls.__dict__.items():
|
|
|
|
|
print("name %s method %s" % (name, method))
|
|
|
|
|
if hasattr(method, "command"):
|
|
|
|
@ -21,9 +23,12 @@ def update_commands(cls):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_command(comstr, name=""):
|
|
|
|
|
""" decorator for making a method into a command """
|
|
|
|
|
def decorator(func):
|
|
|
|
|
""" the decorator itself """
|
|
|
|
|
@functools.wraps(func)
|
|
|
|
|
def func_wrap(*args, **kwargs):
|
|
|
|
|
""" this is to avoid roaching the namespace """
|
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
|
func_wrap.command = comstr
|
|
|
|
|
func_wrap.name = name
|
|
|
|
@ -32,9 +37,12 @@ def add_command(comstr, name=""):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_api(apistr, name=""):
|
|
|
|
|
""" decorator for making a method into a public bureau api method"""
|
|
|
|
|
def decorator(func):
|
|
|
|
|
""" the decorator itself """
|
|
|
|
|
@functools.wraps(func)
|
|
|
|
|
def func_wrap(*args, **kwargs):
|
|
|
|
|
""" this is to avoid roaching the namespace """
|
|
|
|
|
return func(*args, **kwargs)
|
|
|
|
|
func_wrap.api = apistr
|
|
|
|
|
func_wrap.name = name
|
|
|
|
@ -42,45 +50,79 @@ def add_api(apistr, name=""):
|
|
|
|
|
return decorator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Bureau:
|
|
|
|
|
class Bureau(object):
|
|
|
|
|
""" Bureau is a base class that implements standard methods for
|
|
|
|
|
inter-bureau communication, IO, registration and some convenient stuff
|
|
|
|
|
for printing. """
|
|
|
|
|
name = "TEST"
|
|
|
|
|
prefix = "00"
|
|
|
|
|
version = 0
|
|
|
|
|
|
|
|
|
|
config = {}
|
|
|
|
|
default_config = {"smallprinter": "/dev/usb/lp0"}
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
""" set up ZeroMQ connections and register commands"""
|
|
|
|
|
self.commands = {}
|
|
|
|
|
self.api = {}
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(".screenless"):
|
|
|
|
|
os.mkdir(".screenless")
|
|
|
|
|
basepath = os.path.expanduser("~/.screenless")
|
|
|
|
|
if not os.path.exists(basepath):
|
|
|
|
|
os.mkdir(basepath)
|
|
|
|
|
os.chdir(basepath)
|
|
|
|
|
|
|
|
|
|
self.config = configparser.ConfigParser()
|
|
|
|
|
self.load_config()
|
|
|
|
|
|
|
|
|
|
self.context = zmq.Context()
|
|
|
|
|
self._recv = self.context.socket(zmq.REP)
|
|
|
|
|
self._recv.bind("ipc://.screenless/" + self.prefix + ".ipc")
|
|
|
|
|
self._recv.bind("ipc://" + self.prefix + ".ipc")
|
|
|
|
|
print(("bureau " + self.name + " waiting for messages"))
|
|
|
|
|
print("commands: ")
|
|
|
|
|
print(self.commands)
|
|
|
|
|
|
|
|
|
|
def load_config(self):
|
|
|
|
|
"""
|
|
|
|
|
load (or reload) config data from file
|
|
|
|
|
"""
|
|
|
|
|
cfgfile = self.prefix + ".ini"
|
|
|
|
|
if os.path.exists(cfgfile):
|
|
|
|
|
self.config.read(cfgfile)
|
|
|
|
|
else:
|
|
|
|
|
self.config["DEFAULT"] = self.default_config
|
|
|
|
|
self.config["bureau"] = self.default_config
|
|
|
|
|
with open(cfgfile, "w") as configfile:
|
|
|
|
|
self.config.write(configfile)
|
|
|
|
|
|
|
|
|
|
def send(self, recipient, message, data=None):
|
|
|
|
|
"""
|
|
|
|
|
send commands or API calls to another bureau.
|
|
|
|
|
recipient: the 2-character bureau ID
|
|
|
|
|
message: a text based message as used in many commands
|
|
|
|
|
data: an optional dict, used in API calls
|
|
|
|
|
|
|
|
|
|
returns either empty string, text or a json object
|
|
|
|
|
"""
|
|
|
|
|
message += "."
|
|
|
|
|
if data:
|
|
|
|
|
message += json.dumps(data)
|
|
|
|
|
sender = self.context.socket(zmq.REQ)
|
|
|
|
|
sender.connect("ipc://.screenless/" + recipient + ".ipc")
|
|
|
|
|
sender.connect("ipc://" + recipient + ".ipc")
|
|
|
|
|
sender.send_string(message)
|
|
|
|
|
# TODO: retry this a few times with a proper sleep/timeout
|
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
try:
|
|
|
|
|
ret = json.loads(sender.recv(flags=zmq.NOBLOCK))
|
|
|
|
|
# TODO: deal with non-json replies
|
|
|
|
|
except zmq.ZMQError:
|
|
|
|
|
print("message sent but got no reply...")
|
|
|
|
|
ret = None
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
def _publish_methods(self):
|
|
|
|
|
"""
|
|
|
|
|
this internal method registers all public commands and bureau API
|
|
|
|
|
methods. Inhuman Resources module can then display menus and docs.
|
|
|
|
|
"""
|
|
|
|
|
# register bureau with Inhuman Resources
|
|
|
|
|
bureau_detail = {"name": self.name, "prefix": self.prefix,
|
|
|
|
|
"desc": self.__doc__}
|
|
|
|
@ -132,6 +174,9 @@ class Bureau:
|
|
|
|
|
subprocess.call("lpr -P " + lpname + " " + pdffile)
|
|
|
|
|
|
|
|
|
|
def print_small(self, text, printer="/dev/usb/lp1"):
|
|
|
|
|
"""
|
|
|
|
|
print on Thermal Line printer.
|
|
|
|
|
"""
|
|
|
|
|
lp = open(printer, "w")
|
|
|
|
|
text += "\r\n" * 10
|
|
|
|
|
text += ".d0"
|
|
|
|
@ -140,6 +185,9 @@ class Bureau:
|
|
|
|
|
|
|
|
|
|
@add_command("test")
|
|
|
|
|
def test(self, data):
|
|
|
|
|
"""
|
|
|
|
|
Standard test command.
|
|
|
|
|
"""
|
|
|
|
|
# stupid test to see if modules work
|
|
|
|
|
print(("hi! testing. " + self.name + " bureau seems to work!"))
|
|
|
|
|
return b"seems to work."
|
|
|
|
@ -158,14 +206,15 @@ class Bureau:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
"""main loop for processing messages
|
|
|
|
|
"""
|
|
|
|
|
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()
|
|
|
|
|
io_handler = threading.Thread(target=self.run_io)
|
|
|
|
|
io_handler.start()
|
|
|
|
|
|
|
|
|
|
# register commands and api methods
|
|
|
|
|
self._publish_methods()
|
|
|
|
@ -184,8 +233,8 @@ class Bureau:
|
|
|
|
|
else:
|
|
|
|
|
data = None
|
|
|
|
|
print("data: " + str(data))
|
|
|
|
|
except IndexError as e:
|
|
|
|
|
print("invalid message: ", e)
|
|
|
|
|
except IndexError as err:
|
|
|
|
|
print("invalid message: ", err)
|
|
|
|
|
continue
|
|
|
|
|
print(("got method: " + ref))
|
|
|
|
|
|
|
|
|
@ -201,12 +250,12 @@ class Bureau:
|
|
|
|
|
ret = ""
|
|
|
|
|
ret = b"0" + ret
|
|
|
|
|
self._recv.send(ret)
|
|
|
|
|
except TypeError as e:
|
|
|
|
|
print(e)
|
|
|
|
|
except TypeError as err:
|
|
|
|
|
print(err)
|
|
|
|
|
print("invalid data for command '{}': {}".format(ref, data))
|
|
|
|
|
self._recv.send_unicode("Error. Invalid or missing data.")
|
|
|
|
|
except KeyError as e:
|
|
|
|
|
print(e)
|
|
|
|
|
except KeyError as err:
|
|
|
|
|
print(err)
|
|
|
|
|
print("You are calling a command as an API or vice-versa.")
|
|
|
|
|
self._recv.send_unicode(
|
|
|
|
|
"Error. Command called as API or API as command.")
|
|
|
|
@ -216,5 +265,5 @@ class Bureau:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
test = Bureau()
|
|
|
|
|
test.run()
|
|
|
|
|
buro = Bureau()
|
|
|
|
|
buro.run()
|
|
|
|
|