From e68acf1f316d10147170f68bec53980816d476ac Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Tue, 21 Jun 2016 17:35:31 -0700 Subject: [PATCH] Packaged Python code with setuptools --- .gitignore | 3 +- Makefile | 23 ++--- README.md | 7 +- bbctrl.init.d => scripts/bbctrl.init.d | 0 jogtest.py => scripts/jogtest.py | 0 setup_rpi.sh => scripts/setup_rpi.sh | 0 scripts/svg2abs.js | 64 ++++++++++++++ setup.py | 26 ++++++ bbctrl.py => src/py/bbctrl/__init__.py | 88 ++++++++++++++----- src/py/bbctrl/http | 1 + {inevent => src/py/inevent}/AbsAxisScaling.py | 0 {inevent => src/py/inevent}/Constants.py | 0 {inevent => src/py/inevent}/Event.py | 0 {inevent => src/py/inevent}/EventHandler.py | 0 {inevent => src/py/inevent}/EventState.py | 0 {inevent => src/py/inevent}/EventStream.py | 0 {inevent => src/py/inevent}/FindDevices.py | 0 {inevent => src/py/inevent}/Format.py | 0 {inevent => src/py/inevent}/InEvent.py | 0 {inevent => src/py/inevent}/JogHandler.py | 0 {inevent => src/py/inevent}/Keys.py | 0 {inevent => src/py/inevent}/__init__.py | 0 {inevent => src/py/inevent}/ioctl.py | 0 lcd.py => src/py/lcd/__init__.py | 15 +++- splash.py => src/py/lcd/splash.py | 0 25 files changed, 187 insertions(+), 40 deletions(-) rename bbctrl.init.d => scripts/bbctrl.init.d (100%) rename jogtest.py => scripts/jogtest.py (100%) rename setup_rpi.sh => scripts/setup_rpi.sh (100%) create mode 100644 scripts/svg2abs.js create mode 100755 setup.py rename bbctrl.py => src/py/bbctrl/__init__.py (79%) create mode 120000 src/py/bbctrl/http rename {inevent => src/py/inevent}/AbsAxisScaling.py (100%) rename {inevent => src/py/inevent}/Constants.py (100%) rename {inevent => src/py/inevent}/Event.py (100%) rename {inevent => src/py/inevent}/EventHandler.py (100%) rename {inevent => src/py/inevent}/EventState.py (100%) rename {inevent => src/py/inevent}/EventStream.py (100%) rename {inevent => src/py/inevent}/FindDevices.py (100%) rename {inevent => src/py/inevent}/Format.py (100%) rename {inevent => src/py/inevent}/InEvent.py (100%) rename {inevent => src/py/inevent}/JogHandler.py (100%) rename {inevent => src/py/inevent}/Keys.py (100%) rename {inevent => src/py/inevent}/__init__.py (100%) rename {inevent => src/py/inevent}/ioctl.py (100%) rename lcd.py => src/py/lcd/__init__.py (94%) rename splash.py => src/py/lcd/splash.py (100%) diff --git a/.gitignore b/.gitignore index 70a8c44..150b38a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ .sconf_temp/ .sconsign.dblite -/http /build node_modules *~ \#* *.pyc __pycache__ +*.egg-info +/upload diff --git a/Makefile b/Makefile index 658cd52..87e7417 100644 --- a/Makefile +++ b/Makefile @@ -6,18 +6,19 @@ STYLUS := $(NODE_MODS)/stylus/bin/stylus 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 @@ -25,7 +26,7 @@ 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) @@ -37,10 +38,10 @@ umount: 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) @@ -54,7 +55,7 @@ build/hashes.jade: $(CSS_ASSETS).sha256 $(JS_ASSETS).sha256 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 $@) @@ -68,12 +69,12 @@ node_modules: 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 $@) diff --git a/README.md b/README.md index d42b74b..323cc65 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ ssh pi@ Substitute ```` 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@: +scp scripts/setup_rpi.sh pi@: ssh pi@ sudo ./setup_rpi.sh ``` @@ -120,3 +120,6 @@ You should see a prompt. ``` avrdude -c avrispmkII -p ATxmega128A3U -P usb -U flash:w:tinyg.hex:i ``` + + +## Setup Python Development diff --git a/bbctrl.init.d b/scripts/bbctrl.init.d similarity index 100% rename from bbctrl.init.d rename to scripts/bbctrl.init.d diff --git a/jogtest.py b/scripts/jogtest.py similarity index 100% rename from jogtest.py rename to scripts/jogtest.py diff --git a/setup_rpi.sh b/scripts/setup_rpi.sh similarity index 100% rename from setup_rpi.sh rename to scripts/setup_rpi.sh diff --git a/scripts/svg2abs.js b/scripts/svg2abs.js new file mode 100644 index 0000000..7cfc6dd --- /dev/null +++ b/scripts/svg2abs.js @@ -0,0 +1,64 @@ +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; + } +} diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..f0a591e --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +#!/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, + ) diff --git a/bbctrl.py b/src/py/bbctrl/__init__.py similarity index 79% rename from bbctrl.py rename to src/py/bbctrl/__init__.py index 0a4e875..25c5caf 100755 --- a/bbctrl.py +++ b/src/py/bbctrl/__init__.py @@ -1,11 +1,5 @@ #!/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 @@ -17,6 +11,9 @@ import multiprocessing import time import select import atexit +import argparse + +from pkg_resources import Requirement, resource_filename import lcd import inevent @@ -34,11 +31,6 @@ config = { "verbose": False } - -with open('http/config-template.json', 'r', encoding = 'utf-8') as f: - config_template = json.load(f) - - state = {} clients = [] @@ -46,6 +38,10 @@ 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) @@ -193,11 +189,11 @@ class FileHandler(APIHandler): class SerialProcess(multiprocessing.Process): - def __init__(self, input_queue, output_queue): + 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(SERIAL_PORT, SERIAL_BAUDRATE, timeout = 1) + self.sp = serial.Serial(port, baud, timeout = 1) self.input_queue.put('\n') @@ -319,44 +315,88 @@ def checkEvents(): eventProcessor = inevent.InEvent(types = "js kbd".split()) eventHandler = JogHandler(config) -screen = lcd.LCD(1, 0x27) - -def splash(): +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(): +def goodbye(screen): screen.clear() screen.display(1, 'Goodbye', lcd.JUSTIFY_CENTER) -if __name__ == "__main__": +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(input_queue, output_queue) + sp = SerialProcess(args.serial, args.baud, input_queue, output_queue) sp.daemon = True sp.start() except Exception as e: - print(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() - splash() - atexit.register(goodbye) + # 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) - app.listen(HTTP_PORT, address = HTTP_ADDR) - print('Listening on http://{}:{}/'.format(HTTP_ADDR, HTTP_PORT)) + + 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() diff --git a/src/py/bbctrl/http b/src/py/bbctrl/http new file mode 120000 index 0000000..de7005b --- /dev/null +++ b/src/py/bbctrl/http @@ -0,0 +1 @@ +../../../build/http/ \ No newline at end of file diff --git a/inevent/AbsAxisScaling.py b/src/py/inevent/AbsAxisScaling.py similarity index 100% rename from inevent/AbsAxisScaling.py rename to src/py/inevent/AbsAxisScaling.py diff --git a/inevent/Constants.py b/src/py/inevent/Constants.py similarity index 100% rename from inevent/Constants.py rename to src/py/inevent/Constants.py diff --git a/inevent/Event.py b/src/py/inevent/Event.py similarity index 100% rename from inevent/Event.py rename to src/py/inevent/Event.py diff --git a/inevent/EventHandler.py b/src/py/inevent/EventHandler.py similarity index 100% rename from inevent/EventHandler.py rename to src/py/inevent/EventHandler.py diff --git a/inevent/EventState.py b/src/py/inevent/EventState.py similarity index 100% rename from inevent/EventState.py rename to src/py/inevent/EventState.py diff --git a/inevent/EventStream.py b/src/py/inevent/EventStream.py similarity index 100% rename from inevent/EventStream.py rename to src/py/inevent/EventStream.py diff --git a/inevent/FindDevices.py b/src/py/inevent/FindDevices.py similarity index 100% rename from inevent/FindDevices.py rename to src/py/inevent/FindDevices.py diff --git a/inevent/Format.py b/src/py/inevent/Format.py similarity index 100% rename from inevent/Format.py rename to src/py/inevent/Format.py diff --git a/inevent/InEvent.py b/src/py/inevent/InEvent.py similarity index 100% rename from inevent/InEvent.py rename to src/py/inevent/InEvent.py diff --git a/inevent/JogHandler.py b/src/py/inevent/JogHandler.py similarity index 100% rename from inevent/JogHandler.py rename to src/py/inevent/JogHandler.py diff --git a/inevent/Keys.py b/src/py/inevent/Keys.py similarity index 100% rename from inevent/Keys.py rename to src/py/inevent/Keys.py diff --git a/inevent/__init__.py b/src/py/inevent/__init__.py similarity index 100% rename from inevent/__init__.py rename to src/py/inevent/__init__.py diff --git a/inevent/ioctl.py b/src/py/inevent/ioctl.py similarity index 100% rename from inevent/ioctl.py rename to src/py/inevent/ioctl.py diff --git a/lcd.py b/src/py/lcd/__init__.py similarity index 94% rename from lcd.py rename to src/py/lcd/__init__.py index d857766..0380705 100755 --- a/lcd.py +++ b/src/py/lcd/__init__.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 -import smbus +try: + import smbus +except: + import smbus2 as smbus + import time @@ -60,7 +64,12 @@ class LCD: self.height = height self.width = width - self.bus = smbus.SMBus(port) + 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() @@ -75,6 +84,8 @@ class LCD: def write_i2c(self, data): + if self.bus is None: return + if self.backlight: data |= BACKLIGHT_BIT self.bus.write_byte(self.addr, data) diff --git a/splash.py b/src/py/lcd/splash.py similarity index 100% rename from splash.py rename to src/py/lcd/splash.py -- 2.27.0