STATIC := $(patsubst src/resources/%,$(TARGET)/%,$(STATIC))
TEMPLS := $(wildcard src/jade/templates/*.jade)
+RSYNC_EXCLUDE := \*.pyc __pycache__ \*.egg-info \\\#* \*~ .\\\#\*
+RSYNC_EXCLUDE := $(patsubst %,--exclude %,$(RSYNC_EXCLUDE))
+RSYNC_OPTS := $(RSYNC_EXCLUDE) -rLv --no-g
+
ifndef DEST
-DEST=mnt/
+DEST=mnt
endif
WATCH := src/jade src/jade/templates src/stylus src/js src/resources Makefile
copy: all
mkdir -p $(DEST)/bbctrl/src/py $(DEST)/bbctrl/build
- rsync -rLv --no-g --exclude \*.pyc --exclude __pycache__ \
- --exclude \*.egg-info src/py $(DEST)/bbctrl/src/
- rsync -av --no-g build/http $(DEST)/bbctrl/build
- rsync -av --no-g setup.py README.md $(DEST)/bbctrl
+ rsync $(RSYNC_OPTS) src/py $(DEST)/bbctrl/src/
+ rsync $(RSYNC_OPTS) setup.py README.md $(DEST)/bbctrl
mount:
mkdir -p $(DEST)
# Description: Buildbotics Controller Web service
### END INIT INFO
-DAEMON=/usr/local/bin/bbctrl
+
DAEMON_NAME=bbctrl
+DAEMON=/usr/local/bin/$DAEMON_NAME
DAEMON_OPTS=""
DAEMON_USER=root
DAEMON_DIR=/var/lib/$DAEMON_NAME
PIDFILE=/var/run/$DAEMON_NAME.pid
+DEFAULTS=/etc/default/$DAEMON_NAME
+
. /lib/lsb/init-functions
+if [ -e $DEFAULTS ]; then
+ . $DEFAULTS
+fi
+
+
do_start () {
log_daemon_msg "Starting system $DAEMON_NAME daemon"
mkdir -p $DAEMON_DIR &&
start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile \
--user $DAEMON_USER --chuid $DAEMON_USER --chdir $DAEMON_DIR \
- --startas /bin/bash -- \
- -c "exec $DAEMON $DAEMON_OPTS > /var/log/$DAEMON_NAME.log 2>&1"
+ --startas $DAEMON -- $DAEMON_OPTS -l /var/log/$DAEMON_NAME.log
log_end_msg $?
}
--- /dev/null
+import serial
+import logging
+
+
+log = logging.getLogger('AVR')
+
+
+class AVR():
+ def __init__(self, port, baud, ioloop, app):
+ self.app = app
+
+ try:
+ self.sp = serial.Serial(port, baud, timeout = 1)
+
+ except Exception as e:
+ log.warning('Failed to open serial port: %s', e)
+ return
+
+ self.in_buf = ''
+ self.app.input_queue.put('\n')
+
+ ioloop.add_handler(self.sp, self.serial_handler, ioloop.READ)
+ ioloop.add_handler(self.app.input_queue._reader.fileno(),
+ self.queue_handler, ioloop.READ)
+
+
+ def close(self):
+ self.sp.close()
+
+
+ def serial_handler(self, fd, events):
+ try:
+ data = self.sp.read(self.sp.inWaiting())
+ self.in_buf += data.decode('utf-8')
+
+ except Exception as e:
+ log.warning('%s: %s', e, data)
+
+ while True:
+ i = self.in_buf.find('\n')
+ if i == -1: break
+ line = self.in_buf[0:i].strip()
+ self.in_buf = self.in_buf[i + 1:]
+
+ if line:
+ self.app.output_queue.put(line)
+ log.debug(line)
+
+
+ def queue_handler(self, fd, events):
+ if self.app.input_queue.empty(): return
+
+ data = self.app.input_queue.get()
+ self.sp.write(data.encode())
--- /dev/null
+import inevent
+from inevent.Constants import *
+
+
+# Listen for input events
+class Jog(inevent.JogHandler):
+ def __init__(self, args, ioloop):
+ config = {
+ "deadband": 0.1,
+ "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
+ "arrows": [ABS_HAT0X, ABS_HAT0Y],
+ "speed": [0x120, 0x121, 0x122, 0x123],
+ "activate": [0x124, 0x126, 0x125, 0x127],
+ }
+
+ super().__init__(config)
+
+ self.v = [0.0] * 4
+ self.lastV = self.v
+
+ self.processor = inevent.InEvent(ioloop, self, types = "js kbd".split())
+
+
+ def processed_events(self):
+ if self.v != self.lastV:
+ self.lastV = self.v
+
+ v = ["{:6.5f}".format(x) for x in self.v]
+ cmd = '$jog ' + ' '.join(v) + '\n'
+ input_queue.put(cmd)
+
+
+ def changed(self):
+ if self.speed == 1: scale = 1.0 / 128.0
+ if self.speed == 2: scale = 1.0 / 32.0
+ if self.speed == 3: scale = 1.0 / 4.0
+ if self.speed == 4: scale = 1.0
+
+ self.v = [x * scale for x in self.axes]
--- /dev/null
+import lcd
+import atexit
+
+
+class LCD:
+ def __init__(self, port, addr):
+ self.lcd = lcd.LCD(port, addr)
+ self.splash()
+ atexit.register(self.goodbye)
+
+
+ def splash(self):
+ self.lcd.clear()
+ self.lcd.display(0, 'Buildbotics', lcd.JUSTIFY_CENTER)
+ self.lcd.display(1, 'Controller', lcd.JUSTIFY_CENTER)
+ self.lcd.display(3, '*Ready*', lcd.JUSTIFY_CENTER)
+
+
+ def goodbye(self):
+ self.lcd.clear()
+ self.lcd.display(1, 'Goodbye', lcd.JUSTIFY_CENTER)
--- /dev/null
+import os
+import sys
+import json
+import multiprocessing
+import tornado
+import sockjs.tornado
+import logging
+
+import bbctrl
+
+
+log = logging.getLogger('Web')
+
+
+class APIHandler(tornado.web.RequestHandler):
+ def prepare(self):
+ self.json = {}
+
+ if self.request.body:
+ try:
+ self.json = tornado.escape.json_decode(self.request.body)
+ except ValueError:
+ self.send_error(400, message = 'Unable to parse JSON.')
+
+
+ def set_default_headers(self):
+ self.set_header('Content-Type', 'application/json')
+
+
+ def write_error(self, status_code, **kwargs):
+ e = {}
+ e['message'] = str(kwargs['exc_info'][1])
+ e['code'] = status_code
+
+ self.write_json(e)
+
+
+ def write_json(self, data):
+ self.write(json.dumps(data))
+
+
+
+class LoadHandler(APIHandler):
+ def send_file(self, path):
+ with open(path, 'r') as f:
+ self.write_json(json.load(f))
+
+
+ def get(self):
+ try:
+ self.send_file('config.json')
+ except Exception as e:
+ log.warning('%s', e)
+ self.send_file(bbctrl.get_resource('default-config.json'))
+
+
+
+class SaveHandler(APIHandler):
+ def post(self):
+ with open('config.json', 'w') as f:
+ json.dump(self.json, f)
+
+ self.application.update_config(self.json)
+ log.info('Saved config')
+ self.write_json('ok')
+
+
+
+class FileHandler(APIHandler):
+ def prepare(self): pass
+
+
+ def delete(self, path):
+ path = 'upload' + path
+ if os.path.exists(path): os.unlink(path)
+ self.write_json('ok')
+
+
+ def put(self, path):
+ path = 'upload' + path
+ if not os.path.exists(path): return
+
+ with open(path, 'r') as f:
+ for line in f:
+ self.application.input_queue.put(line)
+
+
+ def get(self, path):
+ if path:
+ with open('upload/' + path, 'r') as f:
+ self.write_json(f.read())
+ return
+
+ files = []
+
+ if os.path.exists('upload'):
+ for path in os.listdir('upload'):
+ if os.path.isfile('upload/' + path):
+ files.append(path)
+
+ self.write_json(files)
+
+
+ def post(self, path):
+ gcode = self.request.files['gcode'][0]
+
+ if not os.path.exists('upload'): os.mkdir('upload')
+
+ with open('upload/' + gcode['filename'], 'wb') as f:
+ f.write(gcode['body'])
+
+ self.write_json('ok')
+
+
+
+class Connection(sockjs.tornado.SockJSConnection):
+ def on_open(self, info):
+ self.session.server.app.clients.append(self)
+ self.send(str.encode(json.dumps(self.session.server.app.state)))
+
+
+ def on_close(self):
+ self.session.server.app.clients.remove(self)
+
+
+ def on_message(self, data):
+ self.session.server.app.input_queue.put(data + '\n')
+
+
+
+class Web(tornado.web.Application):
+ def __init__(self, addr, port, ioloop):
+ # Load config template
+ with open(bbctrl.get_resource('http/config-template.json'), 'r',
+ encoding = 'utf-8') as f:
+ self.config_template = json.load(f)
+
+
+ self.state = {}
+ self.clients = []
+
+ self.input_queue = multiprocessing.Queue()
+ self.output_queue = multiprocessing.Queue()
+
+ # Handle output queue events
+ ioloop.add_handler(self.output_queue._reader.fileno(),
+ self.queue_handler, ioloop.READ)
+
+ handlers = [
+ (r'/api/load', LoadHandler),
+ (r'/api/save', SaveHandler),
+ (r'/api/file(/.*)?', FileHandler),
+ (r'/(.*)', tornado.web.StaticFileHandler,
+ {'path': bbctrl.get_resource('http/'),
+ "default_filename": "index.html"}),
+ ]
+
+ router = sockjs.tornado.SockJSRouter(Connection, '/ws')
+ router.app = self
+
+ tornado.web.Application.__init__(self, router.urls + handlers)
+
+ try:
+ self.listen(port, address = addr)
+
+ except Exception as e:
+ log.error('Failed to bind %s:%d: %s', addr, port, e)
+ sys.exit(1)
+
+ log.info('Listening on http://%s:%d/', addr, port)
+
+
+ def queue_handler(self, fd, events):
+ try:
+ data = self.output_queue.get()
+ msg = json.loads(data)
+ self.state.update(msg)
+ if self.clients:
+ self.clients[0].broadcast(self.clients, msg)
+
+ except Exception as e:
+ log.error('%s, data: %s', e, data)
+
+
+ def encode_cmd(self, index, value, spec):
+ if spec['type'] == 'enum': value = spec['values'].index(value)
+ elif spec['type'] == 'bool': value = 1 if value else 0
+ elif spec['type'] == 'percent': value /= 100.0
+
+ cmd = '${}{}={}'.format(index, spec['code'], value)
+ self.input_queue.put(cmd + '\n')
+ #log.info(cmd)
+
+
+ def encode_config_category(self, index, config, category):
+ for key, spec in category.items():
+ if key in config:
+ self.encode_cmd(index, config[key], spec)
+
+
+ def encode_config(self, index, config, tmpl):
+ for category in tmpl.values():
+ self.encode_config_category(index, config, category)
+
+
+ def update_config(self, config):
+ # Motors
+ tmpl = self.config_template['motors']
+ for index in range(len(config['motors'])):
+ self.encode_config(index + 1, config['motors'][index], tmpl)
+
+ # Axes
+ tmpl = self.config_template['axes']
+ axes = 'xyzabc'
+ for axis in axes:
+ if not axis in config['axes']: continue
+ self.encode_config(axis, config['axes'][axis], tmpl)
+
+ # Switches
+ tmpl = self.config_template['switches']
+ for index in range(len(config['switches'])):
+ self.encode_config_category(index + 1,
+ config['switches'][index], tmpl)
+
+ # Spindle
+ tmpl = self.config_template['spindle']
+ self.encode_config_category('', config['spindle'], tmpl)
import os
import sys
import signal
-from tornado import web, ioloop, escape
-from sockjs.tornado import SockJSRouter, SockJSConnection
-import json
-import serial
-import multiprocessing
-import time
-import select
-import atexit
+import tornado
import argparse
+import logging
from pkg_resources import Requirement, resource_filename
-import lcd
-import inevent
-from inevent.Constants import *
-
-
-DIR = os.path.dirname(__file__)
-
-config = {
- "deadband": 0.1,
- "axes": [ABS_X, ABS_Y, ABS_RZ, ABS_Z],
- "arrows": [ABS_HAT0X, ABS_HAT0Y],
- "speed": [0x120, 0x121, 0x122, 0x123],
- "activate": [0x124, 0x126, 0x125, 0x127],
- "verbose": False
- }
-
-state = {}
-clients = []
-
-input_queue = multiprocessing.Queue()
-output_queue = multiprocessing.Queue()
+from bbctrl.LCD import LCD
+from bbctrl.AVR import AVR
+from bbctrl.Web import Web
+from bbctrl.Jog import Jog
def get_resource(path):
return resource_filename(Requirement.parse('bbctrl'), 'bbctrl/' + path)
-def on_exit(sig, func = None):
- print('exit handler triggered')
+def on_exit(sig = 0, func = None):
+ logging.info('Exit handler triggered: signal = %d', sig)
sys.exit(1)
-def encode_cmd(index, value, spec):
- if spec['type'] == 'enum': value = spec['values'].index(value)
- elif spec['type'] == 'bool': value = 1 if value else 0
- elif spec['type'] == 'percent': value /= 100.0
-
- cmd = '${}{}={}'.format(index, spec['code'], value)
- input_queue.put(cmd + '\n')
- #print(cmd)
-
-
-def encode_config_category(index, config, category):
- for key, spec in category.items():
- if key in config:
- encode_cmd(index, config[key], spec)
-
-
-def encode_config(index, config, tmpl):
- for category in tmpl.values():
- encode_config_category(index, config, category)
-
-
-def update_config(config):
- # Motors
- tmpl = config_template['motors']
- for index in range(len(config['motors'])):
- encode_config(index + 1, config['motors'][index], tmpl)
-
- # Axes
- tmpl = config_template['axes']
- axes = 'xyzabc'
- for axis in axes:
- if not axis in config['axes']: continue
- encode_config(axis, config['axes'][axis], tmpl)
-
- # Switches
- tmpl = config_template['switches']
- for index in range(len(config['switches'])):
- encode_config_category(index + 1, config['switches'][index], tmpl)
-
- # Spindle
- tmpl = config_template['spindle']
- encode_config_category('', config['spindle'], tmpl)
-
-
-
-class APIHandler(web.RequestHandler):
- def prepare(self):
- self.json = {}
-
- if self.request.body:
- try:
- self.json = escape.json_decode(self.request.body)
- except ValueError:
- self.send_error(400, message = 'Unable to parse JSON.')
-
-
- def set_default_headers(self):
- self.set_header('Content-Type', 'application/json')
-
-
- def write_error(self, status_code, **kwargs):
- e = {}
- e['message'] = str(kwargs['exc_info'][1])
- e['code'] = status_code
-
- self.write_json(e)
-
-
- def write_json(self, data):
- self.write(json.dumps(data))
-
-
-class LoadHandler(APIHandler):
- def send_file(self, path):
- with open(path, 'r') as f:
- self.write_json(json.load(f))
-
- def get(self):
- try:
- self.send_file('config.json')
- except Exception as e:
- print(e)
- self.send_file(get_resource('http/default-config.json'))
-
-
-class SaveHandler(APIHandler):
- def post(self):
- with open('config.json', 'w') as f:
- json.dump(self.json, f)
-
- update_config(self.json)
- print('Saved config')
- self.write_json('ok')
-
-
-class FileHandler(APIHandler):
- def prepare(self): pass
-
-
- def delete(self, path):
- path = 'upload' + path
- if os.path.exists(path): os.unlink(path)
- self.write_json('ok')
-
-
- def put(self, path):
- path = 'upload' + path
- if not os.path.exists(path): return
-
- with open(path, 'r') as f:
- for line in f:
- input_queue.put(line)
-
-
- def get(self, path):
- if path:
- with open('upload/' + path, 'r') as f:
- self.write_json(f.read())
- return
-
- files = []
-
- if os.path.exists('upload'):
- for path in os.listdir('upload'):
- if os.path.isfile('upload/' + path):
- files.append(path)
-
- self.write_json(files)
-
-
- def post(self, path):
- gcode = self.request.files['gcode'][0]
-
- if not os.path.exists('upload'): os.mkdir('upload')
-
- with open('upload/' + gcode['filename'], 'wb') as f:
- f.write(gcode['body'])
-
- self.write_json('ok')
-
-
-class SerialProcess(multiprocessing.Process):
- def __init__(self, port, baud, input_queue, output_queue):
- multiprocessing.Process.__init__(self)
- self.input_queue = input_queue
- self.output_queue = output_queue
- self.sp = serial.Serial(port, baud, timeout = 1)
- self.input_queue.put('\n')
-
-
- def close(self):
- self.sp.close()
-
-
- def writeSerial(self, data):
- self.sp.write(data.encode())
-
-
- def readSerial(self):
- return self.sp.readline().replace(b"\n", b"")
-
-
- def run(self):
- self.sp.flushInput()
-
- while True:
- fds = [self.input_queue._reader.fileno(), self.sp]
- ready = select.select(fds, [], [], 0.25)[0]
-
- # look for incoming tornado request
- if not self.input_queue.empty():
- data = self.input_queue.get()
-
- # send it to the serial device
- self.writeSerial(data)
-
- # look for incoming serial data
- if self.sp.inWaiting() > 0:
- try:
- data = self.readSerial()
- data = data.decode('utf-8').strip()
- if not data: continue
-
- # send it back to tornado
- self.output_queue.put(data)
-
- print(data)
-
- except Exception as e:
- print(e, data)
-
-
-
-class Connection(SockJSConnection):
- def on_open(self, info):
- clients.append(self)
- self.send(str.encode(json.dumps(state)))
-
-
- def on_close(self):
- clients.remove(self)
-
-
- def on_message(self, data):
- input_queue.put(data + '\n')
-
-
-
-# check the queue for pending messages, and relay them to all connected clients
-def checkQueue():
- while not output_queue.empty():
- try:
- data = output_queue.get()
- msg = json.loads(data)
- state.update(msg)
- if clients: clients[0].broadcast(clients, msg)
-
- except Exception as e:
- print('ERROR: {}, data: {}'.format(e, data))
-
-
-handlers = [
- (r'/api/load', LoadHandler),
- (r'/api/save', SaveHandler),
- (r'/api/file(/.*)?', FileHandler),
- (r'/(.*)', web.StaticFileHandler,
- {'path': os.path.join(DIR, get_resource('http/')),
- "default_filename": "index.html"}),
- ]
-
-router = SockJSRouter(Connection, '/ws')
-
-
-# Listen for input events
-class JogHandler(inevent.JogHandler):
- def __init__(self, config):
- super().__init__(config)
-
- self.v = [0.0] * 4
- self.lastV = self.v
-
-
- def processed_events(self):
- if self.v != self.lastV:
- self.lastV = self.v
-
- v = ["{:6.5f}".format(x) for x in self.v]
- cmd = '$jog ' + ' '.join(v) + '\n'
- input_queue.put(cmd)
-
-
- def changed(self):
- if self.speed == 1: scale = 1.0 / 128.0
- if self.speed == 2: scale = 1.0 / 32.0
- if self.speed == 3: scale = 1.0 / 4.0
- if self.speed == 4: scale = 1.0
-
- self.v = [x * scale for x in self.axes]
-
-
-
-def checkEvents():
- eventProcessor.process_events(eventHandler)
- eventHandler.processed_events()
-
-eventProcessor = inevent.InEvent(types = "js kbd".split())
-eventHandler = JogHandler(config)
-
-
-def splash(screen):
- screen.clear()
- screen.display(0, 'Buildbotics', lcd.JUSTIFY_CENTER)
- screen.display(1, 'Controller', lcd.JUSTIFY_CENTER)
- screen.display(3, '*Ready*', lcd.JUSTIFY_CENTER)
-
-
-def goodbye(screen):
- screen.clear()
- screen.display(1, 'Goodbye', lcd.JUSTIFY_CENTER)
-
-
def parse_args():
parser = argparse.ArgumentParser(
description = 'Buildbotics Machine Controller')
help = 'LCD I2C address')
parser.add_argument('-v', '--verbose', action = 'store_true',
help = 'Verbose output')
+ parser.add_argument('-l', '--log', metavar = "FILE",
+ help = 'Set a log file')
return parser.parse_args()
def run():
- # Set signal handler
- signal.signal(signal.SIGTERM, on_exit)
-
- global args
args = parse_args()
- # Load config template
- global config_template
- with open(get_resource('http/config-template.json'), 'r',
- encoding = 'utf-8') as f:
- config_template = json.load(f)
-
# Init logging
- import logging
- logging.getLogger().setLevel(logging.DEBUG)
+ log = logging.getLogger()
+ log.setLevel(logging.DEBUG if args.verbose else logging.INFO)
+ if args.log: log.addHandler(logging.FileHandler(args.log, mode = 'w'))
- # Start the serial worker
- try:
- sp = SerialProcess(args.serial, args.baud, input_queue, output_queue)
- sp.daemon = True
- sp.start()
- except Exception as e:
- print('Failed to open serial port:', e)
-
- # Adjust the interval according to frames sent by serial port
- ioloop.PeriodicCallback(checkQueue, 100).start()
- ioloop.PeriodicCallback(checkEvents, 100).start()
+ # Set signal handler
+ signal.signal(signal.SIGTERM, on_exit)
- # Setup LCD
- global screen
- screen = lcd.LCD(args.lcd_port, args.lcd_addr)
- splash(screen)
- atexit.register(goodbye, screen)
+ # Create ioloop
+ ioloop = tornado.ioloop.IOLoop.current()
# Start the web server
- app = web.Application(router.urls + handlers)
+ app = Web(args.addr, args.port, ioloop)
+
+ # Start AVR driver
+ avr = AVR(args.serial, args.baud, ioloop, app)
+
+ # Start job input controler
+ jog = Jog(args, ioloop)
+
+ # Start LCD driver
+ lcd = LCD(args.lcd_port, args.lcd_addr)
try:
- app.listen(args.port, address = args.addr)
- except Exception as e:
- print('Failed to bind {}:{}:'.format(args.addr, args.port), e)
- sys.exit(1)
+ ioloop.start()
- print('Listening on http://{}:{}/'.format(args.addr, args.port))
+ except KeyboardInterrupt:
+ on_exit()
- ioloop.IOLoop.instance().start()
+ except: log.exception('')
if __name__ == "__main__": run()
--- /dev/null
+{
+ "motors": [
+ {
+ "motor-map": "x",
+ "step-angle": 1.8,
+ "travel-per-rev": 3.175,
+ "microsteps": 16,
+ "polarity": "normal",
+ "power-mode": "always-on",
+ "power-level": 80,
+ "stallguard": 70
+ }, {
+ "motor-map": "y"
+ }, {
+ "motor-map": "z"
+ }, {
+ "motor-map": "a"
+ }
+ ],
+
+ "axes": {
+ "x": {
+ "mode": "standard",
+ "max-velocity": 16000,
+ "max-feedrate": 16000,
+ "max-jerk": 40,
+ "min-soft-limit": 0,
+ "max-soft-limit": 150,
+ "max-homing-jerk": 80,
+ "junction-deviation": 0.05,
+ "search-velocity": 500,
+ "latch-velocity": 100,
+ "latch-backoff": 5,
+ "zero-backoff": 1
+ },
+
+ "y": {
+ "mode": "standard"
+ },
+
+ "z": {
+ "mode": "standard"
+ },
+
+ "a": {
+ "mode": "radius",
+ "max-velocity": 1000000,
+ "max-feedrate": 1000000,
+ "min-soft-limit": 0,
+ "max-soft-limit": 0
+ },
+
+ "b": {
+ "mode": "disabled"
+ },
+
+ "c": {
+ "mode": "disabled"
+ }
+ },
+
+ "switches": [
+ {"type": "normally-open"},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {},
+ {}
+ ],
+
+ "spindle": {
+ }
+}
# SOFTWARE.
import re
+import logging
from inevent.Constants import *
+log = logging.getLogger('inevent')
+
def test_bit(nlst, b):
index = b / 32
if test_bit(events, EV_FF): s.append("EV_FF" )
if test_bit(events, EV_PWR): s.append("EV_PWR")
if test_bit(events, EV_FF_STATUS): s.append("EV_FF_STATUS")
-
+
return s
self.EV_PWRevents = []
self.EV_FF_STATUSevents = []
self.eventTypes = []
-
+
match = re.search(".*Bus=([0-9A-Fa-f]+).*Vendor=([0-9A-Fa-f]+).*"
"Product=([0-9A-Fa-f]+).*Version=([0-9A-Fa-f]+).*",
firstLine)
if not match:
- print("Do not understand device ID:", line)
+ log.warning("Do not understand device ID:", line)
self.bus = 0
self.vendor = 0
self.product = 0
self.sysfs, self.uniq, self.handlers, self.eventIndex, self.isKeyboard,
self.isMouse, self.isJoystick, EvToStr(self.eventTypes)))
-
+
deviceCapabilities = []
for line in filehandle:
if line[0] == "I":
deviceCapabilities.append(DeviceCapabilities(line, filehandle))
-
+
return deviceCapabilities
import re
import select
import errno
+import functools
+import logging
from inevent.EventHandler import EventHandler
from inevent import Keys
from inevent.EventStream import EventStream
+log = logging.getLogger('inevent')
+
_KEYS = (k for k in vars(Keys) if not k.startswith('_'))
KEY_CODE = dict((k, getattr(Keys, k)) for k in _KEYS)
CODE_KEY = {}
The keys are listed in inevent.Constants.py or /usr/include/linux/input.h
Note that the key names refer to a US keyboard.
"""
- def __init__(self, types = ["kbd", "mouse", "js"]):
+ def __init__(self, ioloop, cb, types = ["kbd", "mouse", "js"]):
+ self.ioloop = ioloop
+ self.cb = cb
self.streams = []
self.handler = EventHandler()
self.types = types
devs = list(find_devices(types))
- for index, type in devs: self.add_stream(index, type)
+ for index, type in devs:
+ self.add_stream(index, type)
self.udevCtx = pyudev.Context()
self.udevMon = pyudev.Monitor.from_netlink(self.udevCtx)
self.udevMon.filter_by(subsystem = 'input')
self.udevMon.start()
+ ioloop.add_handler(self.udevMon.fileno(), self.udev_handler, ioloop.READ)
def process_udev_event(self):
self.remove_stream(devIndex)
- def process_events(self, cb = None):
- """
- Handle all events that have been triggered since the last call.
- """
-
- # Gather list of file descriptors to watch
- selectlist = [x.filehandle for x in self.streams]
- udevFD = self.udevMon.fileno()
- selectlist.append(udevFD)
-
- processedEvent = True
- while processedEvent:
- processedEvent = False
-
- # Select
- ready = select.select(selectlist, [], [], 0)[0]
+ def stream_handler(self, fd, events):
+ for stream in self.streams:
+ if stream.filehandle == fd:
+ while True:
+ event = stream.read()
+ if event: self.handler.event(event, self.cb)
+ else: break
- # Handle events
- for fd in ready:
- if fd == udevFD:
- self.process_udev_event()
- processedEvent = True
- for stream in self.streams:
- if stream.filehandle == fd:
- event = stream.read()
- if event:
- self.handler.event(event, cb)
- processedEvent = True
+ def udev_handler(self, fd, events):
+ self.process_udev_event()
def add_stream(self, devIndex, devType):
try:
- self.streams.append(EventStream(devIndex, devType))
- print('Added {}[{:d}]'.format(devType, devIndex))
+ stream = EventStream(devIndex, devType)
+ self.streams.append(stream)
+
+ self.ioloop.add_handler(stream.filehandle, self.stream_handler,
+ self.ioloop.READ)
+
+ log.info('Added %s[%d]', devType, devIndex)
except OSError as e:
- if not e.errno in [errno.EPERM, errno.EACCES]: raise e
+ if e.errno in [errno.EPERM, errno.EACCES]:
+ log.warning('Failed to add %s[%d]: %s', devType, devIndex, e)
+ else: raise e
def remove_stream(self, devIndex):
for stream in self.streams:
if stream.devIndex == devIndex:
self.streams.remove(stream)
- print('Removed {}[{:d}]'.format(stream.devType, devIndex))
- break
+ self.ioloop.remove_handler(stream.filehandle)
+
+ log.info('Removed %s[%d]', stream.devType, devIndex)
def key_state(self, key):
Only do this when you're finished with this object. You can't use it again.
"""
for s in self.streams: s.release()
-
+import logging
+
from inevent.Constants import *
+log = logging.getLogger('inevent')
+
+
def axes_to_string(axes):
- return "({:6.3f}, {:6.3f}, {:6.3f}, {:6.3f})".format(*axes)
+ return '({:6.3f}, {:6.3f}, {:6.3f})'.format(*axes)
-def print_event(event, state):
- print("{} {}: ".format(event.get_source(), event.get_type_name()), end = '')
+def event_to_string(event, state):
+ s = '{} {}: '.format(event.get_source(), event.get_type_name())
if event.type == EV_ABS:
- print(axes_to_string(state.get_joystick3d()) + " " +
- axes_to_string(state.get_joystickR3d()) + " " +
- "({:2.0f}, {:2.0f}) ".format(*state.get_hat()))
+ s += axes_to_string(state.get_joystick3d()) + ' ' + \
+ axes_to_string(state.get_joystickR3d()) + ' ' + \
+ '({:2.0f}, {:2.0f}) '.format(*state.get_hat())
if event.type == EV_REL:
- print("({:d}, {:d}) ".format(*state.get_mouse()) +
- "({:d}, {:d})".format(*state.get_wheel()))
+ s += '({:d}, {:d}) '.format(*state.get_mouse()) + \
+ '({:d}, {:d})'.format(*state.get_wheel())
if event.type == EV_KEY:
- state = "pressed" if event.value else "released"
- print("0x{:x} {}".format(event.code, state))
+ state = 'pressed' if event.value else 'released'
+ s += '0x{:x} {}'.format(event.code, state)
+
+ return s
class JogHandler:
def changed(self):
- print(axes_to_string(self.axes) + " x {:d}".format(self.speed))
+ log.debug(axes_to_string(self.axes) + ' x {:d}'.format(self.speed))
def __call__(self, event, state):
axis = self.config['arrows'].index(event.code)
if event.value < 0:
- if axis == 1: print('up')
- else: print('left')
+ if axis == 1: log.debug('up')
+ else: log.debug('left')
elif 0 < event.value:
- if axis == 1: print('down')
- else: print('right')
+ if axis == 1: log.debug('down')
+ else: log.debug('right')
elif event.type == EV_KEY and event.code in self.config['speed']:
old_speed = self.speed
if event.value: self.activate |= 1 << index
else: self.activate &= ~(1 << index)
- if self.config.get('verbose', False): print_event(event, state)
+ log.debug(event_to_string(event, state))
# Update axes
old_axes = list(self.axes)
import smbus2 as smbus
import time
+import logging
+
+
+log = logging.getLogger('LCD')
# Control flags
self.bus = smbus.SMBus(port)
except FileNotFoundError as e:
self.bus = None
- print('Failed to open LCD device:', e)
+ log.warning('Failed to open device: %s', e)
self.backlight = True