.sconf_temp/
.sconsign.dblite
-/http
/build
node_modules
*~
\#*
*.pyc
__pycache__
+*.egg-info
+/upload
AP := $(NODE_MODS)/autoprefixer/autoprefixer
BROWSERIFY := $(NODE_MODS)/browserify/bin/cmd.js
+TARGET := build/http
HTML := index
-HTML := $(patsubst %,http/%.html,$(HTML))
+HTML := $(patsubst %,$(TARGET)/%.html,$(HTML))
CSS := $(wildcard src/stylus/*.styl)
CSS_ASSETS := build/css/style.css
JS := $(wildcard src/js/*.js)
-JS_ASSETS := http/js/assets.js
+JS_ASSETS := $(TARGET)/js/assets.js
STATIC := $(shell find src/resources -type f)
-STATIC := $(patsubst src/resources/%,http/%,$(STATIC))
+STATIC := $(patsubst src/resources/%,$(TARGET)/%,$(STATIC))
TEMPLS := $(wildcard src/jade/templates/*.jade)
ifndef DEST
-DEST=bbctrl/
+DEST=mnt/
endif
WATCH := src/jade src/jade/templates src/stylus src/js src/resources Makefile
all: html css js static
copy: all
- cp -r *.py inevent http/ $(DEST)
+ cp -r *.py inevent $(TARGET)/ $(DEST)
mount:
mkdir -p $(DEST)
html: templates $(HTML)
css: $(CSS_ASSETS) $(CSS_ASSETS).sha256
- install -D $< http/css/style-$(shell cat $(CSS_ASSETS).sha256).css
+ install -D $< $(TARGET)/css/style-$(shell cat $(CSS_ASSETS).sha256).css
js: $(JS_ASSETS) $(JS_ASSETS).sha256
- install -D $< http/js/assets-$(shell cat $(JS_ASSETS).sha256).js
+ install -D $< $(TARGET)/js/assets-$(shell cat $(JS_ASSETS).sha256).js
static: $(STATIC)
echo "- var css_hash = '$(shell cat $(CSS_ASSETS).sha256)'" > $@
echo "- var js_hash = '$(shell cat $(JS_ASSETS).sha256)'" >> $@
-http/index.html: build/templates.jade build/hashes.jade
+$(TARGET)/index.html: build/templates.jade build/hashes.jade
$(JS_ASSETS): $(JS) node_modules
@mkdir -p $(shell dirname $@)
mkdir -p $(shell dirname $@)
sha256sum $< | sed 's/^\([a-f0-9]\+\) .*$$/\1/' > $@
-http/%: src/resources/%
+$(TARGET)/%: src/resources/%
install -D $< $@
-http/%.html: src/jade/%.jade $(wildcard src/jade/*.jade) node_modules
+$(TARGET)/%.html: src/jade/%.jade $(wildcard src/jade/*.jade) node_modules
@mkdir -p $(shell dirname $@)
- $(JADE) -P $< -o http || (rm -f $@; exit 1)
+ $(JADE) -P $< -o $(TARGET) || (rm -f $@; exit 1)
build/css/%.css: src/stylus/%.styl node_modules
mkdir -p $(shell dirname $@)
Substitute ``<ip>`` with the correct IP address. The default password is ``raspberry``. You should see a prompt like this: ``pi@raspberrypi ~ $``, but in color.
## Configure the RPi
-Copy the ``setup_rpi.sh`` script to the RPi and run it as root:
+Copy the ``scripts/setup_rpi.sh`` script to the RPi and run it as root:
```
-scp setup_rpi.sh pi@<ip>:
+scp scripts/setup_rpi.sh pi@<ip>:
ssh pi@<ip> sudo ./setup_rpi.sh
```
```
avrdude -c avrispmkII -p ATxmega128A3U -P usb -U flash:w:tinyg.hex:i
```
+
+
+## Setup Python Development
+++ /dev/null
-#!/bin/bash
-
-### BEGIN INIT INFO
-# Provides: bbctl
-# Required-Start: $local_fs $network
-# Required-Stop: $local_fs $network
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 6
-# Short-Description: Buildbotics Controller Web service
-# Description: Buildbotics Controller Web service
-### END INIT INFO
-
-DAEMON=/home/bbmc/bbctrl.py
-DAEMON_NAME=bbctrl
-DAEMON_OPTS=""
-DAEMON_USER=root
-DAEMON_DIR=$(dirname $DAEMON)
-PIDFILE=/var/run/$DAEMON_NAME.pid
-
-. /lib/lsb/init-functions
-
-
-do_start () {
- log_daemon_msg "Starting system $DAEMON_NAME daemon"
- 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"
- log_end_msg $?
-}
-
-
-do_stop () {
- log_daemon_msg "Stopping system $DAEMON_NAME daemon"
- start-stop-daemon --stop --pidfile $PIDFILE --retry 10
- log_end_msg $?
-}
-
-
-case "$1" in
- start|stop) do_${1} ;;
- restart|reload|force-reload) do_stop; do_start ;;
- status)
- status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
- ;;
-
- *)
- echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
- exit 1
- ;;
-esac
+++ /dev/null
-#!/usr/bin/env python3
-
-## Change this to match your local settings
-SERIAL_PORT = '/dev/ttyAMA0'
-SERIAL_BAUDRATE = 115200
-HTTP_PORT = 8080
-HTTP_ADDR = '0.0.0.0'
-
-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 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
- }
-
-
-with open('http/config-template.json', 'r', encoding = 'utf-8') as f:
- config_template = json.load(f)
-
-
-state = {}
-clients = []
-
-input_queue = multiprocessing.Queue()
-output_queue = multiprocessing.Queue()
-
-
-def on_exit(sig, func = None):
- print('exit handler triggered')
- 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('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, input_queue, output_queue):
- multiprocessing.Process.__init__(self)
- self.input_queue = input_queue
- self.output_queue = output_queue
- self.sp = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, 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, '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)
-
-screen = lcd.LCD(1, 0x27)
-
-
-def splash():
- 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.clear()
- screen.display(1, 'Goodbye', lcd.JUSTIFY_CENTER)
-
-
-if __name__ == "__main__":
- signal.signal(signal.SIGTERM, on_exit)
-
- import logging
- logging.getLogger().setLevel(logging.DEBUG)
-
- # Start the serial worker
- try:
- sp = SerialProcess(input_queue, output_queue)
- sp.daemon = True
- sp.start()
- except Exception as e:
- print(e)
-
- # Adjust the interval according to frames sent by serial port
- ioloop.PeriodicCallback(checkQueue, 100).start()
- ioloop.PeriodicCallback(checkEvents, 100).start()
-
- splash()
- atexit.register(goodbye)
-
- # Start the web server
- app = web.Application(router.urls + handlers)
- app.listen(HTTP_PORT, address = HTTP_ADDR)
- print('Listening on http://{}:{}/'.format(HTTP_ADDR, HTTP_PORT))
- ioloop.IOLoop.instance().start()
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import array
-import fcntl
-import struct
-from inevent import ioctl
-
-
-def EVIOCGABS(axis):
- return ioctl._IOR(ord('E'), 0x40 + axis, "ffffff") # get abs value/limits
-
-
-
-class AbsAxisScaling(object):
- """
- Fetches and implements the EV_ABS axis scaling.
-
- The constructor fetches the scaling values from the given stream for the
- given axis using an ioctl.
-
- There is a scale method, which scales a given value to the range -1..+1.
- """
-
- def __init__(self, stream, axis):
- """
- Fetch the scale values for this stream and fill in the instance
- variables accordingly.
- """
- s = array.array("i", [1, 2, 3, 4, 5, 6])
- try:
- fcntl.ioctl(stream.filehandle, EVIOCGABS(axis), s)
-
- except IOError:
- self.value = self.minimum = self.maximum = self.fuzz = self.flat = \
- self.resolution = 1
-
- else:
- self.value, self.minimum, self.maximum, self.fuzz, self.flat, \
- self.resolution = struct.unpack("iiiiii", s)
-
-
- def __str__(self):
- return "Value {0} Min {1}, Max {2}, Fuzz {3}, Flat {4}, Res {5}".format(
- self.value, self.minimum, self.maximum, self.fuzz, self.flat,
- self.resolution)
-
-
- def scale(self, value):
- """
- scales the given value into the range -1..+1
- """
- return (float(value) - float(self.minimum)) / \
- float(self.maximum - self.minimum) * 2.0 - 1.0
-
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-EV_SYN = 0x00
-EV_KEY = 0x01
-EV_REL = 0x02
-EV_ABS = 0x03
-EV_MSC = 0x04
-EV_SW = 0x05
-EV_LED = 0x11
-EV_SND = 0x12
-EV_REP = 0x14
-EV_FF = 0x15
-EV_PWR = 0x16
-EV_FF_STATUS = 0x17
-
-ev_type_name = {}
-ev_type_name[EV_SYN] = "SYN"
-ev_type_name[EV_KEY] = "KEY"
-ev_type_name[EV_REL] = "REL"
-ev_type_name[EV_ABS] = "ABS"
-ev_type_name[EV_MSC] = "MSC"
-ev_type_name[EV_SW] = "SW"
-ev_type_name[EV_LED] = "LED"
-ev_type_name[EV_SND] = "SND"
-ev_type_name[EV_REP] = "REP"
-ev_type_name[EV_FF] = "FF"
-ev_type_name[EV_PWR] = "PWR"
-ev_type_name[EV_FF_STATUS] = "FF_STATUS"
-
-SYN_REPORT = 0
-SYN_CONFIG = 1
-
-REL_X = 0x00
-REL_Y = 0x01
-REL_Z = 0x02
-REL_RX = 0x03
-REL_RY = 0x04
-REL_RZ = 0x05
-REL_HWHEEL = 0x06
-REL_DIAL = 0x07
-REL_WHEEL = 0x08
-REL_MISC = 0x09
-REL_MAX = 0x0f
-
-ABS_X = 0x00
-ABS_Y = 0x01
-ABS_Z = 0x02
-ABS_RX = 0x03
-ABS_RY = 0x04
-ABS_RZ = 0x05
-ABS_THROTTLE = 0x06
-ABS_RUDDER = 0x07
-ABS_WHEEL = 0x08
-ABS_GAS = 0x09
-ABS_BRAKE = 0x0a
-ABS_HAT0X = 0x10
-ABS_HAT0Y = 0x11
-ABS_HAT1X = 0x12
-ABS_HAT1Y = 0x13
-ABS_HAT2X = 0x14
-ABS_HAT2Y = 0x15
-ABS_HAT3X = 0x16
-ABS_HAT3Y = 0x17
-ABS_PRESSURE = 0x18
-ABS_DISTANCE = 0x19
-ABS_TILT_X = 0x1a
-ABS_TILT_Y = 0x1b
-ABS_TOOL_WIDTH = 0x1c
-ABS_VOLUME = 0x20
-ABS_MISC = 0x28
-ABS_MAX = 0x3f
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import struct
-
-from inevent import Format
-from inevent.Constants import *
-
-
-
-class Event(object):
- """
- A single event from the linux input event system.
-
- Events are tuples: (Time, Type, Code, Value)
- In addition we remember the stream it came from.
-
- Externally, only the unhandled event handler gets passed the whole event,
- but the SYN handler gets the code and value. (Also the keyboard handler, but
- those are renamed to key and value.)
-
- This class is responsible for converting the Linux input event structure into
- one of these objects and back again.
- """
- def __init__(self, stream, time = None, type = None, code = None,
- value = None):
- """
- Create a new event.
-
- Generally all but the stream parameter are left out; we will want to
- populate the object from a Linux input event using decode.
- """
- self.stream = stream
- self.time = time
- self.type = type
- self.code = code
- self.value = value
-
-
- def get_type_name(self):
- if self.type not in ev_type_name: return '0x%x' % self.type
- return ev_type_name[self.type]
-
-
- def get_source(self):
- return "%s[%d]" % (self.stream.devType, self.stream.devIndex)
-
-
- def __str__(self):
- """
- Uses the stream to give the device type and whether it is currently grabbed.
- """
- grabbed = "grabbed" if self.stream.grabbed else "ungrabbed"
-
- return "Event %s %s @%f: %s 0x%x=0x%x" % (
- self.get_source(), grabbed, self.time, self.get_type_name(), self.code,
- self.value)
-
-
- def __repr__(self):
- return "Event(%s, %f, 0x%x, 0x%x, 0x%x)" % (
- repr(self.stream), self.time, self.type, self.code, self.value)
-
-
- def encode(self):
- """
- Encode this event into a Linux input event structure.
-
- The output is packed into a string. It is unlikely that this function
- will be required, but it might as well be here.
- """
- tint = long(self.time)
- tfrac = long((self.time - tint) * 1000000)
-
- return \
- struct.pack(Format.Event, tsec, tfrac, self.type, self.code, self.value)
-
-
- def decode(self, s):
- """
- Decode a Linux input event into the fields of this object.
-
- Arguments:
- *s*
- A binary structure packed into a string.
- """
- tsec, tfrac, self.type, self.code, self.value = \
- struct.unpack(Format.Event, s)
-
- self.time = tsec + tfrac / 1000000.0
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-from inevent.Constants import *
-from inevent.EventStream import EventStream
-
-
-class EventHandler(object):
- """
- A class to handle events.
-
- Four types of events are handled: REL (mouse movement), KEY (keybaord keys and
- other device buttons), ABS (joysticks and gamepad analogue sticks) and SYN
- (delimits simultaneous events such as mouse movements)
- """
- def __init__(self):
- self.buttons = dict()
-
-
- def event(self, event, handler):
- """
- Handles the given event.
-
- If the event is passed to a handler or otherwise handled then returns None,
- else returns the event. All handlers are optional.
-
- All key events are handled by putting them in the self.buttons dict, and
- optionally by calling the supplied handler.
-
- REL X, Y and wheel V and H events are all accumulated internally and
- also optionally passed to the supplied handler. All these events are
- handled.
-
- ABS X, Y, Z, RX, RY, RZ, Hat0X, Hat0Y are all accumulated internally and
- also optionally passed to the supplied handler. Other ABS events are not
- handled.
-
- All SYN events are passed to the supplied handler.
-
- There are several ABS events that we do not handle. In particular:
- THROTTLE, RUDDER, WHEEL, GAS, BRAKE, HAT1, HAT2, HAT3, PRESSURE,
- DISTANCE, TILT, TOOL_WIDTH. Implementing these is left as an exercise
- for the interested reader.
-
- Likewise, since one handler is handling all events for all devices, we
- may get the situation where two devices return the same button. The only
- way to handle that would seem to be to have a key dict for every device,
- which seems needlessly profligate for a situation that may never arise.
- """
-
- state = event.stream.state
-
- if event.type == EV_KEY: self.buttons[event.code] = event.value
- elif event.type == EV_REL: state.rel[event.code] += event.value
- elif event.type == EV_ABS:
- state.abs[event.code] = event.stream.scale(event.code, event.value)
-
- if handler: handler(event, state)
-
-
- def key_state(self, code):
- """
- Returns the last event value for the given key code.
-
- Key names can be converted to key codes using codeOf[str].
- If the key is pressed the returned value will be 1 (pressed) or 2 (held).
- If the key is not pressed, the returned value will be 0.
- """
- return self.buttons.get(code, 0)
-
-
- def clear_key(self, code):
- """
- Clears the event value for the given key code.
-
- Key names can be converted to key codes using codeOf[str].
- This emulates a key-up but does not generate any events.
- """
- self.buttons[code] = 0
-
-
- def get_keys(self):
- """
- Returns the first of whichever keys have been pressed.
-
- Key names can be converted to key codes using codeOf[str].
- This emulates a key-up but does not generate any events.
- """
- k_list = []
-
- for k in self.buttons:
- if self.buttons[k] != 0: k_list.append(k)
-
- return k_list
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-from inevent.Constants import *
-
-
-class EventState:
- def __init__(self):
- self.abs = [0.0] * ABS_MAX
- self.rel = [0.0] * REL_MAX
-
-
- def __str__(self):
- return ("({:6.3f}, {:6.3f}, {:6.3f}) ".format(*self.get_joystick3d()) +
- "({:6.3f}, {:6.3f}, {:6.3f}) ".format(*self.get_joystickR3d()) +
- "({:2.0f}, {:2.0f}) ".format(*self.get_hat()) +
- "({:d}, {:d}) ".format(*self.get_mouse()) +
- "({:d}, {:d})".format(*self.get_wheel()))
-
-
- def get_joystick(self):
- """
- Returns the x,y coordinates for a joystick or left gamepad analogue stick.
-
- The values are returned as a tuple. All values are -1.0 to +1.0 with
- 0.0 being centred.
- """
- return self.abs[ABS_X], self.abs[ABS_Y]
-
-
- def get_joystick3d(self):
- """
- Returns the x,y,z coordinates for a joystick or left gamepad analogue stick
-
- The values are returned as a tuple. All values are -1.0 to +1.0 with
- 0.0 being centred.
- """
- return self.abs[ABS_X], self.abs[ABS_Y], self.abs[ABS_Z]
-
-
- def get_joystickR(self):
- """
- Returns the x,y coordinates for a right gamepad analogue stick.
-
- The values are returned as a tuple. For some odd reason, the gamepad
- returns values in the Z axes of both joysticks, with y being the first.
-
- All values are -1.0 to +1.0 with 0.0 being centred.
- """
- return self.abs[ABS_RZ], self.abs[ABS_Z]
-
-
- def get_joystickR3d(self):
- """
- Returns the x,y,z coordinates for a 2nd joystick control
-
- The values are returned as a tuple. All values are -1.0 to +1.0 with
- 0.0 being centred.
- """
- return self.abs[ABS_RX], self.abs[ABS_RY], self.abs[ABS_RZ]
-
-
- def get_hat(self):
- """
- Returns the x,y coordinates for a joystick hat or gamepad direction pad
-
- The values are returned as a tuple. All values are -1.0 to +1.0 with
- 0.0 being centred.
- """
- return self.abs[ABS_HAT0X], self.abs[ABS_HAT0Y]
-
-
- def get_mouse(self):
- return self.rel[REL_X], self.rel[REL_Y]
-
-
- def get_wheel(self):
- return self.rel[REL_WHEEL], self.rel[REL_HWHEEL]
-
-
- def get_mouse_movement(self):
- """
- Returns the accumulated REL (mouse or other relative device) movements
- since the last call.
-
- The returned value is a tuple: (X, Y, WHEEL, H-WHEEL)
- """
- ret = self.get_mouse() + self.get_wheel()
-
- self.rel[REL_X] = self.rel[REL_Y] = 0
- self.rel[REL_WHEEL] = self.rel[REL_HWHEEL] = 0
-
- return ret
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import fcntl
-import os
-import select
-
-from inevent.Constants import *
-from inevent import ioctl
-from inevent.AbsAxisScaling import AbsAxisScaling
-from inevent import Event
-from inevent import Format
-from inevent.EventState import EventState
-
-
-EVIOCGRAB = ioctl._IOW(ord('E'), 0x90, "i") # Grab/Release device
-
-
-
-class EventStream(object):
- """
- encapsulates the event* file handling
-
- Each device is represented by a file in /dev/input called eventN, where N is
- a small number. (Actually, a keybaord can be represented by two such files.)
- Instances of this class open one of these files and provide means to read
- events from them.
-
- Class methods also exist to read from multiple files simultaneously, and
- also to grab and ungrab all instances of a given type.
- """
- axisX = 0
- axisY = 1
- axisZ = 2
- axisRX = 3
- axisRY = 4
- axisRZ = 5
- axisHat0X = 6
- axisHat0Y = 7
- axisHat1X = 8
- axisHat1Y = 9
- axisHat2X = 10
- axisHat2Y = 11
- axisHat3X = 12
- axisHat3Y = 13
- axisThrottle = 14
- axisRudder = 15
- axisWheel = 16
- axisGas = 17
- axisBrake = 18
- axisPressure = 19
- axisDistance = 20
- axisTiltX = 21
- axisTiltY = 22
- axisToolWidth = 23
- numAxes = 24
-
- axisToEvent = [
- ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ, ABS_HAT0X, ABS_HAT0Y,
- ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y, ABS_HAT3X, ABS_HAT3Y,
- ABS_THROTTLE, ABS_RUDDER, ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_PRESSURE,
- ABS_DISTANCE, ABS_TILT_X, ABS_TILT_Y, ABS_TOOL_WIDTH]
-
-
- def __init__(self, devIndex, devType):
- """
- Opens the given /dev/input/event file and grabs it.
-
- Also adds it to a class-global list of all existing streams.
- """
- self.devIndex = devIndex
- self.devType = devType
- self.filename = "/dev/input/event" + str(devIndex)
- self.filehandle = os.open(self.filename, os.O_RDWR)
- self.state = EventState()
- self.grab(True)
- self.absInfo = [None] * ABS_MAX
-
- if devType == "js":
- for axis in range(ABS_MAX):
- self.absInfo[axis] = AbsAxisScaling(self, axis)
-
-
- def scale(self, axis, value):
- """
- Scale the given value according to the given axis.
-
- acquire_abs_info must have been previously called to acquire the data to
- do the scaling.
- """
- assert axis < ABS_MAX, "Axis number out of range"
-
- if self.absInfo[axis]: return self.absInfo[axis].scale(value)
- else: return value
-
-
- def grab(self, grab = True):
- """
- Grab (or release) exclusive access to all devices of the given type.
-
- The devices are grabbed if grab is True and released if grab is False.
-
- All devices are grabbed to begin with. We might want to ungrab the
- keyboard for example to use it for text entry. While not grabbed, all
- key-down and key-hold events are filtered out.
- """
- fcntl.ioctl(self.filehandle, EVIOCGRAB, 1 if grab else 0)
- self.grabbed = grab
-
-
- def __iter__(self):
- """s
- Required to make this class an iterator
- """
- return self
-
-
- def next(self):
- """
- Returns the next waiting event.
-
- If no event is waiting, returns None.
- """
- ready = select.select([self.filehandle], [], [], 0)[0]
- if ready: return self.read()
-
-
- def read(self):
- """
- Read and return the next waiting event.
- """
- try:
- s = os.read(self.filehandle, Format.EventSize)
- if s:
- event = Event.Event(self)
- event.decode(s)
- return event
-
- except: pass
-
-
- def __enter__(self): return self
-
-
- def release(self):
- "Ungrabs the file and closes it."
-
- try:
- self.grab(False)
- os.close(self.filehandle)
-
- except:
- pass
-
-
- def __exit__(self, type, value, traceback):
- "Ungrabs the file and closes it."
-
- self.release()
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import re
-from inevent.Constants import *
-
-
-def test_bit(nlst, b):
- index = b / 32
- bit = b % 32
- return index < len(nlst) and nlst[index] & (1 << bit)
-
-
-def EvToStr(events):
- s = []
-
- if test_bit(events, EV_SYN): s.append("EV_SYN")
- if test_bit(events, EV_KEY): s.append("EV_KEY")
- if test_bit(events, EV_REL): s.append("EV_REL")
- if test_bit(events, EV_ABS): s.append("EV_ABS")
- if test_bit(events, EV_MSC): s.append("EV_MSC")
- if test_bit(events, EV_LED): s.append("EV_LED")
- if test_bit(events, EV_SND): s.append("EV_SND")
- if test_bit(events, EV_REP): s.append("EV_REP")
- 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
-
-
-class DeviceCapabilities(object):
- def __init__(self, firstLine, filehandle):
- self.EV_SYNevents = []
- self.EV_KEYevents = []
- self.EV_RELevents = []
- self.EV_ABSevents = []
- self.EV_MSCevents = []
- self.EV_LEDevents = []
- self.EV_SNDevents = []
- self.EV_REPevents = []
- self.EV_FFevents = []
- 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)
- self.bus = 0
- self.vendor = 0
- self.product = 0
- self.version = 0
-
- else:
- self.bus = int(match.group(1), base = 16)
- self.vendor = int(match.group(2), base = 16)
- self.product = int(match.group(3), base = 16)
- self.version = int(match.group(4), base = 16)
-
- for line in filehandle:
- if not line.strip(): break
-
- if line[0] == "N":
- match = re.search('Name="([^"]+)"', line)
- if match: self.name = match.group(1)
- else: self.name = "UNKNOWN"
-
- elif line[0] == "P":
- match = re.search('Phys=(.+)', line)
- if match: self.phys = match.group(1)
- else: self.phys = "UNKNOWN"
-
- elif line[0] == "S":
- match = re.search('Sysfs=(.+)', line)
- if match: self.sysfs = match.group(1)
- else: self.sysfs = "UNKNOWN"
-
- elif line[0] == "U":
- match = re.search('Uniq=(.*)', line)
- if match: self.uniq = match.group(1)
- else: self.uniq = "UNKNOWN"
-
- elif line[0] == "H":
- match = re.search('Handlers=(.+)', line)
- if match: self.handlers = match.group(1).split()
- else: self.handlers = []
-
- elif line[:5] == "B: EV":
- eventsNums = [int(x, base = 16) for x in line[6:].split()]
- eventsNums.reverse()
- self.eventTypes = eventsNums
-
- elif line[:6] == "B: KEY":
- eventsNums = [int(x, base = 16) for x in line[7:].split()]
- eventsNums.reverse()
- self.EV_KEYevents = eventsNums
-
- elif line[:6] == "B: ABS":
- eventsNums = [int(x, base = 16) for x in line[7:].split()]
- eventsNums.reverse()
- self.EV_ABSevents = eventsNums
-
- elif line[:6] == "B: MSC":
- eventsNums = [int(x, base = 16) for x in line[7:].split()]
- eventsNums.reverse()
- self.EV_MSCevents = eventsNums
-
- elif line[:6] == "B: REL":
- eventsNums = [int(x, base = 16) for x in line[7:].split()]
- eventsNums.reverse()
- self.EV_RELevents = eventsNums
-
- elif line[:6] == "B: LED":
- eventsNums = [int(x, base = 16) for x in line[7:].split()]
- eventsNums.reverse()
- self.EV_LEDevents = eventsNums
-
- for handler in self.handlers:
- if handler[:5] == "event": self.eventIndex = int(handler[5:])
-
- self.isMouse = False
- self.isKeyboard = False
- self.isJoystick = False
-
-
- def doesProduce(self, eventType, eventCode):
- return test_bit(self.eventTypes, eventType) and (
- (eventType == EV_SYN and test_bit(self.EV_SYNevents, eventCode)) or
- (eventType == EV_KEY and test_bit(self.EV_KEYevents, eventCode)) or
- (eventType == EV_REL and test_bit(self.EV_RELevents, eventCode)) or
- (eventType == EV_ABS and test_bit(self.EV_ABSevents, eventCode)) or
- (eventType == EV_MSC and test_bit(self.EV_MSCevents, eventCode)) or
- (eventType == EV_LED and test_bit(self.EV_LEDevents, eventCode)) or
- (eventType == EV_SND and test_bit(self.EV_SNDevents, eventCode)) or
- (eventType == EV_REP and test_bit(self.EV_REPevents, eventCode)) or
- (eventType == EV_FF and test_bit(self.EV_FFevents, eventCode)) or
- (eventType == EV_PWR and test_bit(self.EV_PWRevents, eventCode)) or
- (eventType == EV_FF_STATUS and
- test_bit(self.EV_FF_STATUSevents, eventCode)))
-
-
- def __str__(self):
- return (
- "%s\n"
- "Bus: %s Vendor: %s Product: %s Version: %s\n"
- "Phys: %s\n"
- "Sysfs: %s\n"
- "Uniq: %s\n"
- "Handlers: %s Event Index: %s\n"
- "Keyboard: %s Mouse: %s Joystick: %s\n"
- "Events: %s" % (
- self.name, self.bus. self.vendor, self.product, self.version, self.phys,
- self.sysfs, self.uniq, self.handlers, self.eventIndex, self.isKeyboard,
- self.isMouse, self.isJoystick, EvToStr(self.eventTypes)))
-
-
-deviceCapabilities = []
-
-
-def get_devices(filename = "/proc/bus/input/devices"):
- global deviceCapabilities
-
- with open("/proc/bus/input/devices", "r") as filehandle:
- for line in filehandle:
- if line[0] == "I":
- deviceCapabilities.append(DeviceCapabilities(line, filehandle))
-
- return deviceCapabilities
-
-
-def print_devices():
- devs = get_devices()
-
- for dev in devs:
- print(str(dev))
- print(" ABS: {}"
- .format([x for x in range(64) if test_bit(dev.EV_ABSevents, x)]))
- print(" REL: {}"
- .format([x for x in range(64) if test_bit(dev.EV_RELevents, x)]))
- print(" MSC: {}"
- .format([x for x in range(64) if test_bit(dev.EV_MSCevents, x)]))
- print(" KEY: {}"
- .format([x for x in range(512) if test_bit(dev.EV_KEYevents, x)]))
- print(" LED: {}"
- .format([x for x in range(64) if test_bit(dev.EV_LEDevents, x)]))
- print()
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import struct
-
-Event = 'llHHi'
-EventSize = struct.calcsize(Event)
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import pyudev
-import re
-import select
-import errno
-
-from inevent.EventHandler import EventHandler
-from inevent import Keys
-from inevent.Constants import *
-from inevent.EventStream import EventStream
-
-
-_KEYS = (k for k in vars(Keys) if not k.startswith('_'))
-KEY_CODE = dict((k, getattr(Keys, k)) for k in _KEYS)
-CODE_KEY = {}
-for v in KEY_CODE: CODE_KEY[KEY_CODE[v]] = v
-
-
-def key_to_code(key):
- return KEY_CODE.get(str(key), -1) \
- if isinstance(key, str) else key
-
-
-def code_to_key(code): return CODE_KEY.get(code, '')
-
-
-def find_devices(types):
- """Finds the event indices of all devices of the specified types.
-
- A type is a string on the handlers line of /proc/bus/input/devices.
- Keyboards use "kbd", mice use "mouse" and joysticks (and gamepads) use "js".
-
- Returns a list of integer indexes N, where /dev/input/eventN is the event
- stream for each device.
-
- If butNot is given it holds a list of tuples which the returned values should
- not match.
-
- All devices of each type are returned; if you have two mice, they will both
- be used.
- """
- with open("/proc/bus/input/devices", "r") as filehandle:
- for line in filehandle:
- if line[0] == "H":
- for type in types:
- if type in line:
- match = re.search("event([0-9]+)", line)
- index = match and match.group(1)
- if index: yield int(index), type
- break
-
-
-
-class InEvent(object):
- """Encapsulates the entire InEvent subsystem.
-
- This is generally all you need to import.
-
- On instantiation, we open all devices that are keyboards, mice or joysticks.
- That means we might have two of one sort of another, and that might be a
- problem, but it would be rather rare.
-
- There are several ABS (joystick, touch) events that we do not handle,
- specifically THROTTLE, RUDDER, WHEEL, GAS, BRAKE, HAT1, HAT2, HAT3, PRESSURE,
- DISTANCE, TILT, TOOL_WIDTH. Implementing these is left as an exercise
- for the interested reader. Similarly, we make no attempt to handle
- multi-touch.
-
- Handlers can be supplied, in which case they are called for each event, but
- it isn't necessary; API exists for all the events.
-
- The handler signature is:
-
- def handler_func(event, state)
-
- where:
- event:
- An Event object describing the event.
-
- state:
- An EventState object describing the current state.
-
- Use key_to_code() to convert from the name of a key to its code,
- and code_to_key() to convert a code to a name.
-
- 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"]):
- self.streams = []
- self.handler = EventHandler()
- self.types = types
-
- devs = list(find_devices(types))
- 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()
-
-
- def process_udev_event(self):
- action, device = self.udevMon.receive_device()
-
- match = re.search(r"/dev/input/event([0-9]+)", str(device.device_node))
- devIndex = match and match.group(1)
- if not devIndex: return
- devIndex = int(devIndex)
-
- if action == 'add':
- for index, devType in find_devices(self.types):
- if index == devIndex:
- self.add_stream(devIndex, devType)
- break
-
- if action == 'remove':
- 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]
-
- # 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 add_stream(self, devIndex, devType):
- try:
- self.streams.append(EventStream(devIndex, devType))
- print('Added {}[{:d}]'.format(devType, devIndex))
-
- except OSError as e:
- if not e.errno in [errno.EPERM, errno.EACCES]: 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
-
-
- def key_state(self, key):
- """
- Returns the state of the given key.
-
- The returned value will be 0 for key-up, or 1 for key-down. This method
- returns a key-held(2) as 1 to aid in using the returned value as a
- movement distance.
-
- This function accepts either the key code or the string name of the key.
- It would be more efficient to look-up and store the code of
- the key with KEY_CODE[], rather than using the string every time. (Which
- involves a dict look-up keyed with a string for every key_state call, every
- time around the loop.)
-
- Gamepad keys are:
- Select = BTN_BASE3, Start = BTN_BASE4
- L1 = BTN_TOP R1 = BTN_BASE
- L2 = BTN_PINKIE R2 = BTN_BASE2
-
- The action buttons are:
- BTN_THUMB
- BTN_TRIGGER
- BTN_TOP
- BTN_THUMB2
-
- Analogue Left Button = BTN_BASE5
- Analogue Right Button = BTN_BASE6
-
- Some of those may clash with extended mouse buttons, so if you are using
- both at once, you'll see some overlap.
-
- The direction pad is hat0 (see get_hat)
- """
- return self.handler.key_state(key_to_code(key))
-
-
- def clear_key(self, key):
- """
- Clears the state of the given key.
-
- Emulates a key-up, but does not call any handlers.
- """
- return self.handler.clear_key(key_to_code(key))
-
-
- def get_keys(self):
- return [code_to_key(k) for k in self.handler.get_keys()]
-
-
- def release(self):
- """
- Ungrabs all streams and closes all files.
-
- Only do this when you're finished with this object. You can't use it again.
- """
- for s in self.streams: s.release()
-
+++ /dev/null
-from inevent.Constants import *
-
-
-def axes_to_string(axes):
- return "({:6.3f}, {:6.3f}, {:6.3f}, {:6.3f})".format(*axes)
-
-
-def print_event(event, state):
- print("{} {}: ".format(event.get_source(), event.get_type_name()), end = '')
-
- 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()))
-
- if event.type == EV_REL:
- print("({: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))
-
-
-class JogHandler:
- def __init__(self, config):
- self.config = config
- self.axes = [0.0, 0.0, 0.0, 0.0]
- self.speed = 3
- self.activate = 0
-
-
- def changed(self):
- print(axes_to_string(self.axes) + " x {:d}".format(self.speed))
-
-
- def __call__(self, event, state):
- if event.type not in [EV_ABS, EV_REL, EV_KEY]: return
-
- changed = False
-
- # Process event
- if event.type == EV_ABS and event.code in self.config['axes']:
- pass
-
- elif event.type == EV_ABS and event.code in self.config['arrows']:
- axis = self.config['arrows'].index(event.code)
-
- if event.value < 0:
- if axis == 1: print('up')
- else: print('left')
-
- elif 0 < event.value:
- if axis == 1: print('down')
- else: print('right')
-
- elif event.type == EV_KEY and event.code in self.config['speed']:
- old_speed = self.speed
- self.speed = self.config['speed'].index(event.code) + 1
- if self.speed != old_speed: changed = True
-
- elif event.type == EV_KEY and event.code in self.config['activate']:
- index = self.config['activate'].index(event.code)
-
- if event.value: self.activate |= 1 << index
- else: self.activate &= ~(1 << index)
-
- if self.config.get('verbose', False): print_event(event, state)
-
- # Update axes
- old_axes = list(self.axes)
-
- for axis in range(4):
- self.axes[axis] = event.stream.state.abs[self.config['axes'][axis]]
- if abs(self.axes[axis]) < self.config['deadband']:
- self.axes[axis] = 0
- if not (1 << axis) & self.activate and self.activate:
- self.axes[axis] = 0
-
- if old_axes != self.axes: changed = True
-
- if changed: self.changed()
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-KEY_ESC = 1
-KEY_1 = 2
-KEY_2 = 3
-KEY_3 = 4
-KEY_4 = 5
-KEY_5 = 6
-KEY_6 = 7
-KEY_7 = 8
-KEY_8 = 9
-KEY_9 = 10
-KEY_0 = 11
-KEY_MINUS = 12
-KEY_EQUAL = 13
-KEY_BACKSPACE = 14
-KEY_TAB = 15
-KEY_Q = 16
-KEY_W = 17
-KEY_E = 18
-KEY_R = 19
-KEY_T = 20
-KEY_Y = 21
-KEY_U = 22
-KEY_I = 23
-KEY_O = 24
-KEY_P = 25
-KEY_LEFTBRACE = 26
-KEY_RIGHTBRACE = 27
-KEY_ENTER = 28
-KEY_LEFTCTRL = 29
-KEY_A = 30
-KEY_S = 31
-KEY_D = 32
-KEY_F = 33
-KEY_G = 34
-KEY_H = 35
-KEY_J = 36
-KEY_K = 37
-KEY_L = 38
-KEY_SEMICOLON = 39
-KEY_APOSTROPHE = 40
-KEY_GRAVE = 41
-KEY_LEFTSHIFT = 42
-KEY_BACKSLASH = 43
-KEY_Z = 44
-KEY_X = 45
-KEY_C = 46
-KEY_V = 47
-KEY_B = 48
-KEY_N = 49
-KEY_M = 50
-KEY_COMMA = 51
-KEY_DOT = 52
-KEY_SLASH = 53
-KEY_RIGHTSHIFT = 54
-KEY_KPASTERISK = 55
-KEY_LEFTALT = 56
-KEY_SPACE = 57
-KEY_CAPSLOCK = 58
-KEY_F1 = 59
-KEY_F2 = 60
-KEY_F3 = 61
-KEY_F4 = 62
-KEY_F5 = 63
-KEY_F6 = 64
-KEY_F7 = 65
-KEY_F8 = 66
-KEY_F9 = 67
-KEY_F10 = 68
-KEY_NUMLOCK = 69
-KEY_SCROLLLOCK = 70
-KEY_KP7 = 71
-KEY_KP8 = 72
-KEY_KP9 = 73
-KEY_KPMINUS = 74
-KEY_KP4 = 75
-KEY_KP5 = 76
-KEY_KP6 = 77
-KEY_KPPLUS = 78
-KEY_KP1 = 79
-KEY_KP2 = 80
-KEY_KP3 = 81
-KEY_KP0 = 82
-KEY_KPDOT = 83
-
-KEY_ZENKAKUHANKAKU = 85
-KEY_102ND = 86
-KEY_F11 = 87
-KEY_F12 = 88
-KEY_RO = 89
-KEY_KATAKANA = 90
-KEY_HIRAGANA = 91
-KEY_HENKAN = 92
-KEY_KATAKANAHIRAGANA = 93
-KEY_MUHENKAN = 94
-KEY_KPJPCOMMA = 95
-KEY_KPENTER = 96
-KEY_RIGHTCTRL = 97
-KEY_KPSLASH = 98
-KEY_SYSRQ = 99
-KEY_RIGHTALT = 100
-KEY_LINEFEED = 101
-KEY_HOME = 102
-KEY_UP = 103
-KEY_PAGEUP = 104
-KEY_LEFT = 105
-KEY_RIGHT = 106
-KEY_END = 107
-KEY_DOWN = 108
-KEY_PAGEDOWN = 109
-KEY_INSERT = 110
-KEY_DELETE = 111
-KEY_MACRO = 112
-KEY_MUTE = 113
-KEY_VOLUMEDOWN = 114
-KEY_VOLUMEUP = 115
-KEY_POWER = 116
-KEY_KPEQUAL = 117
-KEY_KPPLUSMINUS = 118
-KEY_PAUSE = 119
-
-KEY_KPCOMMA = 121
-KEY_HANGUEL = 122
-KEY_HANJA = 123
-KEY_YEN = 124
-KEY_LEFTMETA = 125
-KEY_RIGHTMETA = 126
-KEY_COMPOSE = 127
-
-KEY_STOP = 128
-KEY_AGAIN = 129
-KEY_PROPS = 130
-KEY_UNDO = 131
-KEY_FRONT = 132
-KEY_COPY = 133
-KEY_OPEN = 134
-KEY_PASTE = 135
-KEY_FIND = 136
-KEY_CUT = 137
-KEY_HELP = 138
-KEY_MENU = 139
-KEY_CALC = 140
-KEY_SETUP = 141
-KEY_SLEEP = 142
-KEY_WAKEUP = 143
-KEY_FILE = 144
-KEY_SENDFILE = 145
-KEY_DELETEFILE = 146
-KEY_XFER = 147
-KEY_PROG1 = 148
-KEY_PROG2 = 149
-KEY_WWW = 150
-KEY_MSDOS = 151
-KEY_COFFEE = 152
-KEY_DIRECTION = 153
-KEY_CYCLEWINDOWS = 154
-KEY_MAIL = 155
-KEY_BOOKMARKS = 156
-KEY_COMPUTER = 157
-KEY_BACK = 158
-KEY_FORWARD = 159
-KEY_CLOSECD = 160
-KEY_EJECTCD = 161
-KEY_EJECTCLOSECD = 162
-KEY_NEXTSONG = 163
-KEY_PLAYPAUSE = 164
-KEY_PREVIOUSSONG = 165
-KEY_STOPCD = 166
-KEY_RECORD = 167
-KEY_REWIND = 168
-KEY_PHONE = 169
-KEY_ISO = 170
-KEY_CONFIG = 171
-KEY_HOMEPAGE = 172
-KEY_REFRESH = 173
-KEY_EXIT = 174
-KEY_MOVE = 175
-KEY_EDIT = 176
-KEY_SCROLLUP = 177
-KEY_SCROLLDOWN = 178
-KEY_KPLEFTPAREN = 179
-KEY_KPRIGHTPAREN = 180
-
-KEY_F13 = 183
-KEY_F14 = 184
-KEY_F15 = 185
-KEY_F16 = 186
-KEY_F17 = 187
-KEY_F18 = 188
-KEY_F19 = 189
-KEY_F20 = 190
-KEY_F21 = 191
-KEY_F22 = 192
-KEY_F23 = 193
-KEY_F24 = 194
-
-KEY_PLAYCD = 200
-KEY_PAUSECD = 201
-KEY_PROG3 = 202
-KEY_PROG4 = 203
-KEY_SUSPEND = 205
-KEY_CLOSE = 206
-KEY_PLAY = 207
-KEY_FASTFORWARD = 208
-KEY_BASSBOOST = 209
-KEY_PRINT = 210
-KEY_HP = 211
-KEY_CAMERA = 212
-KEY_SOUND = 213
-KEY_QUESTION = 214
-KEY_EMAIL = 215
-KEY_CHAT = 216
-KEY_SEARCH = 217
-KEY_CONNECT = 218
-KEY_FINANCE = 219
-KEY_SPORT = 220
-KEY_SHOP = 221
-KEY_ALTERASE = 222
-KEY_CANCEL = 223
-KEY_BRIGHTNESSDOWN = 224
-KEY_BRIGHTNESSUP = 225
-KEY_MEDIA = 226
-
-KEY_UNKNOWN = 240
-
-BTN_MISC = 0x100
-BTN_0 = 0x100
-BTN_1 = 0x101
-BTN_2 = 0x102
-BTN_3 = 0x103
-BTN_4 = 0x104
-BTN_5 = 0x105
-BTN_6 = 0x106
-BTN_7 = 0x107
-BTN_8 = 0x108
-BTN_9 = 0x109
-
-BTN_MOUSE = 0x110
-BTN_LEFT = 0x110
-BTN_RIGHT = 0x111
-BTN_MIDDLE = 0x112
-BTN_SIDE = 0x113
-BTN_EXTRA = 0x114
-BTN_FORWARD = 0x115
-BTN_BACK = 0x116
-BTN_TASK = 0x117
-
-BTN_JOYSTICK = 0x120
-BTN_TRIGGER = 0x120
-BTN_THUMB = 0x121
-BTN_THUMB2 = 0x122
-BTN_TOP = 0x123
-BTN_TOP2 = 0x124
-BTN_PINKIE = 0x125
-BTN_BASE = 0x126
-BTN_BASE2 = 0x127
-BTN_BASE3 = 0x128
-BTN_BASE4 = 0x129
-BTN_BASE5 = 0x12a
-BTN_BASE6 = 0x12b
-BTN_DEAD = 0x12f
-
-BTN_GAMEPAD = 0x130
-BTN_A = 0x130
-BTN_B = 0x131
-BTN_C = 0x132
-BTN_X = 0x133
-BTN_Y = 0x134
-BTN_Z = 0x135
-BTN_TL = 0x136
-BTN_TR = 0x137
-BTN_TL2 = 0x138
-BTN_TR2 = 0x139
-BTN_SELECT = 0x13a
-BTN_START = 0x13b
-BTN_MODE = 0x13c
-BTN_THUMBL = 0x13d
-BTN_THUMBR = 0x13e
-
-BTN_DIGI = 0x140
-BTN_TOOL_PEN = 0x140
-BTN_TOOL_RUBBER = 0x141
-BTN_TOOL_BRUSH = 0x142
-BTN_TOOL_PENCIL = 0x143
-BTN_TOOL_AIRBRUSH = 0x144
-BTN_TOOL_FINGER = 0x145
-BTN_TOOL_MOUSE = 0x146
-BTN_TOOL_LENS = 0x147
-BTN_TOUCH = 0x14a
-BTN_STYLUS = 0x14b
-BTN_STYLUS2 = 0x14c
-BTN_TOOL_DOUBLETAP = 0x14d
-BTN_TOOL_TRIPLETAP = 0x14e
-
-BTN_WHEEL = 0x150
-BTN_GEAR_DOWN = 0x150
-BTN_GEAR_UP = 0x151
-
-KEY_OK = 0x160
-KEY_SELECT = 0x161
-KEY_GOTO = 0x162
-KEY_CLEAR = 0x163
-KEY_POWER2 = 0x164
-KEY_OPTION = 0x165
-KEY_INFO = 0x166
-KEY_TIME = 0x167
-KEY_VENDOR = 0x168
-KEY_ARCHIVE = 0x169
-KEY_PROGRAM = 0x16a
-KEY_CHANNEL = 0x16b
-KEY_FAVORITES = 0x16c
-KEY_EPG = 0x16d
-KEY_PVR = 0x16e
-KEY_MHP = 0x16f
-KEY_LANGUAGE = 0x170
-KEY_TITLE = 0x171
-KEY_SUBTITLE = 0x172
-KEY_ANGLE = 0x173
-KEY_ZOOM = 0x174
-KEY_MODE = 0x175
-KEY_KEYBOARD = 0x176
-KEY_SCREEN = 0x177
-KEY_PC = 0x178
-KEY_TV = 0x179
-KEY_TV2 = 0x17a
-KEY_VCR = 0x17b
-KEY_VCR2 = 0x17c
-KEY_SAT = 0x17d
-KEY_SAT2 = 0x17e
-KEY_CD = 0x17f
-KEY_TAPE = 0x180
-KEY_RADIO = 0x181
-KEY_TUNER = 0x182
-KEY_PLAYER = 0x183
-KEY_TEXT = 0x184
-KEY_DVD = 0x185
-KEY_AUX = 0x186
-KEY_MP3 = 0x187
-KEY_AUDIO = 0x188
-KEY_VIDEO = 0x189
-KEY_DIRECTORY = 0x18a
-KEY_LIST = 0x18b
-KEY_MEMO = 0x18c
-KEY_CALENDAR = 0x18d
-KEY_RED = 0x18e
-KEY_GREEN = 0x18f
-KEY_YELLOW = 0x190
-KEY_BLUE = 0x191
-KEY_CHANNELUP = 0x192
-KEY_CHANNELDOWN = 0x193
-KEY_FIRST = 0x194
-KEY_LAST = 0x195
-KEY_AB = 0x196
-KEY_NEXT = 0x197
-KEY_RESTART = 0x198
-KEY_SLOW = 0x199
-KEY_SHUFFLE = 0x19a
-KEY_BREAK = 0x19b
-KEY_PREVIOUS = 0x19c
-KEY_DIGITS = 0x19d
-KEY_TEEN = 0x19e
-KEY_TWEN = 0x19f
-
-KEY_DEL_EOL = 0x1c0
-KEY_DEL_EOS = 0x1c1
-KEY_INS_LINE = 0x1c2
-KEY_DEL_LINE = 0x1c3
-
-KEY_FN = 0x1d0
-KEY_FN_ESC = 0x1d1
-KEY_FN_F1 = 0x1d2
-KEY_FN_F2 = 0x1d3
-KEY_FN_F3 = 0x1d4
-KEY_FN_F4 = 0x1d5
-KEY_FN_F5 = 0x1d6
-KEY_FN_F6 = 0x1d7
-KEY_FN_F7 = 0x1d8
-KEY_FN_F8 = 0x1d9
-KEY_FN_F9 = 0x1da
-KEY_FN_F10 = 0x1db
-KEY_FN_F11 = 0x1dc
-KEY_FN_F12 = 0x1dd
-KEY_FN_1 = 0x1de
-KEY_FN_2 = 0x1df
-KEY_FN_D = 0x1e0
-KEY_FN_E = 0x1e1
-KEY_FN_F = 0x1e2
-KEY_FN_S = 0x1e3
-KEY_FN_B = 0x1e4
-
-KEY_MAX = 0x1ff
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-from .InEvent import InEvent
-from .JogHandler import JogHandler
+++ /dev/null
-# The inevent Python module was adapted from pi3d.event from the pi3d
-# project.
-#
-# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
-# Copyright (c) 2015, Tim Skillman.
-# Copyright (c) 2015, Paddy Gaunt.
-# Copyright (c) 2015, Tom Ritchford.
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-# IOCTL macros
-#
-# ioctl command encoding: 32 bits total, command in lower 16 bits,
-# size of the parameter structure in the lower 14 bits of the
-# upper 16 bits.
-#
-# Encoding the size of the parameter structure in the ioctl request
-# is useful for catching programs compiled with old versions
-# and to avoid overwriting user space outside the user buffer area.
-# The highest 2 bits are reserved for indicating the ``access mode''.
-# NOTE: This limits the max parameter size to 16kB - 1
-#
-# The following is for compatibility across the various Linux
-# platforms. The generic ioctl numbering scheme doesn't really enforce
-# a type field. De facto, however, the top 8 bits of the lower 16
-# bits are indeed used as a type field, so we might just as well make
-# this explicit here.
-
-import struct
-
-
-sizeof = struct.calcsize
-
-_IOC_NRBITS = 8
-_IOC_TYPEBITS = 8
-_IOC_SIZEBITS = 14
-_IOC_DIRBITS = 2
-
-_IOC_NRMASK = (1 << _IOC_NRBITS) - 1
-_IOC_TYPEMASK = (1 << _IOC_TYPEBITS) - 1
-_IOC_SIZEMASK = (1 << _IOC_SIZEBITS) - 1
-_IOC_DIRMASK = (1 << _IOC_DIRBITS) - 1
-
-_IOC_NRSHIFT = 0
-_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
-_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
-_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
-
-_IOC_NONE = 0
-_IOC_WRITE = 1
-_IOC_READ = 2
-_IOC_RW = _IOC_READ | _IOC_WRITE
-
-
-def _IOC(dir, type, nr, size):
- return int(
- (dir << _IOC_DIRSHIFT) |
- (type << _IOC_TYPESHIFT) |
- (nr << _IOC_NRSHIFT) |
- (size << _IOC_SIZESHIFT))
-
-# encode ioctl numbers
-def _IO(type, nr): return _IOC(_IOC_NONE, type, nr, 0)
-def _IOR(type, nr, fmt): return _IOC(_IOC_READ, type, nr, sizeof(fmt))
-def _IOW(type, nr, fmt): return _IOC(_IOC_WRITE, type, nr, sizeof(fmt))
-def _IOWR(type, nr, fmt): return _IOC(_IOC_RW, type, nr, sizeof(fmt))
-def _IOR_BAD(type, nr, fmt): return _IOC(_IOC_READ, type, nr, sizeof(fmt))
-def _IOW_BAD(type, nr, fmt): return _IOC(_IOC_WRITE, type, nr, sizeof(fmt))
-def _IOWR_BAD(type, nr, fmt): return _IOC(_IOC_RW, type, nr, sizeof(fmt))
-
-# decode ioctl numbers
-def _IOC_DIR(nr): return (nr >> _IOC_DIRSHIFT) & _IOC_DIRMASK
-def _IOC_TYPE(nr): return (nr >> _IOC_TYPESHIFT) & _IOC_TYPEMASK
-def _IOC_NR(nr): return (nr >> _IOC_NRSHIFT) & _IOC_NRMASK
-def _IOC_SIZE(nr): return (nr >> _IOC_SIZESHIFT) & _IOC_SIZEMASK
-
-# for drivers/sound files
-IOC_IN = _IOC_WRITE << _IOC_DIRSHIFT
-IOC_OUT = _IOC_READ << _IOC_DIRSHIFT
-IOC_INOUT = _IOC_RW << _IOC_DIRSHIFT
-IOCSIZE_MASK = _IOC_SIZEMASK << _IOC_SIZESHIFT
-IOCSIZE_SHIFT = _IOC_SIZESHIFT
-
+++ /dev/null
-#!/usr/bin/env python3
-
-from inevent import InEvent, JogHandler
-from inevent.Constants import *
-
-
-if __name__ == "__main__":
- # Load config
- config = {
- "deadband": 0.1,
- "axes": [ABS_X, ABS_Y, ABS_RZ],
- "arrows": [ABS_HAT0X, ABS_HAT0Y],
- "speed": [0x120, 0x121, 0x122, 0x123],
- "activate": [0x124, 0x126, 0x125]
- }
-
- # Listen for input events
- events = InEvent(types = "js kbd".split())
- handler = JogHandler(config)
-
- while not events.key_state("KEY_ESC"):
- events.process_events(handler)
+++ /dev/null
-#!/usr/bin/env python3
-
-import smbus
-import time
-
-
-# Control flags
-REG_SELECT_BIT = 1 << 0
-READ_BIT = 1 << 1
-ENABLE_BIT = 1 << 2
-BACKLIGHT_BIT = 1 << 3
-
-# Commands
-LCD_CLEAR_DISPLAY = 1 << 0
-LCD_RETURN_HOME = 1 << 1
-LCD_ENTRY_MODE_SET = 1 << 2
-LCD_DISPLAY_CONTROL = 1 << 3
-LCD_CURSOR_SHIFT = 1 << 4
-LCD_FUNCTION_SET = 1 << 5
-LCD_SET_CGRAM_ADDR = 1 << 6
-LCD_SET_DDRAM_ADDR = 1 << 7
-
-# Entry Mode Set flags
-LCD_ENTRY_SHIFT_DISPLAY = 1 << 0
-LCD_ENTRY_SHIFT_INC = 1 << 1
-LCD_ENTRY_SHIFT_DEC = 0 << 1
-
-# Display Control flags
-LCD_BLINK_ON = 1 << 0
-LCD_BLINK_OFF = 0 << 0
-LCD_CURSOR_ON = 1 << 1
-LCD_CURSOR_OFF = 0 << 1
-LCD_DISPLAY_ON = 1 << 2
-LCD_DISPLAY_OFF = 0 << 2
-
-# Cursor Shift flags
-LCD_SHIFT_RIGHT = 1 << 2
-LCD_SHIFT_LEFT = 0 << 2
-LCD_SHIFT_DISPLAY = 1 << 3
-LCD_SHIFT_CURSOR = 0 << 3
-
-# Function Set flags
-LCD_5x11_DOTS = 1 << 2
-LCD_5x8_DOTS = 0 << 2
-LCD_2_LINE = 1 << 3
-LCD_1_LINE = 0 << 3
-LCD_8_BIT_MODE = 1 << 4
-LCD_4_BIT_MODE = 0 << 4
-
-# Text justification flags
-JUSTIFY_LEFT = 0
-JUSTIFY_RIGHT = 1
-JUSTIFY_CENTER = 2
-
-
-
-class LCD:
- def __init__(self, port, addr, height = 4, width = 20):
- self.addr = addr
- self.height = height
- self.width = width
-
- self.bus = smbus.SMBus(port)
- self.backlight = True
-
- self.reset()
-
-
- def reset(self):
- self.clear()
- self.write(LCD_FUNCTION_SET | LCD_2_LINE | LCD_5x8_DOTS |
- LCD_4_BIT_MODE)
- self.write(LCD_DISPLAY_CONTROL | LCD_DISPLAY_ON)
- self.write(LCD_ENTRY_MODE_SET | LCD_ENTRY_SHIFT_INC)
-
-
- def write_i2c(self, data):
- if self.backlight: data |= BACKLIGHT_BIT
-
- self.bus.write_byte(self.addr, data)
- time.sleep(0.0001)
-
-
- # Write half of a command to LCD
- def write_nibble(self, data):
- self.write_i2c(data)
-
- # Strobe
- self.write_i2c(data | ENABLE_BIT)
- time.sleep(0.0005)
-
- self.write_i2c(data & ~ENABLE_BIT)
- time.sleep(0.0001)
-
-
- # Write an 8-bit command to LCD
- def write(self, cmd, flags = 0):
- self.write_nibble(flags | (cmd & 0xf0))
- self.write_nibble(flags | ((cmd << 4) & 0xf0))
-
-
- def set_cursor(self, on, blink):
- data = LCD_DISPLAY_CONTROL
-
- if on: data |= LCD_CURSOR_ON
- if blink: data |= LCD_BLINK_ON
-
- self.write(data)
-
-
- def set_backlight(self, enable):
- self.backlight = enable
- self.write_i2c(0)
-
-
- def program_char(self, addr, data):
- if addr < 0 or 8 <= addr: return
-
- self.write(LCD_SET_CGRAM_ADDR | (addr << 3))
- for x in data:
- self.write(x, REG_SELECT_BIT)
-
-
- def goto(self, x, y):
- if x < 0 or self.width <= x or y < 0 or self.height <= y: return
- self.write(LCD_SET_DDRAM_ADDR | (0, 64, 20, 84)[y] + int(x))
-
-
- def text(self, msg):
- for c in msg:
- self.write(ord(c), REG_SELECT_BIT)
-
-
- def display(self, line, msg, justify = JUSTIFY_LEFT):
- if justify == JUSTIFY_RIGHT: x = self.width - len(msg)
- elif justify == JUSTIFY_CENTER: x = (self.width - len(msg)) / 2
- else: x = 0
-
- if x < 0: x = 0
-
- self.goto(x, line)
- self.text(msg)
-
-
- def shift(self, count = 1, right = True, display = True):
- cmd = LCD_CURSOR_SHIFT
- if right: cmd |= LCD_SHIFT_RIGHT
- if display: cmd |= LCD_SHIFT_DISPLAY
-
- for i in range(count): self.write(cmd)
-
-
- # Clear LCD and move cursor home
- def clear(self):
- self.write(LCD_CLEAR_DISPLAY)
- self.write(LCD_RETURN_HOME)
-
-
-
-if __name__ == "__main__":
- lcd = LCD(1, 0x27)
-
- lcd.clear()
-
- lcd.program_char(0, (0b11011,
- 0b11011,
- 0b00000,
- 0b01100,
- 0b01100,
- 0b00000,
- 0b11011,
- 0b11011))
-
- lcd.program_char(1, (0b11000,
- 0b01100,
- 0b00110,
- 0b00011,
- 0b00011,
- 0b00110,
- 0b01100,
- 0b11000))
-
- lcd.program_char(2, (0b00011,
- 0b00110,
- 0b01100,
- 0b11000,
- 0b11000,
- 0b01100,
- 0b00110,
- 0b00011))
-
- lcd.display(0, '\0' * lcd.width)
- lcd.display(1, 'Hello world!', JUSTIFY_CENTER)
- lcd.display(2, '\1\2' * (lcd.width / 2))
- lcd.display(3, '12345678901234567890')
--- /dev/null
+#!/bin/bash
+
+### BEGIN INIT INFO
+# Provides: bbctl
+# Required-Start: $local_fs $network
+# Required-Stop: $local_fs $network
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Buildbotics Controller Web service
+# Description: Buildbotics Controller Web service
+### END INIT INFO
+
+DAEMON=/home/bbmc/bbctrl.py
+DAEMON_NAME=bbctrl
+DAEMON_OPTS=""
+DAEMON_USER=root
+DAEMON_DIR=$(dirname $DAEMON)
+PIDFILE=/var/run/$DAEMON_NAME.pid
+
+. /lib/lsb/init-functions
+
+
+do_start () {
+ log_daemon_msg "Starting system $DAEMON_NAME daemon"
+ 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"
+ log_end_msg $?
+}
+
+
+do_stop () {
+ log_daemon_msg "Stopping system $DAEMON_NAME daemon"
+ start-stop-daemon --stop --pidfile $PIDFILE --retry 10
+ log_end_msg $?
+}
+
+
+case "$1" in
+ start|stop) do_${1} ;;
+ restart|reload|force-reload) do_stop; do_start ;;
+ status)
+ status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
+ ;;
+
+ *)
+ echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
+ exit 1
+ ;;
+esac
--- /dev/null
+#!/usr/bin/env python3
+
+from inevent import InEvent, JogHandler
+from inevent.Constants import *
+
+
+if __name__ == "__main__":
+ # Load config
+ config = {
+ "deadband": 0.1,
+ "axes": [ABS_X, ABS_Y, ABS_RZ],
+ "arrows": [ABS_HAT0X, ABS_HAT0Y],
+ "speed": [0x120, 0x121, 0x122, 0x123],
+ "activate": [0x124, 0x126, 0x125]
+ }
+
+ # Listen for input events
+ events = InEvent(types = "js kbd".split())
+ handler = JogHandler(config)
+
+ while not events.key_state("KEY_ESC"):
+ events.process_events(handler)
--- /dev/null
+#!/bin/bash
+
+ID=2
+
+# Update the system
+apt-get update
+apt-get dist-upgrade -y
+
+# Resize FS
+# TODO no /dev/root in Jessie
+ROOT_PART=$(readlink /dev/root)
+PART_NUM=${ROOT_PART#mmcblk0p}
+
+if [ "$PART_NUM" != "$ROOT_PART" ]; then
+ # Get the starting offset of the root partition
+ PART_START=$(
+ parted /dev/mmcblk0 -ms unit s p | grep "^${PART_NUM}" | cut -f 2 -d:)
+ [ "$PART_START" ] &&
+
+ fdisk /dev/mmcblk0 <<EOF &&
+p
+d
+$PART_NUM
+n
+p
+$PART_NUM
+$PART_START
+p
+w
+EOF
+
+ # Now set up an init.d script to do the resize
+ cat <<\EOF >/etc/init.d/resize2fs_once &&
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: resize2fs_once
+# Required-Start:
+# Required-Stop:
+# Default-Start: 2 3 4 5 S
+# Default-Stop:
+# Short-Description: Resize the root filesystem to fill partition
+# Description:
+### END INIT INFO
+. /lib/lsb/init-functions
+case "$1" in
+ start)
+ log_daemon_msg "Starting resize2fs_once" &&
+ resize2fs /dev/root &&
+ rm /etc/init.d/resize2fs_once &&
+ update-rc.d resize2fs_once remove &&
+ log_end_msg $?
+ ;;
+ *)
+ echo "Usage: $0 start" >&2
+ exit 3
+ ;;
+esac
+EOF
+
+ chmod +x /etc/init.d/resize2fs_once &&
+ update-rc.d resize2fs_once defaults
+fi
+
+# Install pacakges
+apt-get install -y avahi-daemon avrdude minicom python3-pip i2c-tools
+pip-3.2 install tornado sockjs-tornado pyserial smbus
+
+# Clean
+apt-get autoclean
+
+# Change hostname
+sed -i "s/raspberrypi/bbctrl$ID/" /etc/hosts /etc/hostname
+
+# Create user
+useradd -m -p $(openssl passwd -1 buildbotics) -s /bin/bash bbmc
+echo "bbmc ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+
+# Disable console on serial port
+#sed -i 's/^\(.*ttyAMA0.*\)$/# \1/' /etc/inittab
+sed -i 's/console=ttyAMA0,115200 //' /boot/cmdline.txt
+
+# Disable extra gettys
+sed -i 's/^\([23456]:.*\/sbin\/getty\)/#\1/' /etc/inittab
+
+# Enable I2C
+sed -i 's/#dtparam=i2c/dtparam=i2c/' /boot/config.txt
+echo i2c-bcm2708 >> /etc/modules
+echo i2c-dev >> /etc/modules
+
+# Install bbctrl w/ init.d script
+cp bbctrl.init.d /etc/init.d/bbctrl
+chmod +x /etc/init.d/bbctrl
+update-rc.d bbctrl defaults
+
+# TODO setup input and serial device permissions in udev
+
+reboot
--- /dev/null
+function convertToAbsolute(path) {
+ var x0, y0, x1, y1, x2, y2, segs = path.pathSegList;
+
+ for (var x = 0, y = 0, i = 0, len = segs.numberOfItems; i < len; i++) {
+ var seg = segs.getItem(i), c = seg.pathSegTypeAsLetter;
+
+ if (/[MLHVCSQTA]/.test(c)){
+ if ('x' in seg) x = seg.x;
+ if ('y' in seg) y = seg.y;
+
+ } else {
+ if ('x1' in seg) x1 = x + seg.x1;
+ if ('x2' in seg) x2 = x + seg.x2;
+ if ('y1' in seg) y1 = y + seg.y1;
+ if ('y2' in seg) y2 = y + seg.y2;
+ if ('x' in seg) x += seg.x;
+ if ('y' in seg) y += seg.y;
+
+ switch(c) {
+ case 'm':
+ segs.replaceItem(path.createSVGPathSegMovetoAbs(x, y), i);
+ break;
+ case 'l':
+ segs.replaceItem(path.createSVGPathSegLinetoAbs(x, y), i);
+ break;
+ case 'h':
+ segs.replaceItem(path.createSVGPathSegLinetoHorizontalAbs(x), i);
+ break;
+ case 'v':
+ segs.replaceItem(path.createSVGPathSegLinetoVerticalAbs(y), i);
+ break;
+ case 'c':
+ segs.replaceItem(
+ path.createSVGPathSegCurvetoCubicAbs(x, y, x1, y1, x2, y2), i);
+ break;
+ case 's':
+ segs.replaceItem(
+ path.createSVGPathSegCurvetoCubicSmoothAbs(x, y, x2, y2), i);
+ break;
+ case 'q':
+ segs.replaceItem(
+ path.createSVGPathSegCurvetoQuadraticAbs(x, y, x1, y1), i);
+ break;
+ case 't':
+ segs.replaceItem(
+ path.createSVGPathSegCurvetoQuadraticSmoothAbs(x, y), i);
+ break;
+ case 'a':
+ segs.replaceItem(
+ path.createSVGPathSegArcAbs(x, y, seg.r1, seg.r2, seg.angle,
+ seg.largeArcFlag, seg.sweepFlag), i);
+ break;
+
+ case 'z': case 'Z':
+ x = x0;
+ y = y0;
+ break;
+ }
+ }
+
+ // Record the start of a subpath
+ if (c == 'M' || c == 'm') x0 = x, y0 = y;
+ }
+}
--- /dev/null
+#!/usr/bin/env python3
+
+from setuptools import setup
+
+setup(
+ name = 'bbctrl',
+ version = '0.0.2',
+ description = 'Buildbotics Machine Controller',
+ long_description = open('README.md', 'rt').read(),
+ author = 'Joseph Coffland',
+ author_email = 'joseph@buildbotics.org',
+ platforms = ['any'],
+ license = 'GPL 3+',
+ url = 'https://github.com/buildbotics/rpi-firmware',
+ package_dir = {'': 'src/py'},
+ packages = ['bbctrl', 'inevent', 'lcd'],
+ include_package_data = True,
+ eager_resources = ['bbctrl/http/*'],
+ entry_points = {
+ 'console_scripts': [
+ 'bbctrl = bbctrl:run'
+ ]
+ },
+ install_requires = 'tornado sockjs-tornado pyserial smbus2'.split(),
+ zip_save = False,
+ )
+++ /dev/null
-#!/bin/bash
-
-ID=2
-
-# Update the system
-apt-get update
-apt-get dist-upgrade -y
-
-# Resize FS
-# TODO no /dev/root in Jessie
-ROOT_PART=$(readlink /dev/root)
-PART_NUM=${ROOT_PART#mmcblk0p}
-
-if [ "$PART_NUM" != "$ROOT_PART" ]; then
- # Get the starting offset of the root partition
- PART_START=$(
- parted /dev/mmcblk0 -ms unit s p | grep "^${PART_NUM}" | cut -f 2 -d:)
- [ "$PART_START" ] &&
-
- fdisk /dev/mmcblk0 <<EOF &&
-p
-d
-$PART_NUM
-n
-p
-$PART_NUM
-$PART_START
-p
-w
-EOF
-
- # Now set up an init.d script to do the resize
- cat <<\EOF >/etc/init.d/resize2fs_once &&
-#!/bin/sh
-### BEGIN INIT INFO
-# Provides: resize2fs_once
-# Required-Start:
-# Required-Stop:
-# Default-Start: 2 3 4 5 S
-# Default-Stop:
-# Short-Description: Resize the root filesystem to fill partition
-# Description:
-### END INIT INFO
-. /lib/lsb/init-functions
-case "$1" in
- start)
- log_daemon_msg "Starting resize2fs_once" &&
- resize2fs /dev/root &&
- rm /etc/init.d/resize2fs_once &&
- update-rc.d resize2fs_once remove &&
- log_end_msg $?
- ;;
- *)
- echo "Usage: $0 start" >&2
- exit 3
- ;;
-esac
-EOF
-
- chmod +x /etc/init.d/resize2fs_once &&
- update-rc.d resize2fs_once defaults
-fi
-
-# Install pacakges
-apt-get install -y avahi-daemon avrdude minicom python3-pip i2c-tools
-pip-3.2 install tornado sockjs-tornado pyserial smbus
-
-# Clean
-apt-get autoclean
-
-# Change hostname
-sed -i "s/raspberrypi/bbctrl$ID/" /etc/hosts /etc/hostname
-
-# Create user
-useradd -m -p $(openssl passwd -1 buildbotics) -s /bin/bash bbmc
-echo "bbmc ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
-
-# Disable console on serial port
-#sed -i 's/^\(.*ttyAMA0.*\)$/# \1/' /etc/inittab
-sed -i 's/console=ttyAMA0,115200 //' /boot/cmdline.txt
-
-# Disable extra gettys
-sed -i 's/^\([23456]:.*\/sbin\/getty\)/#\1/' /etc/inittab
-
-# Enable I2C
-sed -i 's/#dtparam=i2c/dtparam=i2c/' /boot/config.txt
-echo i2c-bcm2708 >> /etc/modules
-echo i2c-dev >> /etc/modules
-
-# Install bbctrl w/ init.d script
-cp bbctrl.init.d /etc/init.d/bbctrl
-chmod +x /etc/init.d/bbctrl
-update-rc.d bbctrl defaults
-
-# TODO setup input and serial device permissions in udev
-
-reboot
+++ /dev/null
-#!/usr/bin/env python3
-
-import lcd
-
-if __name__ == "__main__":
- screen = lcd.LCD(1, 0x27)
-
- screen.clear()
- screen.display(0, 'Buildbotics', lcd.JUSTIFY_CENTER)
- screen.display(1, 'Controller', lcd.JUSTIFY_CENTER)
- screen.display(3, 'Booting...', lcd.JUSTIFY_CENTER)
--- /dev/null
+#!/usr/bin/env python3
+
+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 argparse
+
+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()
+
+
+def get_resource(path):
+ return resource_filename(Requirement.parse('bbctrl'), 'bbctrl/' + path)
+
+
+def on_exit(sig, func = None):
+ print('exit handler triggered')
+ 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('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, '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')
+
+ parser.add_argument('-p', '--port', default = 80,
+ type = int, help = 'HTTP port')
+ parser.add_argument('-a', '--addr', metavar = 'IP', default = '0.0.0.0',
+ help = 'HTTP address to bind')
+ parser.add_argument('-s', '--serial', default = '/dev/ttyAMA0',
+ help = 'Serial device')
+ parser.add_argument('-b', '--baud', default = 115200, type = int,
+ help = 'Serial baud rate')
+ parser.add_argument('--lcd-port', default = 1, type = int,
+ help = 'LCD I2C port')
+ parser.add_argument('--lcd-addr', default = 0x27, type = int,
+ help = 'LCD I2C address')
+ parser.add_argument('-v', '--verbose', action = 'store_true',
+ help = 'Verbose output')
+
+ 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)
+
+ # 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()
+
+ # Setup LCD
+ global screen
+ screen = lcd.LCD(args.lcd_port, args.lcd_addr)
+ splash(screen)
+ atexit.register(goodbye, screen)
+
+ # Start the web server
+ app = web.Application(router.urls + handlers)
+
+ 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)
+
+ print('Listening on http://{}:{}/'.format(args.addr, args.port))
+
+ ioloop.IOLoop.instance().start()
+
+
+if __name__ == "__main__": run()
--- /dev/null
+../../../build/http/
\ No newline at end of file
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import array
+import fcntl
+import struct
+from inevent import ioctl
+
+
+def EVIOCGABS(axis):
+ return ioctl._IOR(ord('E'), 0x40 + axis, "ffffff") # get abs value/limits
+
+
+
+class AbsAxisScaling(object):
+ """
+ Fetches and implements the EV_ABS axis scaling.
+
+ The constructor fetches the scaling values from the given stream for the
+ given axis using an ioctl.
+
+ There is a scale method, which scales a given value to the range -1..+1.
+ """
+
+ def __init__(self, stream, axis):
+ """
+ Fetch the scale values for this stream and fill in the instance
+ variables accordingly.
+ """
+ s = array.array("i", [1, 2, 3, 4, 5, 6])
+ try:
+ fcntl.ioctl(stream.filehandle, EVIOCGABS(axis), s)
+
+ except IOError:
+ self.value = self.minimum = self.maximum = self.fuzz = self.flat = \
+ self.resolution = 1
+
+ else:
+ self.value, self.minimum, self.maximum, self.fuzz, self.flat, \
+ self.resolution = struct.unpack("iiiiii", s)
+
+
+ def __str__(self):
+ return "Value {0} Min {1}, Max {2}, Fuzz {3}, Flat {4}, Res {5}".format(
+ self.value, self.minimum, self.maximum, self.fuzz, self.flat,
+ self.resolution)
+
+
+ def scale(self, value):
+ """
+ scales the given value into the range -1..+1
+ """
+ return (float(value) - float(self.minimum)) / \
+ float(self.maximum - self.minimum) * 2.0 - 1.0
+
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+EV_SYN = 0x00
+EV_KEY = 0x01
+EV_REL = 0x02
+EV_ABS = 0x03
+EV_MSC = 0x04
+EV_SW = 0x05
+EV_LED = 0x11
+EV_SND = 0x12
+EV_REP = 0x14
+EV_FF = 0x15
+EV_PWR = 0x16
+EV_FF_STATUS = 0x17
+
+ev_type_name = {}
+ev_type_name[EV_SYN] = "SYN"
+ev_type_name[EV_KEY] = "KEY"
+ev_type_name[EV_REL] = "REL"
+ev_type_name[EV_ABS] = "ABS"
+ev_type_name[EV_MSC] = "MSC"
+ev_type_name[EV_SW] = "SW"
+ev_type_name[EV_LED] = "LED"
+ev_type_name[EV_SND] = "SND"
+ev_type_name[EV_REP] = "REP"
+ev_type_name[EV_FF] = "FF"
+ev_type_name[EV_PWR] = "PWR"
+ev_type_name[EV_FF_STATUS] = "FF_STATUS"
+
+SYN_REPORT = 0
+SYN_CONFIG = 1
+
+REL_X = 0x00
+REL_Y = 0x01
+REL_Z = 0x02
+REL_RX = 0x03
+REL_RY = 0x04
+REL_RZ = 0x05
+REL_HWHEEL = 0x06
+REL_DIAL = 0x07
+REL_WHEEL = 0x08
+REL_MISC = 0x09
+REL_MAX = 0x0f
+
+ABS_X = 0x00
+ABS_Y = 0x01
+ABS_Z = 0x02
+ABS_RX = 0x03
+ABS_RY = 0x04
+ABS_RZ = 0x05
+ABS_THROTTLE = 0x06
+ABS_RUDDER = 0x07
+ABS_WHEEL = 0x08
+ABS_GAS = 0x09
+ABS_BRAKE = 0x0a
+ABS_HAT0X = 0x10
+ABS_HAT0Y = 0x11
+ABS_HAT1X = 0x12
+ABS_HAT1Y = 0x13
+ABS_HAT2X = 0x14
+ABS_HAT2Y = 0x15
+ABS_HAT3X = 0x16
+ABS_HAT3Y = 0x17
+ABS_PRESSURE = 0x18
+ABS_DISTANCE = 0x19
+ABS_TILT_X = 0x1a
+ABS_TILT_Y = 0x1b
+ABS_TOOL_WIDTH = 0x1c
+ABS_VOLUME = 0x20
+ABS_MISC = 0x28
+ABS_MAX = 0x3f
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import struct
+
+from inevent import Format
+from inevent.Constants import *
+
+
+
+class Event(object):
+ """
+ A single event from the linux input event system.
+
+ Events are tuples: (Time, Type, Code, Value)
+ In addition we remember the stream it came from.
+
+ Externally, only the unhandled event handler gets passed the whole event,
+ but the SYN handler gets the code and value. (Also the keyboard handler, but
+ those are renamed to key and value.)
+
+ This class is responsible for converting the Linux input event structure into
+ one of these objects and back again.
+ """
+ def __init__(self, stream, time = None, type = None, code = None,
+ value = None):
+ """
+ Create a new event.
+
+ Generally all but the stream parameter are left out; we will want to
+ populate the object from a Linux input event using decode.
+ """
+ self.stream = stream
+ self.time = time
+ self.type = type
+ self.code = code
+ self.value = value
+
+
+ def get_type_name(self):
+ if self.type not in ev_type_name: return '0x%x' % self.type
+ return ev_type_name[self.type]
+
+
+ def get_source(self):
+ return "%s[%d]" % (self.stream.devType, self.stream.devIndex)
+
+
+ def __str__(self):
+ """
+ Uses the stream to give the device type and whether it is currently grabbed.
+ """
+ grabbed = "grabbed" if self.stream.grabbed else "ungrabbed"
+
+ return "Event %s %s @%f: %s 0x%x=0x%x" % (
+ self.get_source(), grabbed, self.time, self.get_type_name(), self.code,
+ self.value)
+
+
+ def __repr__(self):
+ return "Event(%s, %f, 0x%x, 0x%x, 0x%x)" % (
+ repr(self.stream), self.time, self.type, self.code, self.value)
+
+
+ def encode(self):
+ """
+ Encode this event into a Linux input event structure.
+
+ The output is packed into a string. It is unlikely that this function
+ will be required, but it might as well be here.
+ """
+ tint = long(self.time)
+ tfrac = long((self.time - tint) * 1000000)
+
+ return \
+ struct.pack(Format.Event, tsec, tfrac, self.type, self.code, self.value)
+
+
+ def decode(self, s):
+ """
+ Decode a Linux input event into the fields of this object.
+
+ Arguments:
+ *s*
+ A binary structure packed into a string.
+ """
+ tsec, tfrac, self.type, self.code, self.value = \
+ struct.unpack(Format.Event, s)
+
+ self.time = tsec + tfrac / 1000000.0
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from inevent.Constants import *
+from inevent.EventStream import EventStream
+
+
+class EventHandler(object):
+ """
+ A class to handle events.
+
+ Four types of events are handled: REL (mouse movement), KEY (keybaord keys and
+ other device buttons), ABS (joysticks and gamepad analogue sticks) and SYN
+ (delimits simultaneous events such as mouse movements)
+ """
+ def __init__(self):
+ self.buttons = dict()
+
+
+ def event(self, event, handler):
+ """
+ Handles the given event.
+
+ If the event is passed to a handler or otherwise handled then returns None,
+ else returns the event. All handlers are optional.
+
+ All key events are handled by putting them in the self.buttons dict, and
+ optionally by calling the supplied handler.
+
+ REL X, Y and wheel V and H events are all accumulated internally and
+ also optionally passed to the supplied handler. All these events are
+ handled.
+
+ ABS X, Y, Z, RX, RY, RZ, Hat0X, Hat0Y are all accumulated internally and
+ also optionally passed to the supplied handler. Other ABS events are not
+ handled.
+
+ All SYN events are passed to the supplied handler.
+
+ There are several ABS events that we do not handle. In particular:
+ THROTTLE, RUDDER, WHEEL, GAS, BRAKE, HAT1, HAT2, HAT3, PRESSURE,
+ DISTANCE, TILT, TOOL_WIDTH. Implementing these is left as an exercise
+ for the interested reader.
+
+ Likewise, since one handler is handling all events for all devices, we
+ may get the situation where two devices return the same button. The only
+ way to handle that would seem to be to have a key dict for every device,
+ which seems needlessly profligate for a situation that may never arise.
+ """
+
+ state = event.stream.state
+
+ if event.type == EV_KEY: self.buttons[event.code] = event.value
+ elif event.type == EV_REL: state.rel[event.code] += event.value
+ elif event.type == EV_ABS:
+ state.abs[event.code] = event.stream.scale(event.code, event.value)
+
+ if handler: handler(event, state)
+
+
+ def key_state(self, code):
+ """
+ Returns the last event value for the given key code.
+
+ Key names can be converted to key codes using codeOf[str].
+ If the key is pressed the returned value will be 1 (pressed) or 2 (held).
+ If the key is not pressed, the returned value will be 0.
+ """
+ return self.buttons.get(code, 0)
+
+
+ def clear_key(self, code):
+ """
+ Clears the event value for the given key code.
+
+ Key names can be converted to key codes using codeOf[str].
+ This emulates a key-up but does not generate any events.
+ """
+ self.buttons[code] = 0
+
+
+ def get_keys(self):
+ """
+ Returns the first of whichever keys have been pressed.
+
+ Key names can be converted to key codes using codeOf[str].
+ This emulates a key-up but does not generate any events.
+ """
+ k_list = []
+
+ for k in self.buttons:
+ if self.buttons[k] != 0: k_list.append(k)
+
+ return k_list
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from inevent.Constants import *
+
+
+class EventState:
+ def __init__(self):
+ self.abs = [0.0] * ABS_MAX
+ self.rel = [0.0] * REL_MAX
+
+
+ def __str__(self):
+ return ("({:6.3f}, {:6.3f}, {:6.3f}) ".format(*self.get_joystick3d()) +
+ "({:6.3f}, {:6.3f}, {:6.3f}) ".format(*self.get_joystickR3d()) +
+ "({:2.0f}, {:2.0f}) ".format(*self.get_hat()) +
+ "({:d}, {:d}) ".format(*self.get_mouse()) +
+ "({:d}, {:d})".format(*self.get_wheel()))
+
+
+ def get_joystick(self):
+ """
+ Returns the x,y coordinates for a joystick or left gamepad analogue stick.
+
+ The values are returned as a tuple. All values are -1.0 to +1.0 with
+ 0.0 being centred.
+ """
+ return self.abs[ABS_X], self.abs[ABS_Y]
+
+
+ def get_joystick3d(self):
+ """
+ Returns the x,y,z coordinates for a joystick or left gamepad analogue stick
+
+ The values are returned as a tuple. All values are -1.0 to +1.0 with
+ 0.0 being centred.
+ """
+ return self.abs[ABS_X], self.abs[ABS_Y], self.abs[ABS_Z]
+
+
+ def get_joystickR(self):
+ """
+ Returns the x,y coordinates for a right gamepad analogue stick.
+
+ The values are returned as a tuple. For some odd reason, the gamepad
+ returns values in the Z axes of both joysticks, with y being the first.
+
+ All values are -1.0 to +1.0 with 0.0 being centred.
+ """
+ return self.abs[ABS_RZ], self.abs[ABS_Z]
+
+
+ def get_joystickR3d(self):
+ """
+ Returns the x,y,z coordinates for a 2nd joystick control
+
+ The values are returned as a tuple. All values are -1.0 to +1.0 with
+ 0.0 being centred.
+ """
+ return self.abs[ABS_RX], self.abs[ABS_RY], self.abs[ABS_RZ]
+
+
+ def get_hat(self):
+ """
+ Returns the x,y coordinates for a joystick hat or gamepad direction pad
+
+ The values are returned as a tuple. All values are -1.0 to +1.0 with
+ 0.0 being centred.
+ """
+ return self.abs[ABS_HAT0X], self.abs[ABS_HAT0Y]
+
+
+ def get_mouse(self):
+ return self.rel[REL_X], self.rel[REL_Y]
+
+
+ def get_wheel(self):
+ return self.rel[REL_WHEEL], self.rel[REL_HWHEEL]
+
+
+ def get_mouse_movement(self):
+ """
+ Returns the accumulated REL (mouse or other relative device) movements
+ since the last call.
+
+ The returned value is a tuple: (X, Y, WHEEL, H-WHEEL)
+ """
+ ret = self.get_mouse() + self.get_wheel()
+
+ self.rel[REL_X] = self.rel[REL_Y] = 0
+ self.rel[REL_WHEEL] = self.rel[REL_HWHEEL] = 0
+
+ return ret
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import fcntl
+import os
+import select
+
+from inevent.Constants import *
+from inevent import ioctl
+from inevent.AbsAxisScaling import AbsAxisScaling
+from inevent import Event
+from inevent import Format
+from inevent.EventState import EventState
+
+
+EVIOCGRAB = ioctl._IOW(ord('E'), 0x90, "i") # Grab/Release device
+
+
+
+class EventStream(object):
+ """
+ encapsulates the event* file handling
+
+ Each device is represented by a file in /dev/input called eventN, where N is
+ a small number. (Actually, a keybaord can be represented by two such files.)
+ Instances of this class open one of these files and provide means to read
+ events from them.
+
+ Class methods also exist to read from multiple files simultaneously, and
+ also to grab and ungrab all instances of a given type.
+ """
+ axisX = 0
+ axisY = 1
+ axisZ = 2
+ axisRX = 3
+ axisRY = 4
+ axisRZ = 5
+ axisHat0X = 6
+ axisHat0Y = 7
+ axisHat1X = 8
+ axisHat1Y = 9
+ axisHat2X = 10
+ axisHat2Y = 11
+ axisHat3X = 12
+ axisHat3Y = 13
+ axisThrottle = 14
+ axisRudder = 15
+ axisWheel = 16
+ axisGas = 17
+ axisBrake = 18
+ axisPressure = 19
+ axisDistance = 20
+ axisTiltX = 21
+ axisTiltY = 22
+ axisToolWidth = 23
+ numAxes = 24
+
+ axisToEvent = [
+ ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ, ABS_HAT0X, ABS_HAT0Y,
+ ABS_HAT1X, ABS_HAT1Y, ABS_HAT2X, ABS_HAT2Y, ABS_HAT3X, ABS_HAT3Y,
+ ABS_THROTTLE, ABS_RUDDER, ABS_WHEEL, ABS_GAS, ABS_BRAKE, ABS_PRESSURE,
+ ABS_DISTANCE, ABS_TILT_X, ABS_TILT_Y, ABS_TOOL_WIDTH]
+
+
+ def __init__(self, devIndex, devType):
+ """
+ Opens the given /dev/input/event file and grabs it.
+
+ Also adds it to a class-global list of all existing streams.
+ """
+ self.devIndex = devIndex
+ self.devType = devType
+ self.filename = "/dev/input/event" + str(devIndex)
+ self.filehandle = os.open(self.filename, os.O_RDWR)
+ self.state = EventState()
+ self.grab(True)
+ self.absInfo = [None] * ABS_MAX
+
+ if devType == "js":
+ for axis in range(ABS_MAX):
+ self.absInfo[axis] = AbsAxisScaling(self, axis)
+
+
+ def scale(self, axis, value):
+ """
+ Scale the given value according to the given axis.
+
+ acquire_abs_info must have been previously called to acquire the data to
+ do the scaling.
+ """
+ assert axis < ABS_MAX, "Axis number out of range"
+
+ if self.absInfo[axis]: return self.absInfo[axis].scale(value)
+ else: return value
+
+
+ def grab(self, grab = True):
+ """
+ Grab (or release) exclusive access to all devices of the given type.
+
+ The devices are grabbed if grab is True and released if grab is False.
+
+ All devices are grabbed to begin with. We might want to ungrab the
+ keyboard for example to use it for text entry. While not grabbed, all
+ key-down and key-hold events are filtered out.
+ """
+ fcntl.ioctl(self.filehandle, EVIOCGRAB, 1 if grab else 0)
+ self.grabbed = grab
+
+
+ def __iter__(self):
+ """s
+ Required to make this class an iterator
+ """
+ return self
+
+
+ def next(self):
+ """
+ Returns the next waiting event.
+
+ If no event is waiting, returns None.
+ """
+ ready = select.select([self.filehandle], [], [], 0)[0]
+ if ready: return self.read()
+
+
+ def read(self):
+ """
+ Read and return the next waiting event.
+ """
+ try:
+ s = os.read(self.filehandle, Format.EventSize)
+ if s:
+ event = Event.Event(self)
+ event.decode(s)
+ return event
+
+ except: pass
+
+
+ def __enter__(self): return self
+
+
+ def release(self):
+ "Ungrabs the file and closes it."
+
+ try:
+ self.grab(False)
+ os.close(self.filehandle)
+
+ except:
+ pass
+
+
+ def __exit__(self, type, value, traceback):
+ "Ungrabs the file and closes it."
+
+ self.release()
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import re
+from inevent.Constants import *
+
+
+def test_bit(nlst, b):
+ index = b / 32
+ bit = b % 32
+ return index < len(nlst) and nlst[index] & (1 << bit)
+
+
+def EvToStr(events):
+ s = []
+
+ if test_bit(events, EV_SYN): s.append("EV_SYN")
+ if test_bit(events, EV_KEY): s.append("EV_KEY")
+ if test_bit(events, EV_REL): s.append("EV_REL")
+ if test_bit(events, EV_ABS): s.append("EV_ABS")
+ if test_bit(events, EV_MSC): s.append("EV_MSC")
+ if test_bit(events, EV_LED): s.append("EV_LED")
+ if test_bit(events, EV_SND): s.append("EV_SND")
+ if test_bit(events, EV_REP): s.append("EV_REP")
+ 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
+
+
+class DeviceCapabilities(object):
+ def __init__(self, firstLine, filehandle):
+ self.EV_SYNevents = []
+ self.EV_KEYevents = []
+ self.EV_RELevents = []
+ self.EV_ABSevents = []
+ self.EV_MSCevents = []
+ self.EV_LEDevents = []
+ self.EV_SNDevents = []
+ self.EV_REPevents = []
+ self.EV_FFevents = []
+ 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)
+ self.bus = 0
+ self.vendor = 0
+ self.product = 0
+ self.version = 0
+
+ else:
+ self.bus = int(match.group(1), base = 16)
+ self.vendor = int(match.group(2), base = 16)
+ self.product = int(match.group(3), base = 16)
+ self.version = int(match.group(4), base = 16)
+
+ for line in filehandle:
+ if not line.strip(): break
+
+ if line[0] == "N":
+ match = re.search('Name="([^"]+)"', line)
+ if match: self.name = match.group(1)
+ else: self.name = "UNKNOWN"
+
+ elif line[0] == "P":
+ match = re.search('Phys=(.+)', line)
+ if match: self.phys = match.group(1)
+ else: self.phys = "UNKNOWN"
+
+ elif line[0] == "S":
+ match = re.search('Sysfs=(.+)', line)
+ if match: self.sysfs = match.group(1)
+ else: self.sysfs = "UNKNOWN"
+
+ elif line[0] == "U":
+ match = re.search('Uniq=(.*)', line)
+ if match: self.uniq = match.group(1)
+ else: self.uniq = "UNKNOWN"
+
+ elif line[0] == "H":
+ match = re.search('Handlers=(.+)', line)
+ if match: self.handlers = match.group(1).split()
+ else: self.handlers = []
+
+ elif line[:5] == "B: EV":
+ eventsNums = [int(x, base = 16) for x in line[6:].split()]
+ eventsNums.reverse()
+ self.eventTypes = eventsNums
+
+ elif line[:6] == "B: KEY":
+ eventsNums = [int(x, base = 16) for x in line[7:].split()]
+ eventsNums.reverse()
+ self.EV_KEYevents = eventsNums
+
+ elif line[:6] == "B: ABS":
+ eventsNums = [int(x, base = 16) for x in line[7:].split()]
+ eventsNums.reverse()
+ self.EV_ABSevents = eventsNums
+
+ elif line[:6] == "B: MSC":
+ eventsNums = [int(x, base = 16) for x in line[7:].split()]
+ eventsNums.reverse()
+ self.EV_MSCevents = eventsNums
+
+ elif line[:6] == "B: REL":
+ eventsNums = [int(x, base = 16) for x in line[7:].split()]
+ eventsNums.reverse()
+ self.EV_RELevents = eventsNums
+
+ elif line[:6] == "B: LED":
+ eventsNums = [int(x, base = 16) for x in line[7:].split()]
+ eventsNums.reverse()
+ self.EV_LEDevents = eventsNums
+
+ for handler in self.handlers:
+ if handler[:5] == "event": self.eventIndex = int(handler[5:])
+
+ self.isMouse = False
+ self.isKeyboard = False
+ self.isJoystick = False
+
+
+ def doesProduce(self, eventType, eventCode):
+ return test_bit(self.eventTypes, eventType) and (
+ (eventType == EV_SYN and test_bit(self.EV_SYNevents, eventCode)) or
+ (eventType == EV_KEY and test_bit(self.EV_KEYevents, eventCode)) or
+ (eventType == EV_REL and test_bit(self.EV_RELevents, eventCode)) or
+ (eventType == EV_ABS and test_bit(self.EV_ABSevents, eventCode)) or
+ (eventType == EV_MSC and test_bit(self.EV_MSCevents, eventCode)) or
+ (eventType == EV_LED and test_bit(self.EV_LEDevents, eventCode)) or
+ (eventType == EV_SND and test_bit(self.EV_SNDevents, eventCode)) or
+ (eventType == EV_REP and test_bit(self.EV_REPevents, eventCode)) or
+ (eventType == EV_FF and test_bit(self.EV_FFevents, eventCode)) or
+ (eventType == EV_PWR and test_bit(self.EV_PWRevents, eventCode)) or
+ (eventType == EV_FF_STATUS and
+ test_bit(self.EV_FF_STATUSevents, eventCode)))
+
+
+ def __str__(self):
+ return (
+ "%s\n"
+ "Bus: %s Vendor: %s Product: %s Version: %s\n"
+ "Phys: %s\n"
+ "Sysfs: %s\n"
+ "Uniq: %s\n"
+ "Handlers: %s Event Index: %s\n"
+ "Keyboard: %s Mouse: %s Joystick: %s\n"
+ "Events: %s" % (
+ self.name, self.bus. self.vendor, self.product, self.version, self.phys,
+ self.sysfs, self.uniq, self.handlers, self.eventIndex, self.isKeyboard,
+ self.isMouse, self.isJoystick, EvToStr(self.eventTypes)))
+
+
+deviceCapabilities = []
+
+
+def get_devices(filename = "/proc/bus/input/devices"):
+ global deviceCapabilities
+
+ with open("/proc/bus/input/devices", "r") as filehandle:
+ for line in filehandle:
+ if line[0] == "I":
+ deviceCapabilities.append(DeviceCapabilities(line, filehandle))
+
+ return deviceCapabilities
+
+
+def print_devices():
+ devs = get_devices()
+
+ for dev in devs:
+ print(str(dev))
+ print(" ABS: {}"
+ .format([x for x in range(64) if test_bit(dev.EV_ABSevents, x)]))
+ print(" REL: {}"
+ .format([x for x in range(64) if test_bit(dev.EV_RELevents, x)]))
+ print(" MSC: {}"
+ .format([x for x in range(64) if test_bit(dev.EV_MSCevents, x)]))
+ print(" KEY: {}"
+ .format([x for x in range(512) if test_bit(dev.EV_KEYevents, x)]))
+ print(" LED: {}"
+ .format([x for x in range(64) if test_bit(dev.EV_LEDevents, x)]))
+ print()
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import struct
+
+Event = 'llHHi'
+EventSize = struct.calcsize(Event)
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import pyudev
+import re
+import select
+import errno
+
+from inevent.EventHandler import EventHandler
+from inevent import Keys
+from inevent.Constants import *
+from inevent.EventStream import EventStream
+
+
+_KEYS = (k for k in vars(Keys) if not k.startswith('_'))
+KEY_CODE = dict((k, getattr(Keys, k)) for k in _KEYS)
+CODE_KEY = {}
+for v in KEY_CODE: CODE_KEY[KEY_CODE[v]] = v
+
+
+def key_to_code(key):
+ return KEY_CODE.get(str(key), -1) \
+ if isinstance(key, str) else key
+
+
+def code_to_key(code): return CODE_KEY.get(code, '')
+
+
+def find_devices(types):
+ """Finds the event indices of all devices of the specified types.
+
+ A type is a string on the handlers line of /proc/bus/input/devices.
+ Keyboards use "kbd", mice use "mouse" and joysticks (and gamepads) use "js".
+
+ Returns a list of integer indexes N, where /dev/input/eventN is the event
+ stream for each device.
+
+ If butNot is given it holds a list of tuples which the returned values should
+ not match.
+
+ All devices of each type are returned; if you have two mice, they will both
+ be used.
+ """
+ with open("/proc/bus/input/devices", "r") as filehandle:
+ for line in filehandle:
+ if line[0] == "H":
+ for type in types:
+ if type in line:
+ match = re.search("event([0-9]+)", line)
+ index = match and match.group(1)
+ if index: yield int(index), type
+ break
+
+
+
+class InEvent(object):
+ """Encapsulates the entire InEvent subsystem.
+
+ This is generally all you need to import.
+
+ On instantiation, we open all devices that are keyboards, mice or joysticks.
+ That means we might have two of one sort of another, and that might be a
+ problem, but it would be rather rare.
+
+ There are several ABS (joystick, touch) events that we do not handle,
+ specifically THROTTLE, RUDDER, WHEEL, GAS, BRAKE, HAT1, HAT2, HAT3, PRESSURE,
+ DISTANCE, TILT, TOOL_WIDTH. Implementing these is left as an exercise
+ for the interested reader. Similarly, we make no attempt to handle
+ multi-touch.
+
+ Handlers can be supplied, in which case they are called for each event, but
+ it isn't necessary; API exists for all the events.
+
+ The handler signature is:
+
+ def handler_func(event, state)
+
+ where:
+ event:
+ An Event object describing the event.
+
+ state:
+ An EventState object describing the current state.
+
+ Use key_to_code() to convert from the name of a key to its code,
+ and code_to_key() to convert a code to a name.
+
+ 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"]):
+ self.streams = []
+ self.handler = EventHandler()
+ self.types = types
+
+ devs = list(find_devices(types))
+ 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()
+
+
+ def process_udev_event(self):
+ action, device = self.udevMon.receive_device()
+
+ match = re.search(r"/dev/input/event([0-9]+)", str(device.device_node))
+ devIndex = match and match.group(1)
+ if not devIndex: return
+ devIndex = int(devIndex)
+
+ if action == 'add':
+ for index, devType in find_devices(self.types):
+ if index == devIndex:
+ self.add_stream(devIndex, devType)
+ break
+
+ if action == 'remove':
+ 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]
+
+ # 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 add_stream(self, devIndex, devType):
+ try:
+ self.streams.append(EventStream(devIndex, devType))
+ print('Added {}[{:d}]'.format(devType, devIndex))
+
+ except OSError as e:
+ if not e.errno in [errno.EPERM, errno.EACCES]: 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
+
+
+ def key_state(self, key):
+ """
+ Returns the state of the given key.
+
+ The returned value will be 0 for key-up, or 1 for key-down. This method
+ returns a key-held(2) as 1 to aid in using the returned value as a
+ movement distance.
+
+ This function accepts either the key code or the string name of the key.
+ It would be more efficient to look-up and store the code of
+ the key with KEY_CODE[], rather than using the string every time. (Which
+ involves a dict look-up keyed with a string for every key_state call, every
+ time around the loop.)
+
+ Gamepad keys are:
+ Select = BTN_BASE3, Start = BTN_BASE4
+ L1 = BTN_TOP R1 = BTN_BASE
+ L2 = BTN_PINKIE R2 = BTN_BASE2
+
+ The action buttons are:
+ BTN_THUMB
+ BTN_TRIGGER
+ BTN_TOP
+ BTN_THUMB2
+
+ Analogue Left Button = BTN_BASE5
+ Analogue Right Button = BTN_BASE6
+
+ Some of those may clash with extended mouse buttons, so if you are using
+ both at once, you'll see some overlap.
+
+ The direction pad is hat0 (see get_hat)
+ """
+ return self.handler.key_state(key_to_code(key))
+
+
+ def clear_key(self, key):
+ """
+ Clears the state of the given key.
+
+ Emulates a key-up, but does not call any handlers.
+ """
+ return self.handler.clear_key(key_to_code(key))
+
+
+ def get_keys(self):
+ return [code_to_key(k) for k in self.handler.get_keys()]
+
+
+ def release(self):
+ """
+ Ungrabs all streams and closes all files.
+
+ Only do this when you're finished with this object. You can't use it again.
+ """
+ for s in self.streams: s.release()
+
--- /dev/null
+from inevent.Constants import *
+
+
+def axes_to_string(axes):
+ return "({:6.3f}, {:6.3f}, {:6.3f}, {:6.3f})".format(*axes)
+
+
+def print_event(event, state):
+ print("{} {}: ".format(event.get_source(), event.get_type_name()), end = '')
+
+ 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()))
+
+ if event.type == EV_REL:
+ print("({: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))
+
+
+class JogHandler:
+ def __init__(self, config):
+ self.config = config
+ self.axes = [0.0, 0.0, 0.0, 0.0]
+ self.speed = 3
+ self.activate = 0
+
+
+ def changed(self):
+ print(axes_to_string(self.axes) + " x {:d}".format(self.speed))
+
+
+ def __call__(self, event, state):
+ if event.type not in [EV_ABS, EV_REL, EV_KEY]: return
+
+ changed = False
+
+ # Process event
+ if event.type == EV_ABS and event.code in self.config['axes']:
+ pass
+
+ elif event.type == EV_ABS and event.code in self.config['arrows']:
+ axis = self.config['arrows'].index(event.code)
+
+ if event.value < 0:
+ if axis == 1: print('up')
+ else: print('left')
+
+ elif 0 < event.value:
+ if axis == 1: print('down')
+ else: print('right')
+
+ elif event.type == EV_KEY and event.code in self.config['speed']:
+ old_speed = self.speed
+ self.speed = self.config['speed'].index(event.code) + 1
+ if self.speed != old_speed: changed = True
+
+ elif event.type == EV_KEY and event.code in self.config['activate']:
+ index = self.config['activate'].index(event.code)
+
+ if event.value: self.activate |= 1 << index
+ else: self.activate &= ~(1 << index)
+
+ if self.config.get('verbose', False): print_event(event, state)
+
+ # Update axes
+ old_axes = list(self.axes)
+
+ for axis in range(4):
+ self.axes[axis] = event.stream.state.abs[self.config['axes'][axis]]
+ if abs(self.axes[axis]) < self.config['deadband']:
+ self.axes[axis] = 0
+ if not (1 << axis) & self.activate and self.activate:
+ self.axes[axis] = 0
+
+ if old_axes != self.axes: changed = True
+
+ if changed: self.changed()
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+KEY_ESC = 1
+KEY_1 = 2
+KEY_2 = 3
+KEY_3 = 4
+KEY_4 = 5
+KEY_5 = 6
+KEY_6 = 7
+KEY_7 = 8
+KEY_8 = 9
+KEY_9 = 10
+KEY_0 = 11
+KEY_MINUS = 12
+KEY_EQUAL = 13
+KEY_BACKSPACE = 14
+KEY_TAB = 15
+KEY_Q = 16
+KEY_W = 17
+KEY_E = 18
+KEY_R = 19
+KEY_T = 20
+KEY_Y = 21
+KEY_U = 22
+KEY_I = 23
+KEY_O = 24
+KEY_P = 25
+KEY_LEFTBRACE = 26
+KEY_RIGHTBRACE = 27
+KEY_ENTER = 28
+KEY_LEFTCTRL = 29
+KEY_A = 30
+KEY_S = 31
+KEY_D = 32
+KEY_F = 33
+KEY_G = 34
+KEY_H = 35
+KEY_J = 36
+KEY_K = 37
+KEY_L = 38
+KEY_SEMICOLON = 39
+KEY_APOSTROPHE = 40
+KEY_GRAVE = 41
+KEY_LEFTSHIFT = 42
+KEY_BACKSLASH = 43
+KEY_Z = 44
+KEY_X = 45
+KEY_C = 46
+KEY_V = 47
+KEY_B = 48
+KEY_N = 49
+KEY_M = 50
+KEY_COMMA = 51
+KEY_DOT = 52
+KEY_SLASH = 53
+KEY_RIGHTSHIFT = 54
+KEY_KPASTERISK = 55
+KEY_LEFTALT = 56
+KEY_SPACE = 57
+KEY_CAPSLOCK = 58
+KEY_F1 = 59
+KEY_F2 = 60
+KEY_F3 = 61
+KEY_F4 = 62
+KEY_F5 = 63
+KEY_F6 = 64
+KEY_F7 = 65
+KEY_F8 = 66
+KEY_F9 = 67
+KEY_F10 = 68
+KEY_NUMLOCK = 69
+KEY_SCROLLLOCK = 70
+KEY_KP7 = 71
+KEY_KP8 = 72
+KEY_KP9 = 73
+KEY_KPMINUS = 74
+KEY_KP4 = 75
+KEY_KP5 = 76
+KEY_KP6 = 77
+KEY_KPPLUS = 78
+KEY_KP1 = 79
+KEY_KP2 = 80
+KEY_KP3 = 81
+KEY_KP0 = 82
+KEY_KPDOT = 83
+
+KEY_ZENKAKUHANKAKU = 85
+KEY_102ND = 86
+KEY_F11 = 87
+KEY_F12 = 88
+KEY_RO = 89
+KEY_KATAKANA = 90
+KEY_HIRAGANA = 91
+KEY_HENKAN = 92
+KEY_KATAKANAHIRAGANA = 93
+KEY_MUHENKAN = 94
+KEY_KPJPCOMMA = 95
+KEY_KPENTER = 96
+KEY_RIGHTCTRL = 97
+KEY_KPSLASH = 98
+KEY_SYSRQ = 99
+KEY_RIGHTALT = 100
+KEY_LINEFEED = 101
+KEY_HOME = 102
+KEY_UP = 103
+KEY_PAGEUP = 104
+KEY_LEFT = 105
+KEY_RIGHT = 106
+KEY_END = 107
+KEY_DOWN = 108
+KEY_PAGEDOWN = 109
+KEY_INSERT = 110
+KEY_DELETE = 111
+KEY_MACRO = 112
+KEY_MUTE = 113
+KEY_VOLUMEDOWN = 114
+KEY_VOLUMEUP = 115
+KEY_POWER = 116
+KEY_KPEQUAL = 117
+KEY_KPPLUSMINUS = 118
+KEY_PAUSE = 119
+
+KEY_KPCOMMA = 121
+KEY_HANGUEL = 122
+KEY_HANJA = 123
+KEY_YEN = 124
+KEY_LEFTMETA = 125
+KEY_RIGHTMETA = 126
+KEY_COMPOSE = 127
+
+KEY_STOP = 128
+KEY_AGAIN = 129
+KEY_PROPS = 130
+KEY_UNDO = 131
+KEY_FRONT = 132
+KEY_COPY = 133
+KEY_OPEN = 134
+KEY_PASTE = 135
+KEY_FIND = 136
+KEY_CUT = 137
+KEY_HELP = 138
+KEY_MENU = 139
+KEY_CALC = 140
+KEY_SETUP = 141
+KEY_SLEEP = 142
+KEY_WAKEUP = 143
+KEY_FILE = 144
+KEY_SENDFILE = 145
+KEY_DELETEFILE = 146
+KEY_XFER = 147
+KEY_PROG1 = 148
+KEY_PROG2 = 149
+KEY_WWW = 150
+KEY_MSDOS = 151
+KEY_COFFEE = 152
+KEY_DIRECTION = 153
+KEY_CYCLEWINDOWS = 154
+KEY_MAIL = 155
+KEY_BOOKMARKS = 156
+KEY_COMPUTER = 157
+KEY_BACK = 158
+KEY_FORWARD = 159
+KEY_CLOSECD = 160
+KEY_EJECTCD = 161
+KEY_EJECTCLOSECD = 162
+KEY_NEXTSONG = 163
+KEY_PLAYPAUSE = 164
+KEY_PREVIOUSSONG = 165
+KEY_STOPCD = 166
+KEY_RECORD = 167
+KEY_REWIND = 168
+KEY_PHONE = 169
+KEY_ISO = 170
+KEY_CONFIG = 171
+KEY_HOMEPAGE = 172
+KEY_REFRESH = 173
+KEY_EXIT = 174
+KEY_MOVE = 175
+KEY_EDIT = 176
+KEY_SCROLLUP = 177
+KEY_SCROLLDOWN = 178
+KEY_KPLEFTPAREN = 179
+KEY_KPRIGHTPAREN = 180
+
+KEY_F13 = 183
+KEY_F14 = 184
+KEY_F15 = 185
+KEY_F16 = 186
+KEY_F17 = 187
+KEY_F18 = 188
+KEY_F19 = 189
+KEY_F20 = 190
+KEY_F21 = 191
+KEY_F22 = 192
+KEY_F23 = 193
+KEY_F24 = 194
+
+KEY_PLAYCD = 200
+KEY_PAUSECD = 201
+KEY_PROG3 = 202
+KEY_PROG4 = 203
+KEY_SUSPEND = 205
+KEY_CLOSE = 206
+KEY_PLAY = 207
+KEY_FASTFORWARD = 208
+KEY_BASSBOOST = 209
+KEY_PRINT = 210
+KEY_HP = 211
+KEY_CAMERA = 212
+KEY_SOUND = 213
+KEY_QUESTION = 214
+KEY_EMAIL = 215
+KEY_CHAT = 216
+KEY_SEARCH = 217
+KEY_CONNECT = 218
+KEY_FINANCE = 219
+KEY_SPORT = 220
+KEY_SHOP = 221
+KEY_ALTERASE = 222
+KEY_CANCEL = 223
+KEY_BRIGHTNESSDOWN = 224
+KEY_BRIGHTNESSUP = 225
+KEY_MEDIA = 226
+
+KEY_UNKNOWN = 240
+
+BTN_MISC = 0x100
+BTN_0 = 0x100
+BTN_1 = 0x101
+BTN_2 = 0x102
+BTN_3 = 0x103
+BTN_4 = 0x104
+BTN_5 = 0x105
+BTN_6 = 0x106
+BTN_7 = 0x107
+BTN_8 = 0x108
+BTN_9 = 0x109
+
+BTN_MOUSE = 0x110
+BTN_LEFT = 0x110
+BTN_RIGHT = 0x111
+BTN_MIDDLE = 0x112
+BTN_SIDE = 0x113
+BTN_EXTRA = 0x114
+BTN_FORWARD = 0x115
+BTN_BACK = 0x116
+BTN_TASK = 0x117
+
+BTN_JOYSTICK = 0x120
+BTN_TRIGGER = 0x120
+BTN_THUMB = 0x121
+BTN_THUMB2 = 0x122
+BTN_TOP = 0x123
+BTN_TOP2 = 0x124
+BTN_PINKIE = 0x125
+BTN_BASE = 0x126
+BTN_BASE2 = 0x127
+BTN_BASE3 = 0x128
+BTN_BASE4 = 0x129
+BTN_BASE5 = 0x12a
+BTN_BASE6 = 0x12b
+BTN_DEAD = 0x12f
+
+BTN_GAMEPAD = 0x130
+BTN_A = 0x130
+BTN_B = 0x131
+BTN_C = 0x132
+BTN_X = 0x133
+BTN_Y = 0x134
+BTN_Z = 0x135
+BTN_TL = 0x136
+BTN_TR = 0x137
+BTN_TL2 = 0x138
+BTN_TR2 = 0x139
+BTN_SELECT = 0x13a
+BTN_START = 0x13b
+BTN_MODE = 0x13c
+BTN_THUMBL = 0x13d
+BTN_THUMBR = 0x13e
+
+BTN_DIGI = 0x140
+BTN_TOOL_PEN = 0x140
+BTN_TOOL_RUBBER = 0x141
+BTN_TOOL_BRUSH = 0x142
+BTN_TOOL_PENCIL = 0x143
+BTN_TOOL_AIRBRUSH = 0x144
+BTN_TOOL_FINGER = 0x145
+BTN_TOOL_MOUSE = 0x146
+BTN_TOOL_LENS = 0x147
+BTN_TOUCH = 0x14a
+BTN_STYLUS = 0x14b
+BTN_STYLUS2 = 0x14c
+BTN_TOOL_DOUBLETAP = 0x14d
+BTN_TOOL_TRIPLETAP = 0x14e
+
+BTN_WHEEL = 0x150
+BTN_GEAR_DOWN = 0x150
+BTN_GEAR_UP = 0x151
+
+KEY_OK = 0x160
+KEY_SELECT = 0x161
+KEY_GOTO = 0x162
+KEY_CLEAR = 0x163
+KEY_POWER2 = 0x164
+KEY_OPTION = 0x165
+KEY_INFO = 0x166
+KEY_TIME = 0x167
+KEY_VENDOR = 0x168
+KEY_ARCHIVE = 0x169
+KEY_PROGRAM = 0x16a
+KEY_CHANNEL = 0x16b
+KEY_FAVORITES = 0x16c
+KEY_EPG = 0x16d
+KEY_PVR = 0x16e
+KEY_MHP = 0x16f
+KEY_LANGUAGE = 0x170
+KEY_TITLE = 0x171
+KEY_SUBTITLE = 0x172
+KEY_ANGLE = 0x173
+KEY_ZOOM = 0x174
+KEY_MODE = 0x175
+KEY_KEYBOARD = 0x176
+KEY_SCREEN = 0x177
+KEY_PC = 0x178
+KEY_TV = 0x179
+KEY_TV2 = 0x17a
+KEY_VCR = 0x17b
+KEY_VCR2 = 0x17c
+KEY_SAT = 0x17d
+KEY_SAT2 = 0x17e
+KEY_CD = 0x17f
+KEY_TAPE = 0x180
+KEY_RADIO = 0x181
+KEY_TUNER = 0x182
+KEY_PLAYER = 0x183
+KEY_TEXT = 0x184
+KEY_DVD = 0x185
+KEY_AUX = 0x186
+KEY_MP3 = 0x187
+KEY_AUDIO = 0x188
+KEY_VIDEO = 0x189
+KEY_DIRECTORY = 0x18a
+KEY_LIST = 0x18b
+KEY_MEMO = 0x18c
+KEY_CALENDAR = 0x18d
+KEY_RED = 0x18e
+KEY_GREEN = 0x18f
+KEY_YELLOW = 0x190
+KEY_BLUE = 0x191
+KEY_CHANNELUP = 0x192
+KEY_CHANNELDOWN = 0x193
+KEY_FIRST = 0x194
+KEY_LAST = 0x195
+KEY_AB = 0x196
+KEY_NEXT = 0x197
+KEY_RESTART = 0x198
+KEY_SLOW = 0x199
+KEY_SHUFFLE = 0x19a
+KEY_BREAK = 0x19b
+KEY_PREVIOUS = 0x19c
+KEY_DIGITS = 0x19d
+KEY_TEEN = 0x19e
+KEY_TWEN = 0x19f
+
+KEY_DEL_EOL = 0x1c0
+KEY_DEL_EOS = 0x1c1
+KEY_INS_LINE = 0x1c2
+KEY_DEL_LINE = 0x1c3
+
+KEY_FN = 0x1d0
+KEY_FN_ESC = 0x1d1
+KEY_FN_F1 = 0x1d2
+KEY_FN_F2 = 0x1d3
+KEY_FN_F3 = 0x1d4
+KEY_FN_F4 = 0x1d5
+KEY_FN_F5 = 0x1d6
+KEY_FN_F6 = 0x1d7
+KEY_FN_F7 = 0x1d8
+KEY_FN_F8 = 0x1d9
+KEY_FN_F9 = 0x1da
+KEY_FN_F10 = 0x1db
+KEY_FN_F11 = 0x1dc
+KEY_FN_F12 = 0x1dd
+KEY_FN_1 = 0x1de
+KEY_FN_2 = 0x1df
+KEY_FN_D = 0x1e0
+KEY_FN_E = 0x1e1
+KEY_FN_F = 0x1e2
+KEY_FN_S = 0x1e3
+KEY_FN_B = 0x1e4
+
+KEY_MAX = 0x1ff
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .InEvent import InEvent
+from .JogHandler import JogHandler
--- /dev/null
+# The inevent Python module was adapted from pi3d.event from the pi3d
+# project.
+#
+# Copyright (c) 2016, Joseph Coffland, Cauldron Development LLC.
+# Copyright (c) 2015, Tim Skillman.
+# Copyright (c) 2015, Paddy Gaunt.
+# Copyright (c) 2015, Tom Ritchford.
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# IOCTL macros
+#
+# ioctl command encoding: 32 bits total, command in lower 16 bits,
+# size of the parameter structure in the lower 14 bits of the
+# upper 16 bits.
+#
+# Encoding the size of the parameter structure in the ioctl request
+# is useful for catching programs compiled with old versions
+# and to avoid overwriting user space outside the user buffer area.
+# The highest 2 bits are reserved for indicating the ``access mode''.
+# NOTE: This limits the max parameter size to 16kB - 1
+#
+# The following is for compatibility across the various Linux
+# platforms. The generic ioctl numbering scheme doesn't really enforce
+# a type field. De facto, however, the top 8 bits of the lower 16
+# bits are indeed used as a type field, so we might just as well make
+# this explicit here.
+
+import struct
+
+
+sizeof = struct.calcsize
+
+_IOC_NRBITS = 8
+_IOC_TYPEBITS = 8
+_IOC_SIZEBITS = 14
+_IOC_DIRBITS = 2
+
+_IOC_NRMASK = (1 << _IOC_NRBITS) - 1
+_IOC_TYPEMASK = (1 << _IOC_TYPEBITS) - 1
+_IOC_SIZEMASK = (1 << _IOC_SIZEBITS) - 1
+_IOC_DIRMASK = (1 << _IOC_DIRBITS) - 1
+
+_IOC_NRSHIFT = 0
+_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
+_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
+_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
+
+_IOC_NONE = 0
+_IOC_WRITE = 1
+_IOC_READ = 2
+_IOC_RW = _IOC_READ | _IOC_WRITE
+
+
+def _IOC(dir, type, nr, size):
+ return int(
+ (dir << _IOC_DIRSHIFT) |
+ (type << _IOC_TYPESHIFT) |
+ (nr << _IOC_NRSHIFT) |
+ (size << _IOC_SIZESHIFT))
+
+# encode ioctl numbers
+def _IO(type, nr): return _IOC(_IOC_NONE, type, nr, 0)
+def _IOR(type, nr, fmt): return _IOC(_IOC_READ, type, nr, sizeof(fmt))
+def _IOW(type, nr, fmt): return _IOC(_IOC_WRITE, type, nr, sizeof(fmt))
+def _IOWR(type, nr, fmt): return _IOC(_IOC_RW, type, nr, sizeof(fmt))
+def _IOR_BAD(type, nr, fmt): return _IOC(_IOC_READ, type, nr, sizeof(fmt))
+def _IOW_BAD(type, nr, fmt): return _IOC(_IOC_WRITE, type, nr, sizeof(fmt))
+def _IOWR_BAD(type, nr, fmt): return _IOC(_IOC_RW, type, nr, sizeof(fmt))
+
+# decode ioctl numbers
+def _IOC_DIR(nr): return (nr >> _IOC_DIRSHIFT) & _IOC_DIRMASK
+def _IOC_TYPE(nr): return (nr >> _IOC_TYPESHIFT) & _IOC_TYPEMASK
+def _IOC_NR(nr): return (nr >> _IOC_NRSHIFT) & _IOC_NRMASK
+def _IOC_SIZE(nr): return (nr >> _IOC_SIZESHIFT) & _IOC_SIZEMASK
+
+# for drivers/sound files
+IOC_IN = _IOC_WRITE << _IOC_DIRSHIFT
+IOC_OUT = _IOC_READ << _IOC_DIRSHIFT
+IOC_INOUT = _IOC_RW << _IOC_DIRSHIFT
+IOCSIZE_MASK = _IOC_SIZEMASK << _IOC_SIZESHIFT
+IOCSIZE_SHIFT = _IOC_SIZESHIFT
+
--- /dev/null
+#!/usr/bin/env python3
+
+try:
+ import smbus
+except:
+ import smbus2 as smbus
+
+import time
+
+
+# Control flags
+REG_SELECT_BIT = 1 << 0
+READ_BIT = 1 << 1
+ENABLE_BIT = 1 << 2
+BACKLIGHT_BIT = 1 << 3
+
+# Commands
+LCD_CLEAR_DISPLAY = 1 << 0
+LCD_RETURN_HOME = 1 << 1
+LCD_ENTRY_MODE_SET = 1 << 2
+LCD_DISPLAY_CONTROL = 1 << 3
+LCD_CURSOR_SHIFT = 1 << 4
+LCD_FUNCTION_SET = 1 << 5
+LCD_SET_CGRAM_ADDR = 1 << 6
+LCD_SET_DDRAM_ADDR = 1 << 7
+
+# Entry Mode Set flags
+LCD_ENTRY_SHIFT_DISPLAY = 1 << 0
+LCD_ENTRY_SHIFT_INC = 1 << 1
+LCD_ENTRY_SHIFT_DEC = 0 << 1
+
+# Display Control flags
+LCD_BLINK_ON = 1 << 0
+LCD_BLINK_OFF = 0 << 0
+LCD_CURSOR_ON = 1 << 1
+LCD_CURSOR_OFF = 0 << 1
+LCD_DISPLAY_ON = 1 << 2
+LCD_DISPLAY_OFF = 0 << 2
+
+# Cursor Shift flags
+LCD_SHIFT_RIGHT = 1 << 2
+LCD_SHIFT_LEFT = 0 << 2
+LCD_SHIFT_DISPLAY = 1 << 3
+LCD_SHIFT_CURSOR = 0 << 3
+
+# Function Set flags
+LCD_5x11_DOTS = 1 << 2
+LCD_5x8_DOTS = 0 << 2
+LCD_2_LINE = 1 << 3
+LCD_1_LINE = 0 << 3
+LCD_8_BIT_MODE = 1 << 4
+LCD_4_BIT_MODE = 0 << 4
+
+# Text justification flags
+JUSTIFY_LEFT = 0
+JUSTIFY_RIGHT = 1
+JUSTIFY_CENTER = 2
+
+
+
+class LCD:
+ def __init__(self, port, addr, height = 4, width = 20):
+ self.addr = addr
+ self.height = height
+ self.width = width
+
+ try:
+ self.bus = smbus.SMBus(port)
+ except FileNotFoundError as e:
+ self.bus = None
+ print('Failed to open LCD device:', e)
+
+ self.backlight = True
+
+ self.reset()
+
+
+ def reset(self):
+ self.clear()
+ self.write(LCD_FUNCTION_SET | LCD_2_LINE | LCD_5x8_DOTS |
+ LCD_4_BIT_MODE)
+ self.write(LCD_DISPLAY_CONTROL | LCD_DISPLAY_ON)
+ self.write(LCD_ENTRY_MODE_SET | LCD_ENTRY_SHIFT_INC)
+
+
+ def write_i2c(self, data):
+ if self.bus is None: return
+
+ if self.backlight: data |= BACKLIGHT_BIT
+
+ self.bus.write_byte(self.addr, data)
+ time.sleep(0.0001)
+
+
+ # Write half of a command to LCD
+ def write_nibble(self, data):
+ self.write_i2c(data)
+
+ # Strobe
+ self.write_i2c(data | ENABLE_BIT)
+ time.sleep(0.0005)
+
+ self.write_i2c(data & ~ENABLE_BIT)
+ time.sleep(0.0001)
+
+
+ # Write an 8-bit command to LCD
+ def write(self, cmd, flags = 0):
+ self.write_nibble(flags | (cmd & 0xf0))
+ self.write_nibble(flags | ((cmd << 4) & 0xf0))
+
+
+ def set_cursor(self, on, blink):
+ data = LCD_DISPLAY_CONTROL
+
+ if on: data |= LCD_CURSOR_ON
+ if blink: data |= LCD_BLINK_ON
+
+ self.write(data)
+
+
+ def set_backlight(self, enable):
+ self.backlight = enable
+ self.write_i2c(0)
+
+
+ def program_char(self, addr, data):
+ if addr < 0 or 8 <= addr: return
+
+ self.write(LCD_SET_CGRAM_ADDR | (addr << 3))
+ for x in data:
+ self.write(x, REG_SELECT_BIT)
+
+
+ def goto(self, x, y):
+ if x < 0 or self.width <= x or y < 0 or self.height <= y: return
+ self.write(LCD_SET_DDRAM_ADDR | (0, 64, 20, 84)[y] + int(x))
+
+
+ def text(self, msg):
+ for c in msg:
+ self.write(ord(c), REG_SELECT_BIT)
+
+
+ def display(self, line, msg, justify = JUSTIFY_LEFT):
+ if justify == JUSTIFY_RIGHT: x = self.width - len(msg)
+ elif justify == JUSTIFY_CENTER: x = (self.width - len(msg)) / 2
+ else: x = 0
+
+ if x < 0: x = 0
+
+ self.goto(x, line)
+ self.text(msg)
+
+
+ def shift(self, count = 1, right = True, display = True):
+ cmd = LCD_CURSOR_SHIFT
+ if right: cmd |= LCD_SHIFT_RIGHT
+ if display: cmd |= LCD_SHIFT_DISPLAY
+
+ for i in range(count): self.write(cmd)
+
+
+ # Clear LCD and move cursor home
+ def clear(self):
+ self.write(LCD_CLEAR_DISPLAY)
+ self.write(LCD_RETURN_HOME)
+
+
+
+if __name__ == "__main__":
+ lcd = LCD(1, 0x27)
+
+ lcd.clear()
+
+ lcd.program_char(0, (0b11011,
+ 0b11011,
+ 0b00000,
+ 0b01100,
+ 0b01100,
+ 0b00000,
+ 0b11011,
+ 0b11011))
+
+ lcd.program_char(1, (0b11000,
+ 0b01100,
+ 0b00110,
+ 0b00011,
+ 0b00011,
+ 0b00110,
+ 0b01100,
+ 0b11000))
+
+ lcd.program_char(2, (0b00011,
+ 0b00110,
+ 0b01100,
+ 0b11000,
+ 0b11000,
+ 0b01100,
+ 0b00110,
+ 0b00011))
+
+ lcd.display(0, '\0' * lcd.width)
+ lcd.display(1, 'Hello world!', JUSTIFY_CENTER)
+ lcd.display(2, '\1\2' * (lcd.width / 2))
+ lcd.display(3, '12345678901234567890')
--- /dev/null
+#!/usr/bin/env python3
+
+import lcd
+
+if __name__ == "__main__":
+ screen = lcd.LCD(1, 0x27)
+
+ screen.clear()
+ screen.display(0, 'Buildbotics', lcd.JUSTIFY_CENTER)
+ screen.display(1, 'Controller', lcd.JUSTIFY_CENTER)
+ screen.display(3, 'Booting...', lcd.JUSTIFY_CENTER)