diff --git a/src/py/bbctrl/APIHandler.py b/src/py/bbctrl/APIHandler.py
index c21ca56..960ff8f 100644
--- a/src/py/bbctrl/APIHandler.py
+++ b/src/py/bbctrl/APIHandler.py
@@ -1,54 +1,24 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import json
-import traceback
-import bbctrl
-
from tornado.web import HTTPError
+import bbctrl
+import json
import tornado.httpclient
class APIHandler(bbctrl.RequestHandler):
+
def delete(self, *args, **kwargs):
self.delete_ok(*args, **kwargs)
self.write_json('ok')
-
- def delete_ok(self): raise HTTPError(405)
-
+ def delete_ok(self):
+ raise HTTPError(405)
def put(self, *args, **kwargs):
self.put_ok(*args, **kwargs)
self.write_json('ok')
-
- def put_ok(self): raise HTTPError(405)
-
+ def put_ok(self):
+ raise HTTPError(405)
def prepare(self):
self.json = {}
@@ -59,11 +29,9 @@ class APIHandler(bbctrl.RequestHandler):
except ValueError:
raise HTTPError(400, 'Unable to parse JSON')
-
def set_default_headers(self):
self.set_header('Content-Type', 'application/json')
-
def write_error(self, status_code, **kwargs):
e = {}
@@ -73,16 +41,17 @@ class APIHandler(bbctrl.RequestHandler):
typ, value, tb = kwargs['exc_info']
if isinstance(value, HTTPError) and value.log_message:
e['message'] = value.log_message % value.args
- else: e['message'] = str(kwargs['exc_info'][1])
+ else:
+ e['message'] = str(kwargs['exc_info'][1])
- else: e['message'] = 'Unknown error'
+ else:
+ e['message'] = 'Unknown error'
e['code'] = status_code
self.write_json(e)
-
- def write_json(self, data, pretty = False):
- if pretty: data = json.dumps(data, indent = 2, separators = (',', ': '))
- else: data = json.dumps(data, separators = (',', ':'))
+ def write_json(self, data, pretty=False):
+ if pretty: data = json.dumps(data, indent=2, separators=(',', ': '))
+ else: data = json.dumps(data, separators=(',', ':'))
self.write(data)
diff --git a/src/py/bbctrl/AVR.py b/src/py/bbctrl/AVR.py
index 211fe6e..0222ffe 100644
--- a/src/py/bbctrl/AVR.py
+++ b/src/py/bbctrl/AVR.py
@@ -1,59 +1,29 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
+import ctypes
import serial
import time
import traceback
-import ctypes
-
-import bbctrl
-import bbctrl.Cmd as Cmd
class serial_struct(ctypes.Structure):
_fields_ = [
- ('type', ctypes.c_int),
- ('line', ctypes.c_int),
- ('port', ctypes.c_uint),
- ('irq', ctypes.c_int),
- ('flags', ctypes.c_int),
- ('xmit_fifo_size', ctypes.c_int),
- ('custom_divisor', ctypes.c_int),
- ('baud_base', ctypes.c_int),
- ('close_delay', ctypes.c_ushort),
- ('io_type', ctypes.c_byte),
- ('reserved', ctypes.c_byte),
- ('hub6', ctypes.c_int),
- ('closing_wait', ctypes.c_ushort),
- ('closing_wait2', ctypes.c_ushort),
- ('iomem_base', ctypes.c_char_p),
+ ('type', ctypes.c_int),
+ ('line', ctypes.c_int),
+ ('port', ctypes.c_uint),
+ ('irq', ctypes.c_int),
+ ('flags', ctypes.c_int),
+ ('xmit_fifo_size', ctypes.c_int),
+ ('custom_divisor', ctypes.c_int),
+ ('baud_base', ctypes.c_int),
+ ('close_delay', ctypes.c_ushort),
+ ('io_type', ctypes.c_byte),
+ ('reserved', ctypes.c_byte),
+ ('hub6', ctypes.c_int),
+ ('closing_wait', ctypes.c_ushort),
+ ('closing_wait2', ctypes.c_ushort),
+ ('iomem_base', ctypes.c_char_p),
('iomem_reg_shift', ctypes.c_ushort),
- ('port_high', ctypes.c_uint),
- ('iomap_base', ctypes.c_ulong),
+ ('port_high', ctypes.c_uint),
+ ('iomap_base', ctypes.c_ulong),
]
@@ -65,11 +35,12 @@ def serial_set_low_latency(sp):
ss = serial_struct()
fcntl.ioctl(sp, termios.TIOCGSERIAL, ss)
- ss.flags |= 1 << ASYNCB_LOW_LATENCY # pylint: disable=no-member
+ ss.flags |= 1 << ASYNCB_LOW_LATENCY # pylint: disable=no-member
fcntl.ioctl(sp, termios.TIOCSSERIAL, ss)
class AVR(object):
+
def __init__(self, ctrl):
self.ctrl = ctrl
self.log = ctrl.log.get('AVR')
@@ -79,14 +50,16 @@ class AVR(object):
self.read_cb = None
self.write_cb = None
-
- def close(self): pass
-
+ def close(self):
+ pass
def _start(self):
try:
- self.sp = serial.Serial(self.ctrl.args.serial, self.ctrl.args.baud,
- rtscts = 1, timeout = 0, write_timeout = 0)
+ self.sp = serial.Serial(self.ctrl.args.serial,
+ self.ctrl.args.baud,
+ rtscts=1,
+ timeout=0,
+ write_timeout=0)
self.sp.nonblocking()
#serial_set_low_latency(self.sp)
@@ -98,7 +71,6 @@ class AVR(object):
self.ctrl.ioloop.add_handler(self.sp, self._serial_handler,
self.ctrl.ioloop.READ)
-
def set_handlers(self, read_cb, write_cb):
if self.read_cb is not None or self.write_cb is not None:
raise Exception('Handler already set')
@@ -107,7 +79,6 @@ class AVR(object):
self.write_cb = write_cb
self._start()
-
def enable_write(self, enable):
if self.sp is None: return
@@ -115,11 +86,9 @@ class AVR(object):
if enable: flags |= self.ctrl.ioloop.WRITE
self.ctrl.ioloop.update_handler(self.sp, flags)
-
def _serial_write(self):
self.write_cb(lambda data: self.sp.write(data))
-
def _serial_read(self):
try:
data = ''
@@ -129,17 +98,16 @@ class AVR(object):
except Exception as e:
self.log.warning('%s: %s', e, data)
-
def _serial_handler(self, fd, events):
try:
if self.ctrl.ioloop.READ & events: self._serial_read()
if self.ctrl.ioloop.WRITE & events: self._serial_write()
except Exception as e:
- self.log.warning('Serial handler error: %s', traceback.format_exc())
+ self.log.warning('Serial handler error: %s',
+ traceback.format_exc())
-
- def i2c_command(self, cmd, byte = None, word = None, block = None):
+ def i2c_command(self, cmd, byte=None, word=None, block=None):
self.log.info('I2C: %s b=%s w=%s d=%s' % (cmd, byte, word, block))
retry = 10
cmd = ord(cmd[0])
diff --git a/src/py/bbctrl/Camera.py b/src/py/bbctrl/Camera.py
index da42be1..d4fc84f 100644
--- a/src/py/bbctrl/Camera.py
+++ b/src/py/bbctrl/Camera.py
@@ -1,42 +1,9 @@
-#!/usr/bin/env python3
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import os
-import fcntl
-import select
-import struct
-import mmap
-import pyudev
-import base64
-import socket
-import ctypes
-from tornado import gen, web, iostream
+from tornado import web, iostream
import bbctrl
+import fcntl
+import mmap
+import os
+import pyudev
try:
import v4l2
@@ -45,6 +12,7 @@ except:
def array_to_string(a):
+
def until_zero(a):
for c in a:
if c == 0: return
@@ -61,13 +29,17 @@ def fourcc_to_string(i):
chr((i >> 24) & 0xff)
-def string_to_fourcc(s): return v4l2.v4l2_fourcc(s[0], s[1], s[2], s[3])
+def string_to_fourcc(s):
+ return v4l2.v4l2_fourcc(s[0], s[1], s[2], s[3])
def format_frame(frame):
- frame = [b'--', VideoHandler.boundary.encode('utf8'), b'\r\n',
- b'Content-type: image/jpeg\r\n',
- b'Content-length: %d\r\n\r\n' % len(frame), frame]
+ frame = [
+ b'--',
+ VideoHandler.boundary.encode('utf8'), b'\r\n',
+ b'Content-type: image/jpeg\r\n',
+ b'Content-length: %d\r\n\r\n' % len(frame), frame
+ ]
return b''.join(frame)
@@ -79,13 +51,13 @@ def get_image_resource(path):
class VideoDevice(object):
- def __init__(self, path = '/dev/video0'):
+
+ def __init__(self, path='/dev/video0'):
self.fd = os.open(path, os.O_RDWR | os.O_NONBLOCK | os.O_CLOEXEC)
self.buffers = []
-
- def fileno(self): return self.fd
-
+ def fileno(self):
+ return self.fd
def get_audio(self):
b = v4l2.v4l2_audio()
@@ -99,11 +71,11 @@ class VideoDevice(object):
l.append((array_to_string(b.name), b.capability, b.mode))
b.index += 1
- except OSError: break
+ except OSError:
+ break
return l
-
def get_formats(self):
b = v4l2.v4l2_fmtdesc()
b.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -120,11 +92,11 @@ class VideoDevice(object):
b.index += 1
- except OSError: break
+ except OSError:
+ break
return l
-
def get_frame_sizes(self, fourcc):
b = v4l2.v4l2_frmsizeenum()
b.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -140,18 +112,18 @@ class VideoDevice(object):
sizes.append((b.discrete.width, b.discrete.height))
else:
- sizes.append((b.stepwise.min_width, b.stepwise.max_width,
- b.stepwise.step_width, b.stepwise.min_height,
- b.stepwise.max_height,
- b.stepwise.step_height))
+ sizes.append(
+ (b.stepwise.min_width, b.stepwise.max_width,
+ b.stepwise.step_width, b.stepwise.min_height,
+ b.stepwise.max_height, b.stepwise.step_height))
- b.index += 1 # pylint: disable=no-member
+ b.index += 1 # pylint: disable=no-member
- except OSError: break
+ except OSError:
+ break
return sizes
-
def set_format(self, width, height, fourcc):
fmt = v4l2.v4l2_format()
fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -163,13 +135,12 @@ class VideoDevice(object):
fcntl.ioctl(self, v4l2.VIDIOC_S_FMT, fmt)
-
def create_buffers(self, count):
# Create buffers
rbuf = v4l2.v4l2_requestbuffers()
- rbuf.count = count;
- rbuf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE;
- rbuf.memory = v4l2.V4L2_MEMORY_MMAP;
+ rbuf.count = count
+ rbuf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
+ rbuf.memory = v4l2.V4L2_MEMORY_MMAP
fcntl.ioctl(self, v4l2.VIDIOC_REQBUFS, rbuf)
@@ -182,15 +153,16 @@ class VideoDevice(object):
fcntl.ioctl(self, v4l2.VIDIOC_QUERYBUF, buf)
# Mem map buffer
- mm = mmap.mmap(self.fileno(), buf.length, mmap.MAP_SHARED,
+ mm = mmap.mmap(self.fileno(),
+ buf.length,
+ mmap.MAP_SHARED,
mmap.PROT_READ | mmap.PROT_WRITE,
- offset = buf.m.offset)
+ offset=buf.m.offset)
self.buffers.append(mm)
# Queue the buffer for capture
fcntl.ioctl(self, v4l2.VIDIOC_QBUF, buf)
-
def _dqbuf(self):
buf = v4l2.v4l2_buffer()
buf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -199,11 +171,9 @@ class VideoDevice(object):
return buf
-
def _qbuf(self, buf):
fcntl.ioctl(self, v4l2.VIDIOC_QBUF, buf)
-
def read_frame(self):
buf = self._dqbuf()
mm = self.buffers[buf.index]
@@ -214,16 +184,15 @@ class VideoDevice(object):
return frame
-
- def flush_frame(self): self._qbuf(self._dqbuf())
-
+ def flush_frame(self):
+ self._qbuf(self._dqbuf())
def get_info(self):
caps = v4l2.v4l2_capability()
fcntl.ioctl(self, v4l2.VIDIOC_QUERYCAP, caps)
- caps._driver = array_to_string(caps.driver)
- caps._card = array_to_string(caps.card)
+ caps._driver = array_to_string(caps.driver)
+ caps._card = array_to_string(caps.card)
caps._bus_info = array_to_string(caps.bus_info)
l = []
@@ -251,33 +220,31 @@ class VideoDevice(object):
return caps
-
def set_fps(self, fps):
setfps = v4l2.v4l2_streamparm()
- setfps.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ setfps.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
setfps.parm.capture.timeperframe.numerator = 1
setfps.parm.capture.timeperframe.denominator = fps
fcntl.ioctl(self, v4l2.VIDIOC_S_PARM, setfps)
-
def start(self):
buf_type = v4l2.v4l2_buf_type(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)
fcntl.ioctl(self, v4l2.VIDIOC_STREAMON, buf_type)
-
def stop(self):
buf_type = v4l2.v4l2_buf_type(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)
fcntl.ioctl(self, v4l2.VIDIOC_STREAMOFF, buf_type)
-
def close(self):
if self.fd is None: return
try:
os.close(self.fd)
- finally: self.fd = None
+ finally:
+ self.fd = None
class Camera(object):
+
def __init__(self, ioloop, args, log):
self.ioloop = ioloop
self.log = log.get('Camera')
@@ -305,11 +272,10 @@ class Camera(object):
# Get notifications of camera (un)plug events
self.udevCtx = pyudev.Context()
self.udevMon = pyudev.Monitor.from_netlink(self.udevCtx)
- self.udevMon.filter_by(subsystem = 'video4linux')
+ self.udevMon.filter_by(subsystem='video4linux')
ioloop.add_handler(self.udevMon, self._udev_handler, ioloop.READ)
self.udevMon.start()
-
def _udev_handler(self, fd, events):
action, device = self.udevMon.receive_device()
if device is None or self.dev is not None: return
@@ -324,7 +290,6 @@ class Camera(object):
self.have_camera = False
self.close()
-
def _send_frame(self, frame):
if not len(self.clients): return
@@ -337,14 +302,14 @@ class Camera(object):
except Exception as e:
self.log.warning('Failed to write frame to client: %s' % e)
-
def _fd_handler(self, fd, events):
try:
if len(self.clients):
frame = self.dev.read_frame()
self._send_frame(frame)
- else: self.dev.flush_frame()
+ else:
+ self.dev.flush_frame()
except Exception as e:
if isinstance(e, BlockingIOError): return
@@ -353,7 +318,6 @@ class Camera(object):
self.ioloop.remove_handler(fd)
self.close()
-
def _update_client_image(self):
if self.have_camera and not self.overtemp: return
if self.overtemp and self.have_camera: img = 'overtemp'
@@ -361,7 +325,6 @@ class Camera(object):
if len(self.clients): self.clients[-1].write_img(img)
-
def open(self, path):
try:
self._update_client_image()
@@ -376,9 +339,9 @@ class Camera(object):
if caps.capabilities & v4l2.V4L2_CAP_VIDEO_CAPTURE == 0:
raise Exception('Video capture not supported.')
- fourcc = string_to_fourcc(self.fourcc)
+ fourcc = string_to_fourcc(self.fourcc)
formats = self.dev.get_formats()
- sizes = self.dev.get_frame_sizes(fourcc)
+ sizes = self.dev.get_frame_sizes(fourcc)
self.log.info('Formats: %s', formats)
self.log.info('Sizes: %s', sizes)
@@ -391,7 +354,7 @@ class Camera(object):
if not hasFormat:
raise Exception(self.fourcc + ' video format not supported.')
- self.dev.set_format(self.width, self.height, fourcc = fourcc)
+ self.dev.set_format(self.width, self.height, fourcc=fourcc)
self.dev.set_fps(self.fps)
self.dev.create_buffers(4)
self.dev.start()
@@ -401,22 +364,20 @@ class Camera(object):
self.log.info('Opened camera ' + path)
-
except Exception as e:
self.log.warning('While loading camera: %s' % e)
self._close_dev()
-
def _close_dev(self):
if self.dev is None: return
try:
self.dev.close()
- except Exception as e: self.log.warning('While closing camera: %s', e)
+ except Exception as e:
+ self.log.warning('While closing camera: %s', e)
self.dev = None
-
- def close(self, overtemp = False):
+ def close(self, overtemp=False):
self._update_client_image()
if self.dev is None: return
@@ -424,14 +385,17 @@ class Camera(object):
self.ioloop.remove_handler(self.dev)
try:
self.dev.stop()
- except: pass
+ except:
+ pass
self._close_dev()
self.log.info('Closed camera')
- except: self.log.exception('Internal error: Exception while closing camera')
- finally: self.dev = None
-
+ except:
+ self.log.exception(
+ 'Internal error: Exception while closing camera')
+ finally:
+ self.dev = None
def add_client(self, client):
self.log.info('Adding camera client: %d' % len(self.clients))
@@ -442,13 +406,12 @@ class Camera(object):
self.clients.append(client)
self._update_client_image()
-
def remove_client(self, client):
self.log.info('Removing camera client')
try:
self.clients.remove(client)
- except: pass
-
+ except:
+ pass
def set_overtemp(self, overtemp):
if self.overtemp == overtemp: return
@@ -458,36 +421,32 @@ class Camera(object):
elif self.path is not None: self.open(self.path)
-
class VideoHandler(web.RequestHandler):
boundary = '-f36a3a39e5c955484390e0e3a6b031d1---'
-
def __init__(self, app, request, **kwargs):
super().__init__(app, request, **kwargs)
self.camera = app.camera
-
@web.asynchronous
def get(self):
self.request.connection.stream.max_write_buffer_size = 10000
- self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, '
- 'pre-check=0, post-check=0, max-age=0')
+ self.set_header(
+ 'Cache-Control', 'no-store, no-cache, must-revalidate, '
+ 'pre-check=0, post-check=0, max-age=0')
self.set_header('Connection', 'close')
- self.set_header('Content-Type', 'multipart/x-mixed-replace;boundary=' +
- self.boundary)
+ self.set_header('Content-Type',
+ 'multipart/x-mixed-replace;boundary=' + self.boundary)
self.set_header('Expires', 'Mon, 3 Jan 2000 12:34:56 GMT')
self.set_header('Pragma', 'no-cache')
if self.camera is None: self.write_img('offline')
else: self.camera.add_client(self)
-
def write_img(self, name):
self.write_frame_twice(get_image_resource('http/images/%s.jpg' % name))
-
def write_frame(self, frame):
# Don't allow too many frames to queue up
min_size = len(frame) * 2
@@ -499,12 +458,11 @@ class VideoHandler(web.RequestHandler):
self.flush()
except iostream.StreamBufferFullError:
- pass # Drop frame if buffer is full
-
+ pass # Drop frame if buffer is full
def write_frame_twice(self, frame):
self.write_frame(frame)
self.write_frame(frame)
-
- def on_connection_close(self): self.camera.remove_client(self)
+ def on_connection_close(self):
+ self.camera.remove_client(self)
diff --git a/src/py/bbctrl/Cmd.py b/src/py/bbctrl/Cmd.py
index 88c111b..df494c1 100644
--- a/src/py/bbctrl/Cmd.py
+++ b/src/py/bbctrl/Cmd.py
@@ -1,64 +1,35 @@
-#!/usr/bin/env python3
-
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import struct
import base64
import json
+import struct
# Keep this in sync with AVR code command.def
-SET = '$'
-SET_SYNC = '#'
-MODBUS_READ = 'm'
+SET = '$'
+SET_SYNC = '#'
+MODBUS_READ = 'm'
MODBUS_WRITE = 'M'
-SEEK = 's'
-SET_AXIS = 'a'
-LINE = 'l'
-SYNC_SPEED = '%'
-SPEED = 'p'
-INPUT = 'I'
-DWELL = 'd'
-PAUSE = 'P'
-STOP = 'S'
-UNPAUSE = 'U'
-JOG = 'j'
-REPORT = 'r'
-REBOOT = 'R'
-RESUME = 'c'
-ESTOP = 'E'
-SHUTDOWN = 'X'
-CLEAR = 'C'
-FLUSH = 'F'
-DUMP = 'D'
-HELP = 'h'
+SEEK = 's'
+SET_AXIS = 'a'
+LINE = 'l'
+SYNC_SPEED = '%'
+SPEED = 'p'
+INPUT = 'I'
+DWELL = 'd'
+PAUSE = 'P'
+STOP = 'S'
+UNPAUSE = 'U'
+JOG = 'j'
+REPORT = 'r'
+REBOOT = 'R'
+RESUME = 'c'
+ESTOP = 'E'
+SHUTDOWN = 'X'
+CLEAR = 'C'
+FLUSH = 'F'
+DUMP = 'D'
+HELP = 'h'
SEEK_ACTIVE = 1 << 0
-SEEK_ERROR = 1 << 1
+SEEK_ERROR = 1 << 1
def encode_float(x):
@@ -95,9 +66,16 @@ def set_float(name, value):
return SET_SYNC + '%s=:%s' % (name, encode_float(value))
-def modbus_read(addr): return MODBUS_READ + '%d' % addr
-def modbus_write(addr, value): return MODBUS_WRITE + '%d=%d' % (addr, value)
-def set_axis(axis, position): return SET_AXIS + axis + encode_float(position)
+def modbus_read(addr):
+ return MODBUS_READ + '%d' % addr
+
+
+def modbus_write(addr, value):
+ return MODBUS_WRITE + '%d=%d' % (addr, value)
+
+
+def set_axis(axis, position):
+ return SET_AXIS + axis + encode_float(position)
def line(target, exitVel, maxAccel, maxJerk, times, speeds):
@@ -111,7 +89,7 @@ def line(target, exitVel, maxAccel, maxJerk, times, speeds):
# S-Curve time parameters
for i in range(7):
if times[i]:
- cmd += str(i) + encode_float(times[i] / 60000) # to mins
+ cmd += str(i) + encode_float(times[i] / 60000) # to mins
# Speeds
for dist, speed in speeds:
@@ -120,7 +98,8 @@ def line(target, exitVel, maxAccel, maxJerk, times, speeds):
return cmd
-def speed(value): return SPEED + encode_float(value)
+def speed(value):
+ return SPEED + encode_float(value)
def sync_speed(dist, speed):
@@ -135,28 +114,29 @@ def input(port, mode, timeout):
if port == 'digital-in-1': type, index = 'd', 1
if port == 'digital-in-2': type, index = 'd', 2
if port == 'digital-in-3': type, index = 'd', 3
- if port == 'analog-in-0': type, index = 'a', 0
- if port == 'analog-in-1': type, index = 'a', 1
- if port == 'analog-in-2': type, index = 'a', 2
- if port == 'analog-in-3': type, index = 'a', 3
+ if port == 'analog-in-0': type, index = 'a', 0
+ if port == 'analog-in-1': type, index = 'a', 1
+ if port == 'analog-in-2': type, index = 'a', 2
+ if port == 'analog-in-3': type, index = 'a', 3
# Mode
if mode == 'immediate': m = 0
- if mode == 'rise': m = 1
- if mode == 'fall': m = 2
- if mode == 'high': m = 3
- if mode == 'low': m = 4
+ if mode == 'rise': m = 1
+ if mode == 'fall': m = 2
+ if mode == 'high': m = 3
+ if mode == 'low': m = 4
return '%s%s%d%d%s' % (INPUT, type, index, m, encode_float(timeout))
def output(port, value):
- if port == 'mist': return '#1oa=' + ('1' if value else '0')
+ if port == 'mist': return '#1oa=' + ('1' if value else '0')
if port == 'flood': return '#2oa=' + ('1' if value else '0')
raise Exception('Unsupported output "%s"' % port)
-def dwell(seconds): return DWELL + encode_float(seconds)
+def dwell(seconds):
+ return DWELL + encode_float(seconds)
def pause(type):
@@ -168,7 +148,8 @@ def pause(type):
return '%s%d' % (PAUSE, type)
-def jog(axes): return JOG + encode_axes(axes)
+def jog(axes):
+ return JOG + encode_axes(axes)
def seek(switch, active, error):
@@ -177,7 +158,7 @@ def seek(switch, active, error):
flags = 0
if active: flags |= SEEK_ACTIVE
- if error: flags |= SEEK_ERROR
+ if error: flags |= SEEK_ERROR
cmd += chr(flags + ord('0'))
return cmd
@@ -213,7 +194,6 @@ def decode_command(cmd):
if name in 'xyzabcuvw': data[name] = value
-
elif cmd[0] == SEEK:
data['type'] = 'seek'
@@ -225,9 +205,9 @@ def decode_command(cmd):
elif cmd[0] == LINE:
data['type'] = 'line'
- data['exit-vel'] = decode_float(cmd[1:7])
+ data['exit-vel'] = decode_float(cmd[1:7])
data['max-accel'] = decode_float(cmd[7:13])
- data['max-jerk'] = decode_float(cmd[13:19])
+ data['max-jerk'] = decode_float(cmd[13:19])
data['target'] = {}
data['times'] = [0] * 7
@@ -244,16 +224,24 @@ def decode_command(cmd):
elif cmd[0] == SYNC_SPEED:
data['type'] = 'speed'
data['offset'] = decode_float(cmd[1:7])
- data['speed'] = decode_float(cmd[7:13])
+ data['speed'] = decode_float(cmd[7:13])
- elif cmd[0] == REPORT: data['type'] = 'report'
- elif cmd[0] == PAUSE: data['type'] = 'pause'
- elif cmd[0] == UNPAUSE: data['type'] = 'unpause'
- elif cmd[0] == ESTOP: data['type'] = 'estop'
- elif cmd[0] == SHUTDOWN: data['type'] = 'shutdown'
- elif cmd[0] == CLEAR: data['type'] = 'clear'
- elif cmd[0] == FLUSH: data['type'] = 'flush'
- elif cmd[0] == RESUME: data['type'] = 'resume'
+ elif cmd[0] == REPORT:
+ data['type'] = 'report'
+ elif cmd[0] == PAUSE:
+ data['type'] = 'pause'
+ elif cmd[0] == UNPAUSE:
+ data['type'] = 'unpause'
+ elif cmd[0] == ESTOP:
+ data['type'] = 'estop'
+ elif cmd[0] == SHUTDOWN:
+ data['type'] = 'shutdown'
+ elif cmd[0] == CLEAR:
+ data['type'] = 'clear'
+ elif cmd[0] == FLUSH:
+ data['type'] = 'flush'
+ elif cmd[0] == RESUME:
+ data['type'] = 'resume'
return data
diff --git a/src/py/bbctrl/Comm.py b/src/py/bbctrl/Comm.py
index b7dc27e..cbed8fd 100644
--- a/src/py/bbctrl/Comm.py
+++ b/src/py/bbctrl/Comm.py
@@ -1,65 +1,33 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import serial
-import json
-import time
-import traceback
from collections import deque
-
-import bbctrl
import bbctrl.Cmd as Cmd
-
+import json
+import traceback
# Must be kept in sync with drv8711.h
-DRV8711_STATUS_OTS_bm = 1 << 0
-DRV8711_STATUS_AOCP_bm = 1 << 1
-DRV8711_STATUS_BOCP_bm = 1 << 2
-DRV8711_STATUS_APDF_bm = 1 << 3
-DRV8711_STATUS_BPDF_bm = 1 << 4
-DRV8711_STATUS_UVLO_bm = 1 << 5
-DRV8711_STATUS_STD_bm = 1 << 6
+DRV8711_STATUS_OTS_bm = 1 << 0
+DRV8711_STATUS_AOCP_bm = 1 << 1
+DRV8711_STATUS_BOCP_bm = 1 << 2
+DRV8711_STATUS_APDF_bm = 1 << 3
+DRV8711_STATUS_BPDF_bm = 1 << 4
+DRV8711_STATUS_UVLO_bm = 1 << 5
+DRV8711_STATUS_STD_bm = 1 << 6
DRV8711_STATUS_STDLAT_bm = 1 << 7
-DRV8711_COMM_ERROR_bm = 1 << 8
+DRV8711_COMM_ERROR_bm = 1 << 8
# Ignoring stall and stall latch flags for now
DRV8711_MASK = ~(DRV8711_STATUS_STD_bm | DRV8711_STATUS_STDLAT_bm)
def _driver_flags_to_string(flags):
- if DRV8711_STATUS_OTS_bm & flags: yield 'over temp'
- if DRV8711_STATUS_AOCP_bm & flags: yield 'over current a'
- if DRV8711_STATUS_BOCP_bm & flags: yield 'over current b'
- if DRV8711_STATUS_APDF_bm & flags: yield 'driver fault a'
- if DRV8711_STATUS_BPDF_bm & flags: yield 'driver fault b'
- if DRV8711_STATUS_UVLO_bm & flags: yield 'undervoltage'
- if DRV8711_STATUS_STD_bm & flags: yield 'stall'
+ if DRV8711_STATUS_OTS_bm & flags: yield 'over temp'
+ if DRV8711_STATUS_AOCP_bm & flags: yield 'over current a'
+ if DRV8711_STATUS_BOCP_bm & flags: yield 'over current b'
+ if DRV8711_STATUS_APDF_bm & flags: yield 'driver fault a'
+ if DRV8711_STATUS_BPDF_bm & flags: yield 'driver fault b'
+ if DRV8711_STATUS_UVLO_bm & flags: yield 'undervoltage'
+ if DRV8711_STATUS_STD_bm & flags: yield 'stall'
if DRV8711_STATUS_STDLAT_bm & flags: yield 'stall latch'
- if DRV8711_COMM_ERROR_bm & flags: yield 'comm error'
+ if DRV8711_COMM_ERROR_bm & flags: yield 'comm error'
def driver_flags_to_string(flags):
@@ -67,6 +35,7 @@ def driver_flags_to_string(flags):
class Comm(object):
+
def __init__(self, ctrl, avr):
self.ctrl = ctrl
self.avr = avr
@@ -79,41 +48,38 @@ class Comm(object):
avr.set_handlers(self._read, self._write)
self._poll_cb(False)
+ def comm_next(self):
+ raise Exception('Not implemented')
- def comm_next(self): raise Exception('Not implemented')
- def comm_error(self): raise Exception('Not implemented')
+ def comm_error(self):
+ raise Exception('Not implemented')
+ def is_active(self):
+ return len(self.queue) or self.command is not None
- def is_active(self): return len(self.queue) or self.command is not None
-
-
- def i2c_command(self, cmd, byte = None, word = None, block = None):
+ def i2c_command(self, cmd, byte=None, word=None, block=None):
self.log.info('I2C: %s b=%s w=%s d=%s' % (cmd, byte, word, block))
self.avr.i2c_command(cmd, byte, word, block)
-
- def flush(self): self.avr.enable_write(True)
-
+ def flush(self):
+ self.avr.enable_write(True)
def _load_next_command(self, cmd):
self.log.info('< ' + json.dumps(cmd).strip('"'))
self.command = bytes(cmd.strip() + '\n', 'utf-8')
-
- def resume(self): self.queue_command(Cmd.RESUME)
-
+ def resume(self):
+ self.queue_command(Cmd.RESUME)
def queue_command(self, cmd):
self.queue.append(cmd)
self.flush()
-
- def _poll_cb(self, now = True):
+ def _poll_cb(self, now=True):
# Checks periodically for new commands from planner via comm_next()
if now: self.flush()
self.ctrl.ioloop.call_later(1, self._poll_cb)
-
def _write(self, write_cb):
# Finish writing current command
if self.command is not None:
@@ -125,25 +91,25 @@ class Comm(object):
raise e
self.command = self.command[count:]
- if len(self.command): return # There's more
+ if len(self.command): return # There's more
self.command = None
# Load next command from queue
- if len(self.queue): self._load_next_command(self.queue.popleft())
+ if len(self.queue):
+ self._load_next_command(self.queue.popleft())
- # Load next command from callback
+ # Load next command from callback
else:
- cmd = self.comm_next() # pylint: disable=assignment-from-no-return
+ cmd = self.comm_next() # pylint: disable=assignment-from-no-return
- if cmd is None: self.avr.enable_write(False) # Stop writing
+ if cmd is None: self.avr.enable_write(False) # Stop writing
else: self._load_next_command(cmd)
-
def _update_vars(self, msg):
try:
self.ctrl.state.set_machine_vars(msg['variables'])
self.ctrl.configure()
- self.queue_command(Cmd.DUMP) # Refresh all vars
+ self.queue_command(Cmd.DUMP) # Refresh all vars
# Set axis positions
for axis in 'xyzabc':
@@ -154,16 +120,15 @@ class Comm(object):
self.log.warning('AVR reload failed: %s', traceback.format_exc())
self.ctrl.ioloop.call_later(1, self.connect)
-
def _log_msg(self, msg):
level = msg.get('level', 'info')
where = msg.get('where')
msg = msg['msg']
- if level == 'info': self.log.info(msg, where = where)
- elif level == 'debug': self.log.debug(msg, where = where)
- elif level == 'warning': self.log.warning(msg, where = where)
- elif level == 'error': self.log.error(msg, where = where)
+ if level == 'info': self.log.info(msg, where=where)
+ elif level == 'debug': self.log.debug(msg, where=where)
+ elif level == 'warning': self.log.warning(msg, where=where)
+ elif level == 'error': self.log.error(msg, where=where)
if level == 'error': self.comm_error()
@@ -171,7 +136,6 @@ class Comm(object):
if level == 'warning' and 'code' in msg and msg['code'] == 11:
self.comm_error()
-
def _log_motor_flags(self, update):
for motor in range(3):
var = '%ddf' % motor
@@ -185,17 +149,15 @@ class Comm(object):
flags = driver_flags_to_string(flags)
self.log.info('Motor %d flags: %s' % (motor, flags))
-
def _update_state(self, update):
self.ctrl.state.update(update)
- if 'xx' in update: # State change
- self.ctrl.ready() # We've received data from AVR
- self.flush() # May have more data to send now
+ if 'xx' in update: # State change
+ self.ctrl.ready() # We've received data from AVR
+ self.flush() # May have more data to send now
self._log_motor_flags(update)
-
def _read(self, data):
self.in_buf += data.decode('utf-8')
@@ -227,29 +189,25 @@ class Comm(object):
else:
self._update_state(msg)
-
def estop(self):
if self.ctrl.state.get('xx', '') != 'ESTOPPED':
self.i2c_command(Cmd.ESTOP)
-
def clear(self):
if self.ctrl.state.get('xx', '') == 'ESTOPPED':
self.i2c_command(Cmd.CLEAR)
-
def pause(self):
- self.i2c_command(Cmd.PAUSE, byte = ord('0')) # User pause
-
-
- def reboot(self): self.queue_command(Cmd.REBOOT)
+ self.i2c_command(Cmd.PAUSE, byte=ord('0')) # User pause
+ def reboot(self):
+ self.queue_command(Cmd.REBOOT)
def connect(self):
try:
# Resume once current queue of GCode commands has flushed
self.queue_command(Cmd.RESUME)
- self.queue_command(Cmd.HELP) # Load AVR commands and variables
+ self.queue_command(Cmd.HELP) # Load AVR commands and variables
except Exception as e:
self.log.warning('Connect failed: %s', e)
diff --git a/src/py/bbctrl/CommandQueue.py b/src/py/bbctrl/CommandQueue.py
index a58eeb6..0524645 100644
--- a/src/py/bbctrl/CommandQueue.py
+++ b/src/py/bbctrl/CommandQueue.py
@@ -1,39 +1,14 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import bbctrl
from collections import deque
+import bbctrl
# 16-bit less with wrap around
-def id_less(a, b): return (1 << 15) < (a - b) & ((1 << 16) - 1)
+def id_less(a, b):
+ return (1 << 15) < (a - b) & ((1 << 16) - 1)
class CommandQueue():
+
def __init__(self, ctrl):
self.log = ctrl.log.get('CmdQ')
self.log.set_level(bbctrl.log.WARNING)
@@ -42,23 +17,20 @@ class CommandQueue():
self.releaseID = 0
self.q = deque()
-
- def is_active(self): return len(self.q)
-
+ def is_active(self):
+ return len(self.q)
def clear(self):
self.lastEnqueueID = 0
self.releaseID = 0
self.q.clear()
-
def enqueue(self, id, cb, *args, **kwargs):
self.log.info('add(#%d) releaseID=%d', id, self.releaseID)
self.lastEnqueueID = id
self.q.append([id, cb, args, kwargs])
self._release()
-
def _release(self):
while len(self.q):
id, cb, args, kwargs = self.q[0]
@@ -72,9 +44,8 @@ class CommandQueue():
try:
if cb is not None: cb(*args, **kwargs)
except Exception:
- self.log.exception('Internal error: Command queue callback error')
-
-
+ self.log.exception(
+ 'Internal error: Command queue callback error')
def release(self, id):
if id and not id_less(self.releaseID, id):
diff --git a/src/py/bbctrl/Config.py b/src/py/bbctrl/Config.py
index 2a16b56..3395189 100644
--- a/src/py/bbctrl/Config.py
+++ b/src/py/bbctrl/Config.py
@@ -1,34 +1,7 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import os
-import json
-import pkg_resources
from pkg_resources import Requirement, resource_filename
+import json
+import os
+import pkg_resources
def get_resource(path):
@@ -36,6 +9,7 @@ def get_resource(path):
class Config(object):
+
def __init__(self, ctrl):
self.ctrl = ctrl
self.log = ctrl.log.get('Config')
@@ -46,24 +20,29 @@ class Config(object):
self.version = pkg_resources.require('bbctrl')[0].version
# Load config template
- with open(get_resource('http/config-template.json'), 'r',
- encoding = 'utf-8') as f:
+ with open(get_resource('http/config-template.json'),
+ 'r',
+ encoding='utf-8') as f:
self.template = json.load(f)
- except Exception: self.log.exception('Internal error: Failed to load config template')
-
+ except Exception:
+ self.log.exception(
+ 'Internal error: Failed to load config template')
def load(self):
path = self.ctrl.get_path('config.json')
try:
if os.path.exists(path):
- with open(path, 'r') as f: config = json.load(f)
- else: config = {'version': self.version}
+ with open(path, 'r') as f:
+ config = json.load(f)
+ else:
+ config = {'version': self.version}
try:
self._upgrade(config)
- except Exception: self.log.exception('Internal error: Failed to upgrade config')
+ except Exception:
+ self.log.exception('Internal error: Failed to upgrade config')
except Exception as e:
self.log.warning('%s', e)
@@ -72,15 +51,12 @@ class Config(object):
self._defaults(config)
return config
-
def reload(self):
self._update(self.load(), True)
-
- def get(self, name, default = None):
+ def get(self, name, default=None):
return self.values.get(name, default)
-
def save(self, config):
self._upgrade(config)
self._update(config, False)
@@ -93,21 +69,19 @@ class Config(object):
self.ctrl.preplanner.invalidate_all()
self.log.info('Saved')
-
def reset(self):
if os.path.exists('config.json'): os.unlink('config.json')
self.reload()
self.ctrl.preplanner.invalidate_all()
-
def _valid_value(self, template, value):
type = template['type']
try:
- if type == 'int': value = int(value)
+ if type == 'int': value = int(value)
if type == 'float': value = float(value)
- if type == 'text': value = str(value)
- if type == 'bool': value = bool(value)
+ if type == 'text': value = str(value)
+ if type == 'bool': value = bool(value)
except:
return False
@@ -116,11 +90,10 @@ class Config(object):
return True
-
def __defaults(self, config, name, template):
if 'type' in template:
- if (not name in config or
- not self._valid_value(template, config[name])):
+ if (not name in config
+ or not self._valid_value(template, config[name])):
config[name] = template['default']
elif 'max' in template and template['max'] < config[name]:
@@ -142,21 +115,21 @@ class Config(object):
for name, tmpl in template.items():
self.__defaults(config, name, tmpl)
-
def _defaults(self, config):
for name, tmpl in self.template.items():
if not 'type' in tmpl:
if not name in config: config[name] = {}
conf = config[name]
- else: conf = config
+ else:
+ conf = config
self.__defaults(conf, name, tmpl)
-
def _upgrade(self, config):
version = config['version']
- version = version.split('b')[0] # Strip off any "beta" suffix
- version = tuple(map(int, version.split('.'))) # Break it into a tuple of integers
+ version = version.split('b')[0] # Strip off any "beta" suffix
+ version = tuple(map(
+ int, version.split('.'))) # Break it into a tuple of integers
if version < (1, 0, 7):
config['settings']['max-deviation'] = 0.001
@@ -186,21 +159,24 @@ class Config(object):
config['settings']['junction-accel'] = 200000
if version < (1, 0, 9):
- with open(get_resource('http/onefinity_defaults.json'), 'r', encoding = 'utf-8') as f:
+ with open(get_resource('http/onefinity_defaults.json'),
+ 'r',
+ encoding='utf-8') as f:
defaults = json.load(f)
- config['selected-tool-settings'] = defaults['selected-tool-settings'];
+ config['selected-tool-settings'] = defaults[
+ 'selected-tool-settings']
config['version'] = self.version.split('b')[0]
config['full_version'] = self.version
-
def _encode(self, name, index, config, tmpl, with_defaults):
# Handle category
if not 'type' in tmpl:
for name, entry in tmpl.items():
if 'type' in entry and config is not None:
conf = config.get(name, None)
- else: conf = config
+ else:
+ conf = config
self._encode(name, index, conf, entry, with_defaults)
return
@@ -223,7 +199,8 @@ class Config(object):
if not name in self.values: self.values[name] = {}
self.values[name][index] = value
- else: self.values[name] = value
+ else:
+ self.values[name] = value
# Update state variable
if not 'code' in tmpl: return
@@ -237,7 +214,6 @@ class Config(object):
self.ctrl.state.config(index + tmpl['code'], value)
-
def _update(self, config, with_defaults):
for name, tmpl in self.template.items():
conf = config.get(name, None)
diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py
index 31728c7..f9b8247 100644
--- a/src/py/bbctrl/Ctrl.py
+++ b/src/py/bbctrl/Ctrl.py
@@ -1,8 +1,9 @@
-import os
import bbctrl
+import os
class Ctrl(object):
+
def __init__(self, args, ioloop, id):
self.args = args
self.ioloop = bbctrl.IOLoop(ioloop)
@@ -42,7 +43,8 @@ class Ctrl(object):
self.log.get('Ctrl').exception(
'Internal error: Control initialization failed')
- def __del__(self): print('Ctrl deleted')
+ def __del__(self):
+ print('Ctrl deleted')
def clear_timeout(self):
if self.timeout is not None:
diff --git a/src/py/bbctrl/FileHandler.py b/src/py/bbctrl/FileHandler.py
index 7f2195c..f267cba 100644
--- a/src/py/bbctrl/FileHandler.py
+++ b/src/py/bbctrl/FileHandler.py
@@ -1,11 +1,12 @@
-import os
-import tempfile
+from tornado import gen
+from tornado.escape import url_unescape
+from tornado.web import HTTPError
import bbctrl
import glob
+import os
+import tempfile
import tornado
-from tornado import gen
-from tornado.web import HTTPError
-from tornado.escape import url_unescape;
+
def safe_remove(path):
try:
@@ -16,9 +17,10 @@ def safe_remove(path):
@tornado.web.stream_request_body
class FileHandler(bbctrl.APIHandler):
+
def prepare(self):
if self.request.method == 'PUT':
- self.request.connection.set_max_body_size(2 ** 30)
+ self.request.connection.set_max_body_size(2**30)
filename = self.request.path.split('/')[-1]
self.uploadFilename = url_unescape(filename) \
@@ -57,14 +59,14 @@ class FileHandler(bbctrl.APIHandler):
self.uploadFile.close()
- del(self.uploadFile)
+ del (self.uploadFile)
self.get_ctrl().preplanner.invalidate(self.uploadFilename)
self.get_ctrl().state.add_file(self.uploadFilename)
- self.get_log('FileHandler').info(
- 'GCode received: ' + self.uploadFilename)
+ self.get_log('FileHandler').info('GCode received: ' +
+ self.uploadFilename)
- del(self.uploadFilename)
+ del (self.uploadFilename)
@gen.coroutine
def get(self, filename):
diff --git a/src/py/bbctrl/I2C.py b/src/py/bbctrl/I2C.py
index a97a528..d5889d4 100644
--- a/src/py/bbctrl/I2C.py
+++ b/src/py/bbctrl/I2C.py
@@ -1,30 +1,3 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
import errno
try:
@@ -37,12 +10,12 @@ except:
class I2C(object):
+
def __init__(self, port, disabled):
self.port = port
self.i2c_bus = None
self.disabled = disabled or smbus is None
-
def connect(self):
if self.disabled: return
if self.i2c_bus is None:
@@ -54,7 +27,6 @@ class I2C(object):
if e.errno == errno.ENOENT: self.disabled = True
else: raise type(e)('I2C failed to open device: %s' % e)
-
def read_word(self, addr):
self.connect()
if self.disabled: return
@@ -67,8 +39,7 @@ class I2C(object):
self.i2c_bus = None
raise type(e)('I2C read word failed: %s' % e)
-
- def write(self, addr, cmd, byte = None, word = None, block = None):
+ def write(self, addr, cmd, byte=None, word=None, block=None):
self.connect()
if self.disabled: return
@@ -83,7 +54,8 @@ class I2C(object):
if isinstance(block, str): block = list(map(ord, block))
self.i2c_bus.write_i2c_block_data(addr, cmd, block)
- else: self.i2c_bus.write_byte(addr, cmd)
+ else:
+ self.i2c_bus.write_byte(addr, cmd)
except IOError as e:
self.i2c_bus.close()
diff --git a/src/py/bbctrl/IOLoop.py b/src/py/bbctrl/IOLoop.py
index c9c614b..e4ec69f 100644
--- a/src/py/bbctrl/IOLoop.py
+++ b/src/py/bbctrl/IOLoop.py
@@ -1,35 +1,8 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
import tornado.ioloop
-import bbctrl
class CB(object):
+
def __init__(self, ioloop, delay, cb, *args, **kwargs):
self.ioloop = ioloop
self.cb = cb
@@ -49,45 +22,41 @@ class IOLoop(object):
WRITE = tornado.ioloop.IOLoop.WRITE
ERROR = tornado.ioloop.IOLoop.ERROR
-
def __init__(self, ioloop):
self.ioloop = ioloop
self.fds = set()
self.handles = set()
self.callbacks = {}
-
def close(self):
- for fd in list(self.fds): self.ioloop.remove_handler(fd)
- for h in list(self.handles): self.ioloop.remove_timeout(h)
- for h in list(self.callbacks): self.ioloop.remove_timeout(h)
-
+ for fd in list(self.fds):
+ self.ioloop.remove_handler(fd)
+ for h in list(self.handles):
+ self.ioloop.remove_timeout(h)
+ for h in list(self.callbacks):
+ self.ioloop.remove_timeout(h)
def add_handler(self, fd, handler, events):
self.ioloop.add_handler(fd, handler, events)
if hasattr(fd, 'fileno'): fd = fd.fileno()
self.fds.add(fd)
-
def remove_handler(self, h):
self.ioloop.remove_handler(h)
if hasattr(h, 'fileno'): h = h.fileno()
self.fds.remove(h)
-
- def update_handler(self, fd, events): self.ioloop.update_handler(fd, events)
-
+ def update_handler(self, fd, events):
+ self.ioloop.update_handler(fd, events)
def call_later(self, delay, callback, *args, **kwargs):
cb = CB(self, delay, callback, *args, **kwargs)
return cb.h
-
def remove_timeout(self, h):
self.ioloop.remove_timeout(h)
if h in self.handles: self.handles.remove(h)
if h in self.callbacks: del self.callbacks[h]
-
def add_callback(self, cb, *args, **kwargs):
self.ioloop.add_callback(cb, *args, **kwargs)
diff --git a/src/py/bbctrl/Log.py b/src/py/bbctrl/Log.py
index d333d37..f034650 100644
--- a/src/py/bbctrl/Log.py
+++ b/src/py/bbctrl/Log.py
@@ -1,50 +1,20 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import os
-import sys
-import io
import datetime
-import traceback
+import os
import pkg_resources
-from inspect import getframeinfo, stack
-import bbctrl
-
-
-DEBUG = 0
-INFO = 1
-MESSAGE = 2
-WARNING = 3
-ERROR = 4
+import sys
+import traceback
+DEBUG = 0
+INFO = 1
+MESSAGE = 2
+WARNING = 3
+ERROR = 4
level_names = 'debug info message warning error'.split()
-def get_level_name(level): return level_names[level]
+
+def get_level_name(level):
+ return level_names[level]
# Get this file's name
@@ -52,15 +22,17 @@ _srcfile = os.path.normcase(get_level_name.__code__.co_filename)
class Logger(object):
+
def __init__(self, log, name, level):
self.log = log
self.name = name
self.level = level
+ def set_level(self, level):
+ self.level = level
- def set_level(self, level): self.level = level
- def _enabled(self, level): return self.level <= level and level <= ERROR
-
+ def _enabled(self, level):
+ return self.level <= level and level <= ERROR
def _find_caller(self):
f = sys._getframe()
@@ -78,7 +50,6 @@ class Logger(object):
return '(unknown file)', 0, '(unknown function)'
-
def _log(self, level, msg, *args, **kwargs):
if not self._enabled(level): return
@@ -88,15 +59,22 @@ class Logger(object):
if len(args): msg %= args
- self.log._log(msg, level = level, prefix = self.name, **kwargs)
+ self.log._log(msg, level=level, prefix=self.name, **kwargs)
+ def debug(self, *args, **kwargs):
+ self._log(DEBUG, *args, **kwargs)
- def debug (self, *args, **kwargs): self._log(DEBUG, *args, **kwargs)
- def message(self, *args, **kwargs): self._log(MESSAGE, *args, **kwargs)
- def info (self, *args, **kwargs): self._log(INFO, *args, **kwargs)
- def warning(self, *args, **kwargs): self._log(WARNING, *args, **kwargs)
- def error (self, *args, **kwargs): self._log(ERROR, *args, **kwargs)
+ def message(self, *args, **kwargs):
+ self._log(MESSAGE, *args, **kwargs)
+ def info(self, *args, **kwargs):
+ self._log(INFO, *args, **kwargs)
+
+ def warning(self, *args, **kwargs):
+ self._log(WARNING, *args, **kwargs)
+
+ def error(self, *args, **kwargs):
+ self._log(ERROR, *args, **kwargs)
def exception(self, *args, **kwargs):
msg = traceback.format_exc()
@@ -104,7 +82,9 @@ class Logger(object):
self._log(INFO, msg, **kwargs)
self._log(ERROR, *args, **kwargs)
+
class Log(object):
+
def __init__(self, args, ioloop, path):
self.path = path
self.listeners = []
@@ -121,29 +101,29 @@ class Log(object):
self._log('Log started v%s' % version)
self._log_time(ioloop)
+ def get_path(self):
+ return self.path
- def get_path(self): return self.path
+ def add_listener(self, listener):
+ self.listeners.append(listener)
- def add_listener(self, listener): self.listeners.append(listener)
- def remove_listener(self, listener): self.listeners.remove(listener)
+ def remove_listener(self, listener):
+ self.listeners.remove(listener)
-
- def get(self, name, level = None) -> Logger:
+ def get(self, name, level=None) -> Logger:
if not name in self.loggers:
self.loggers[name] = Logger(self, name, self.level)
return self.loggers[name]
-
def _log_time(self, ioloop):
self._log(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S'))
ioloop.call_later(60 * 60, self._log_time, ioloop)
-
def broadcast(self, msg):
- for listener in self.listeners: listener(msg)
+ for listener in self.listeners:
+ listener(msg)
-
- def _log(self, msg, level = INFO, prefix = '', where = None):
+ def _log(self, msg, level=INFO, prefix='', where=None):
if not msg: return
hdr = '%s:%s:' % ('DIMWE'[level], prefix)
@@ -160,11 +140,10 @@ class Log(object):
# Broadcast to log listeners
if level == INFO: return
- msg = dict(level = get_level_name(level), source = prefix, msg = msg)
+ msg = dict(level=get_level_name(level), source=prefix, msg=msg)
if where is not None: msg['where'] = where
- self.broadcast(dict(log = msg))
-
+ self.broadcast(dict(log=msg))
def _open(self):
if self.path is None: return
@@ -173,8 +152,7 @@ class Log(object):
self.f = open(self.path, 'a')
self.bytes_written = 0
-
- def _rotate(self, path, n = None):
+ def _rotate(self, path, n=None):
fullpath = '%s.%d' % (path, n) if n is not None else path
nextN = (0 if n is None else n) + 1
diff --git a/src/py/bbctrl/Mach.py b/src/py/bbctrl/Mach.py
index 6a4e464..6e64f2b 100644
--- a/src/py/bbctrl/Mach.py
+++ b/src/py/bbctrl/Mach.py
@@ -1,8 +1,7 @@
-import bbctrl
from bbctrl.Comm import Comm
+import bbctrl
import bbctrl.Cmd as Cmd
-
# Axis homing procedure:
#
# Mark axis unhomed
@@ -49,10 +48,11 @@ for more information.\
def overrides(interface_class):
+
def overrider(method):
if not method.__name__ in dir(interface_class):
- raise Exception('%s does not override %s' % (
- method.__name__, interface_class.__name__))
+ raise Exception('%s does not override %s' %
+ (method.__name__, interface_class.__name__))
return method
@@ -60,6 +60,7 @@ def overrides(interface_class):
class Mach(Comm):
+
def __init__(self, ctrl, avr):
super().__init__(ctrl, avr)
@@ -76,20 +77,32 @@ class Mach(Comm):
super().reboot()
- def _get_state(self): return self.ctrl.state.get('xx', '')
- def _is_estopped(self): return self._get_state() == 'ESTOPPED'
- def _is_holding(self): return self._get_state() == 'HOLDING'
- def _is_ready(self): return self._get_state() == 'READY'
- def _get_pause_reason(self): return self.ctrl.state.get('pr', '')
- def _get_cycle(self): return self.ctrl.state.get('cycle', 'idle')
+ def _get_state(self):
+ return self.ctrl.state.get('xx', '')
+
+ def _is_estopped(self):
+ return self._get_state() == 'ESTOPPED'
+
+ def _is_holding(self):
+ return self._get_state() == 'HOLDING'
+
+ def _is_ready(self):
+ return self._get_state() == 'READY'
+
+ def _get_pause_reason(self):
+ return self.ctrl.state.get('pr', '')
+
+ def _get_cycle(self):
+ return self.ctrl.state.get('cycle', 'idle')
def _is_paused(self):
if not self._is_holding() or self.unpausing:
return False
- return self._get_pause_reason() in (
- 'User pause', 'Program pause', 'Optional pause')
+ return self._get_pause_reason() in ('User pause', 'Program pause',
+ 'Optional pause')
- def _set_cycle(self, cycle): self.ctrl.state.set('cycle', cycle)
+ def _set_cycle(self, cycle):
+ self.ctrl.state.set('cycle', cycle)
def _begin_cycle(self, cycle):
current = self._get_cycle()
@@ -126,9 +139,8 @@ class Mach(Comm):
self.planner.reset(stop=False)
# Exit cycle if state changed to READY
- if (state_changed and self._get_cycle() != 'idle' and
- self._is_ready() and not self.planner.is_busy() and
- not super().is_active()):
+ if (state_changed and self._get_cycle() != 'idle' and self._is_ready()
+ and not self.planner.is_busy() and not super().is_active()):
self.planner.position_change()
self._set_cycle('idle')
@@ -152,9 +164,9 @@ class Mach(Comm):
# Must be after holding commands above
op = self.ctrl.state.get('optional_pause', False)
pr = self._get_pause_reason()
- if ((state_changed or 'pr' in update) and self._is_holding() and
- (pr in ('Switch found', 'User stop') or
- (pr == 'Optional pause' and not op))):
+ if ((state_changed or 'pr' in update) and self._is_holding()
+ and (pr in ('Switch found', 'User stop') or
+ (pr == 'Optional pause' and not op))):
self._unpause()
def _unpause(self):
@@ -174,7 +186,8 @@ class Mach(Comm):
def _i2c_block(self, block):
super().i2c_command(block[0], block=block[1:])
- def _i2c_set(self, name, value): self._i2c_block(Cmd.set(name, value))
+ def _i2c_set(self, name, value):
+ self._i2c_block(Cmd.set(name, value))
@overrides(Comm)
def comm_next(self):
@@ -255,8 +268,8 @@ class Mach(Comm):
# Error when axes cannot be homed
reason = state.axis_home_fail_reason(axis)
if reason is not None:
- self.mlog.error('Cannot home %s axis: %s' % (
- axis.upper(), reason))
+ self.mlog.error('Cannot home %s axis: %s' %
+ (axis.upper(), reason))
continue
if mode == 'manual':
@@ -279,8 +292,11 @@ class Mach(Comm):
self.planner.mdi(gcode, False)
super().resume()
- def unhome(self, axis): self.mdi('G28.2 %c0' % axis)
- def estop(self): super().estop()
+ def unhome(self, axis):
+ self.mdi('G28.2 %c0' % axis)
+
+ def estop(self):
+ super().estop()
def clear(self):
if self._is_estopped():
@@ -290,7 +306,8 @@ class Mach(Comm):
def fake_probe_contact(self):
self._i2c_set('pt', 2)
self.ctrl.state.set('pw', 0)
- self.timer = self.ctrl.ioloop.call_later(0.5, self.clear_fake_probe_contact)
+ self.timer = self.ctrl.ioloop.call_later(0.5,
+ self.clear_fake_probe_contact)
def clear_fake_probe_contact(self):
self._i2c_set('pt', 1)
@@ -316,7 +333,8 @@ class Mach(Comm):
self.stopping = True
super().i2c_command(Cmd.STOP)
- def pause(self): super().pause()
+ def pause(self):
+ super().pause()
def unpause(self):
if self._is_paused():
@@ -350,7 +368,8 @@ class Mach(Comm):
def override_speed(self, override):
self._i2c_set('so', int(1000 * override))
- def modbus_read(self, addr): self._i2c_block(Cmd.modbus_read(addr))
+ def modbus_read(self, addr):
+ self._i2c_block(Cmd.modbus_read(addr))
def modbus_write(self, addr, value):
self._i2c_block(Cmd.modbus_write(addr, value))
diff --git a/src/py/bbctrl/MonitorTemp.py b/src/py/bbctrl/MonitorTemp.py
index d208cd2..34d9226 100644
--- a/src/py/bbctrl/MonitorTemp.py
+++ b/src/py/bbctrl/MonitorTemp.py
@@ -1,30 +1,3 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
import time
@@ -35,10 +8,12 @@ def read_temp():
def set_max_freq(freq):
filename = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq'
- with open(filename, 'w') as f: f.write('%d\n' % freq)
+ with open(filename, 'w') as f:
+ f.write('%d\n' % freq)
class MonitorTemp(object):
+
def __init__(self, app):
self.app = app
@@ -57,7 +32,6 @@ class MonitorTemp(object):
self.callback()
-
# Scale max CPU based on temperature
def scale_cpu(self, temp):
if temp < self.min_temp: cpu_freq = self.max_freq
@@ -69,7 +43,6 @@ class MonitorTemp(object):
set_max_freq(cpu_freq)
-
def update_camera(self, temp):
if self.app.camera is None: return
@@ -78,7 +51,6 @@ class MonitorTemp(object):
elif self.high_camera_temp < temp:
self.app.camera.set_overtemp(True)
-
def log_warnings(self, temp):
# Reset temperature warning threshold after timeout
if time.time() < self.last_temp_warn + 60: self.temp_thresh = 80
@@ -89,7 +61,6 @@ class MonitorTemp(object):
self.log.info('Hot RaspberryPi at %d°C' % temp)
-
def callback(self):
try:
temp = read_temp()
@@ -99,6 +70,7 @@ class MonitorTemp(object):
self.update_camera(temp)
self.log_warnings(temp)
- except: self.log.exception('Internal error: Temperature status')
+ except:
+ self.log.exception('Internal error: Temperature status')
self.ioloop.call_later(5, self.callback)
diff --git a/src/py/bbctrl/Planner.py b/src/py/bbctrl/Planner.py
index fe27066..4fb77c0 100644
--- a/src/py/bbctrl/Planner.py
+++ b/src/py/bbctrl/Planner.py
@@ -1,46 +1,16 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
+from bbctrl.CommandQueue import CommandQueue
+import bbctrl.Cmd as Cmd
+import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error
import json
import math
import re
import time
-from collections import deque
-import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error
-import bbctrl.Cmd as Cmd
-from bbctrl.CommandQueue import CommandQueue
-
-reLogLine = re.compile(
- r'^(?P[A-Z])[0-9 ]:'
- r'((?P[^:]+):)?'
- r'((?P\d+):)?'
- r'((?P\d+):)?'
- r'(?P.*)$')
+reLogLine = re.compile(r'^(?P[A-Z])[0-9 ]:'
+ r'((?P[^:]+):)?'
+ r'((?P\d+):)?'
+ r'((?P\d+):)?'
+ r'(?P.*)$')
def log_floats(o):
@@ -50,10 +20,12 @@ def log_floats(o):
return o
-def log_json(o): return json.dumps(log_floats(o))
+def log_json(o):
+ return json.dumps(log_floats(o))
class Planner():
+
def __init__(self, ctrl):
self.ctrl = ctrl
self.log = ctrl.log.get('Planner')
@@ -64,21 +36,23 @@ class Planner():
ctrl.state.add_listener(self._update)
- self.reset(stop = False)
+ self.reset(stop=False)
self._report_time()
+ def is_busy(self):
+ return self.is_running() or self.cmdq.is_active()
- def is_busy(self): return self.is_running() or self.cmdq.is_active()
- def is_running(self): return self.planner.is_running()
- def position_change(self): self._position_dirty = True
+ def is_running(self):
+ return self.planner.is_running()
+ def position_change(self):
+ self._position_dirty = True
- def _sync_position(self, force = False):
+ def _sync_position(self, force=False):
if not force and not self._position_dirty: return
self._position_dirty = False
self.planner.set_position(self.ctrl.state.get_position())
-
def get_config(self, mdi, with_limits):
state = self.ctrl.state
config = self.ctrl.config
@@ -88,21 +62,22 @@ class Planner():
cfg = {
# NOTE Must get current units not configured default units
'default-units': 'METRIC' if state.get('metric') else 'IMPERIAL',
- 'max-vel': state.get_axis_vector('vm', 1000),
+ 'max-vel': state.get_axis_vector('vm', 1000),
'max-accel': state.get_axis_vector('am', 1000000),
- 'max-jerk': state.get_axis_vector('jm', 1000000),
- 'rapid-auto-off': config.get('rapid-auto-off') and is_pwm,
+ 'max-jerk': state.get_axis_vector('jm', 1000000),
+ 'rapid-auto-off': config.get('rapid-auto-off') and is_pwm,
'max-blend-error': deviation,
'max-merge-error': deviation,
- 'max-arc-error': deviation / 10,
- 'junction-accel': config.get('junction-accel'),
+ 'max-arc-error': deviation / 10,
+ 'junction-accel': config.get('junction-accel'),
}
# We place an upper limit of 1000 km/min^3 on jerk for MDI movements
if mdi:
for axis in 'xyzabc':
if axis in cfg['max-jerk']:
- cfg['max-jerk'][axis] = min(1000 * 1000000, cfg['max-jerk'][axis])
+ cfg['max-jerk'][axis] = min(1000 * 1000000,
+ cfg['max-jerk'][axis])
if with_limits:
minLimit = state.get_soft_limit_vector('tn', -math.inf)
@@ -134,13 +109,11 @@ class Planner():
return cfg
-
def _update(self, update):
if 'id' in update:
id = update['id']
- self.planner.set_active(id) # Release planner commands
- self.cmdq.release(id) # Synchronize planner variables
-
+ self.planner.set_active(id) # Release planner commands
+ self.cmdq.release(id) # Synchronize planner variables
def _get_var_cb(self, name, units):
value = 0
@@ -149,37 +122,36 @@ class Planner():
value = self.ctrl.state.get(name[1:], 0)
try:
float(value)
- if units == 'IMPERIAL': value /= 25.4 # Assume metric
- except ValueError: value = 0
+ if units == 'IMPERIAL': value /= 25.4 # Assume metric
+ except ValueError:
+ value = 0
self.log.info('Get: %s=%s (units=%s)' % (name, value, units))
return value
-
def _log_cb(self, line):
line = line.strip()
m = reLogLine.match(line)
if not m: return
- level = m.group('level')
- msg = m.group('msg')
+ level = m.group('level')
+ msg = m.group('msg')
filename = m.group('file')
- line = m.group('line')
- column = m.group('column')
+ line = m.group('line')
+ column = m.group('column')
where = ':'.join(filter(None.__ne__, [filename, line, column]))
if line is not None: line = int(line)
if column is not None: column = int(column)
- if level == 'I': self.log.info (msg, where = where)
- elif level == 'D': self.log.debug (msg, where = where)
- elif level == 'W': self.log.warning (msg, where = where)
- elif level == 'E': self.log.error (msg, where = where)
+ if level == 'I': self.log.info(msg, where=where)
+ elif level == 'D': self.log.debug(msg, where=where)
+ elif level == 'W': self.log.warning(msg, where=where)
+ elif level == 'E': self.log.error(msg, where=where)
else: self.log.error('Could not parse planner log line: ' + line)
-
def _add_message(self, text):
self.ctrl.state.add_message(text)
@@ -187,14 +159,12 @@ class Planner():
if 0 <= line: where = '%s:%d' % (self.where, line)
else: where = self.where
- self.log.message(text, where = where)
-
+ self.log.message(text, where=where)
def _enqueue_set_cmd(self, id, name, value):
self.log.info('set(#%d, %s, %s)', id, name, value)
self.cmdq.enqueue(id, self.ctrl.state.set, name, value)
-
def _report_time(self):
state = self.ctrl.state.get('xx', '')
@@ -205,39 +175,35 @@ class Planner():
self.ctrl.state.set('plan_time', round(plan_time))
- elif state != 'HOLDING': self.ctrl.state.set('plan_time', 0)
+ elif state != 'HOLDING':
+ self.ctrl.state.set('plan_time', 0)
self.ctrl.ioloop.call_later(1, self._report_time)
-
def _plan_time_restart(self):
self.plan_time = self.ctrl.state.get('plan_time', 0)
-
def _update_time(self, plan_time, move_time):
self.current_plan_time = plan_time
self.move_time = move_time
self.move_start = time.time()
-
def _enqueue_line_time(self, block):
if block.get('first', False) or block.get('seeking', False): return
# Sum move times
- move_time = sum(block['times']) / 1000 # To seconds
+ move_time = sum(block['times']) / 1000 # To seconds
self.cmdq.enqueue(block['id'], self._update_time, self.plan_time,
move_time)
self.plan_time += move_time
-
def _enqueue_dwell_time(self, block):
self.cmdq.enqueue(block['id'], self._update_time, self.plan_time,
block['seconds'])
self.plan_time += block['seconds']
-
def __encode(self, block):
type, id = block['type'], block['id']
@@ -266,7 +232,7 @@ class Planner():
if len(name) != 2 or name[1] not in 'xyzabc':
self._enqueue_set_cmd(id, name[1:], value)
- if name == '_feed': # Must come after _enqueue_set_cmd() above
+ if name == '_feed': # Must come after _enqueue_set_cmd() above
return Cmd.set_sync('if', 1 / value if value else 0)
if name[0:1] == '_' and name[1:2] in 'xyzabc':
@@ -281,7 +247,7 @@ class Planner():
if type == 'input':
# TODO handle timeout
- self.planner.synchronize(0) # TODO Fix this
+ self.planner.synchronize(0) # TODO Fix this
return Cmd.input(block['port'], block['mode'], block['timeout'])
if type == 'output':
@@ -297,11 +263,10 @@ class Planner():
sw = self.ctrl.state.get_switch_id(block['switch'])
return Cmd.seek(sw, block['active'], block['error'])
- if type == 'end': return '' # Sends id
+ if type == 'end': return '' # Sends id
raise Exception('Unknown planner command "%s"' % type)
-
def _encode(self, block):
cmd = self.__encode(block)
@@ -309,21 +274,18 @@ class Planner():
self.cmdq.enqueue(block['id'], None)
return Cmd.set_sync('id', block['id']) + '\n' + cmd
-
def reset_times(self):
self.move_start = 0
self.move_time = 0
self.plan_time = 0
self.current_plan_time = 0
-
def close(self):
# Release planner callbacks
if self.planner is not None:
self.planner.set_resolver(None)
self.planner.set_logger(None)
-
def reset(self, *args, **kwargs):
stop = kwargs.get('stop', True)
if stop:
@@ -341,15 +303,13 @@ class Planner():
if resetState:
self.ctrl.state.reset()
-
- def mdi(self, cmd, with_limits = True):
+ def mdi(self, cmd, with_limits=True):
self.where = ''
self.log.info('MDI:' + cmd)
self._sync_position()
self.planner.load_string(cmd, self.get_config(True, with_limits))
self.reset_times()
-
def load(self, path):
self.where = path
path = self.ctrl.get_path('upload', path)
@@ -358,7 +318,6 @@ class Planner():
self.planner.load(path, self.get_config(False, True))
self.reset_times()
-
def stop(self):
try:
self.planner.stop()
@@ -368,7 +327,6 @@ class Planner():
self.log.exception('Internal error: Planner stop')
self.reset()
-
def restart(self):
try:
id = self.ctrl.state.get('id')
@@ -385,7 +343,6 @@ class Planner():
self.log.exception('Internal error: Planner restart')
self.stop()
-
def next(self):
try:
while self.planner.has_more():
diff --git a/src/py/bbctrl/Preplanner.py b/src/py/bbctrl/Preplanner.py
index 094fe9a..1b3b5ce 100644
--- a/src/py/bbctrl/Preplanner.py
+++ b/src/py/bbctrl/Preplanner.py
@@ -1,44 +1,16 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import os
-import time
-import json
-import hashlib
-import glob
-import tempfile
-import signal
from concurrent.futures import Future
from tornado import gen, process, iostream
import bbctrl
+import glob
+import hashlib
+import json
+import os
+import signal
+import tempfile
def hash_dump(o):
- s = json.dumps(o, separators = (',', ':'), sort_keys = True)
+ s = json.dumps(o, separators=(',', ':'), sort_keys=True)
return s.encode('utf8')
@@ -59,10 +31,12 @@ def plan_hash(path, config):
def safe_remove(path):
try:
os.unlink(path)
- except: pass
+ except:
+ pass
class Plan(object):
+
def __init__(self, preplanner, ctrl, filename):
self.preplanner = preplanner
@@ -81,29 +55,27 @@ class Plan(object):
self.hid = plan_hash(self.gcode, self.config)
fbase = '%s.%s.' % (self.base, self.hid)
self.files = [
- fbase + 'json',
- fbase + 'positions.gz',
- fbase + 'speeds.gz']
+ fbase + 'json', fbase + 'positions.gz', fbase + 'speeds.gz'
+ ]
self.future = Future()
ctrl.ioloop.add_callback(self._load)
-
def terminate(self):
if self.cancel: return
self.cancel = True
if self.pid is not None:
try:
os.kill(self.pid, signal.SIGKILL)
- except: pass
-
+ except:
+ pass
def delete(self):
files = glob.glob(self.base + '.*')
- for path in files: safe_remove(path)
+ for path in files:
+ safe_remove(path)
-
- def clean(self, max = 2):
+ def clean(self, max=2):
plans = glob.glob(self.base + '.*.json')
if len(plans) <= max: return
@@ -116,20 +88,21 @@ class Plan(object):
safe_remove(path[:-4] + 'positions.gz')
safe_remove(path[:-4] + 'speeds.gz')
-
def _exists(self):
for path in self.files:
if not os.path.exists(path): return False
return True
-
def _read(self):
if self.cancel: return
try:
- with open(self.files[0], 'r') as f: meta = json.load(f)
- with open(self.files[1], 'rb') as f: positions = f.read()
- with open(self.files[2], 'rb') as f: speeds = f.read()
+ with open(self.files[0], 'r') as f:
+ meta = json.load(f)
+ with open(self.files[1], 'rb') as f:
+ positions = f.read()
+ with open(self.files[2], 'rb') as f:
+ speeds = f.read()
return meta, positions, speeds
@@ -141,26 +114,23 @@ class Plan(object):
if os.path.exists(path):
os.remove(path)
-
@gen.coroutine
def _exec(self):
- self.clean() # Clean up old plans
+ self.clean() # Clean up old plans
with tempfile.TemporaryDirectory() as tmpdir:
- cmd = (
- '/usr/bin/env', 'python3',
- bbctrl.get_resource('plan.py'),
- os.path.abspath(self.gcode), json.dumps(self.state),
- json.dumps(self.config),
- '--max-time=%s' % self.preplanner.max_plan_time,
- '--max-loop=%s' % self.preplanner.max_loop_time
- )
+ cmd = ('/usr/bin/env', 'python3', bbctrl.get_resource('plan.py'),
+ os.path.abspath(self.gcode), json.dumps(self.state),
+ json.dumps(self.config),
+ '--max-time=%s' % self.preplanner.max_plan_time,
+ '--max-loop=%s' % self.preplanner.max_loop_time)
self.preplanner.log.info('Running: %s', cmd)
- proc = process.Subprocess(cmd, stdout = process.Subprocess.STREAM,
- stderr = process.Subprocess.STREAM,
- cwd = tmpdir)
+ proc = process.Subprocess(cmd,
+ stdout=process.Subprocess.STREAM,
+ stderr=process.Subprocess.STREAM,
+ cwd=tmpdir)
errs = ''
self.pid = proc.proc.pid
@@ -170,7 +140,8 @@ class Plan(object):
line = yield proc.stdout.read_until(b'\n')
self.progress = float(line.strip())
if self.cancel: return
- except iostream.StreamClosedError: pass
+ except iostream.StreamClosedError:
+ pass
self.progress = 1
@@ -184,12 +155,11 @@ class Plan(object):
proc.stdout.close()
if not self.cancel:
- os.rename(tmpdir + '/meta.json', self.files[0])
+ os.rename(tmpdir + '/meta.json', self.files[0])
os.rename(tmpdir + '/positions.gz', self.files[1])
- os.rename(tmpdir + '/speeds.gz', self.files[2])
+ os.rename(tmpdir + '/speeds.gz', self.files[2])
os.sync()
-
@gen.coroutine
def _load(self):
try:
@@ -203,11 +173,13 @@ class Plan(object):
self.future.set_result(self._read())
except:
- self.preplanner.log.exception("Failed to load file - doesn't appear to be GCode.")
+ self.preplanner.log.exception(
+ "Failed to load file - doesn't appear to be GCode.")
class Preplanner(object):
- def __init__(self, ctrl, max_plan_time = 60 * 60 * 24, max_loop_time = 300):
+
+ def __init__(self, ctrl, max_plan_time=60 * 60 * 24, max_loop_time=300):
self.ctrl = ctrl
self.log = ctrl.log.get('Preplanner')
@@ -220,31 +192,27 @@ class Preplanner(object):
self.started = Future()
self.plans = {}
-
def start(self):
if not self.started.done():
self.log.info('Preplanner started')
self.started.set_result(True)
-
def invalidate(self, filename):
if filename in self.plans:
self.plans[filename].terminate()
del self.plans[filename]
-
def invalidate_all(self):
for filename, plan in self.plans.items():
plan.terminate()
self.plans = {}
-
def delete_all_plans(self):
files = glob.glob(self.ctrl.get_plan('*'))
- for path in files: safe_remove(path)
+ for path in files:
+ safe_remove(path)
self.invalidate_all()
-
def delete_plans(self, filename):
if filename in self.plans:
self.plans[filename].delete()
@@ -265,6 +233,5 @@ class Preplanner(object):
data = yield plan.future
return data
-
def get_plan_progress(self, filename):
return self.plans[filename].progress if filename in self.plans else 0
diff --git a/src/py/bbctrl/Pwr.py b/src/py/bbctrl/Pwr.py
index 32b46c6..10a4d60 100644
--- a/src/py/bbctrl/Pwr.py
+++ b/src/py/bbctrl/Pwr.py
@@ -1,67 +1,39 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import bbctrl
import bbctrl.Cmd as Cmd
-
# Must match regs in pwr firmware
-TEMP_REG = 0
-VIN_REG = 1
-VOUT_REG = 2
-MOTOR_REG = 3
-LOAD1_REG = 4
-LOAD2_REG = 5
-VDD_REG = 6
-FLAGS_REG = 7
-VERSION_REG = 8
+TEMP_REG = 0
+VIN_REG = 1
+VOUT_REG = 2
+MOTOR_REG = 3
+LOAD1_REG = 4
+LOAD2_REG = 5
+VDD_REG = 6
+FLAGS_REG = 7
+VERSION_REG = 8
# Must be kept in sync with pwr firmware
-UNDER_VOLTAGE_FLAG = 1 << 0
-OVER_VOLTAGE_FLAG = 1 << 1
-OVER_CURRENT_FLAG = 1 << 2
-SENSE_ERROR_FLAG = 1 << 3
-SHUNT_OVERLOAD_FLAG = 1 << 4
-MOTOR_OVERLOAD_FLAG = 1 << 5
-LOAD1_SHUTDOWN_FLAG = 1 << 6
-LOAD2_SHUTDOWN_FLAG = 1 << 7
-MOTOR_UNDER_VOLTAGE_FLAG = 1 << 8
+UNDER_VOLTAGE_FLAG = 1 << 0
+OVER_VOLTAGE_FLAG = 1 << 1
+OVER_CURRENT_FLAG = 1 << 2
+SENSE_ERROR_FLAG = 1 << 3
+SHUNT_OVERLOAD_FLAG = 1 << 4
+MOTOR_OVERLOAD_FLAG = 1 << 5
+LOAD1_SHUTDOWN_FLAG = 1 << 6
+LOAD2_SHUTDOWN_FLAG = 1 << 7
+MOTOR_UNDER_VOLTAGE_FLAG = 1 << 8
MOTOR_VOLTAGE_SENSE_ERROR_FLAG = 1 << 9
MOTOR_CURRENT_SENSE_ERROR_FLAG = 1 << 10
-LOAD1_SENSE_ERROR_FLAG = 1 << 11
-LOAD2_SENSE_ERROR_FLAG = 1 << 12
-VDD_CURRENT_SENSE_ERROR_FLAG = 1 << 13
-POWER_SHUTDOWN_FLAG = 1 << 14
-SHUNT_ERROR_FLAG = 1 << 15
+LOAD1_SENSE_ERROR_FLAG = 1 << 11
+LOAD2_SENSE_ERROR_FLAG = 1 << 12
+VDD_CURRENT_SENSE_ERROR_FLAG = 1 << 13
+POWER_SHUTDOWN_FLAG = 1 << 14
+SHUNT_ERROR_FLAG = 1 << 15
reg_names = 'temp vin vout motor load1 load2 vdd pwr_flags pwr_version'.split()
class Pwr():
+
def __init__(self, ctrl):
self.ctrl = ctrl
self.log = ctrl.log.get('Pwr')
@@ -72,7 +44,6 @@ class Pwr():
self._update_cb(False)
-
def check_fault(self, var, status):
status = bool(status)
@@ -82,7 +53,6 @@ class Pwr():
return False
-
def check_faults(self):
flags = self.regs[FLAGS_REG]
@@ -141,19 +111,17 @@ class Pwr():
if self.check_fault('shunt_error', flags & SHUNT_ERROR_FLAG):
self.log.warning('Shunt error')
-
- def _update_cb(self, now = True):
+ def _update_cb(self, now=True):
if now: self._update()
self.ctrl.ioloop.call_later(1, self._update_cb)
-
def _update(self):
update = {}
try:
for i in range(len(self.regs)):
value = self.ctrl.i2c.read_word(self.i2c_addr + i)
- if value is None: return # Handle lack of i2c port
+ if value is None: return # Handle lack of i2c port
if i == TEMP_REG: value -= 273
elif i == FLAGS_REG or i == VERSION_REG: pass
@@ -169,7 +137,7 @@ class Pwr():
if i == FLAGS_REG: self.check_faults()
except Exception as e:
- if i < 6: # Older pwr firmware does not have regs > 5
+ if i < 6: # Older pwr firmware does not have regs > 5
self.failures += 1
msg = 'Pwr communication failed at reg %d: %s' % (i, e)
if self.failures != 5: self.log.info(msg)
diff --git a/src/py/bbctrl/RequestHandler.py b/src/py/bbctrl/RequestHandler.py
index 771b4fd..99f6b5d 100644
--- a/src/py/bbctrl/RequestHandler.py
+++ b/src/py/bbctrl/RequestHandler.py
@@ -1,59 +1,32 @@
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import traceback
-import bbctrl
-
from tornado.web import HTTPError
+import bbctrl
import tornado.web
+import traceback
class RequestHandler(tornado.web.RequestHandler):
+
def __init__(self, app, request, **kwargs):
super().__init__(app, request, **kwargs)
self.app = app
+ def get_ctrl(self):
+ return self.app.get_ctrl(self.get_cookie('client-id'))
- def get_ctrl(self): return self.app.get_ctrl(self.get_cookie('client-id'))
- def get_log(self, name = 'API'): return self.get_ctrl().log.get(name)
+ def get_log(self, name='API'):
+ return self.get_ctrl().log.get(name)
-
- def get_path(self, path = None, filename = None):
+ def get_path(self, path=None, filename=None):
return self.get_ctrl().get_path(path, filename)
-
- def get_upload(self, filename = None):
+ def get_upload(self, filename=None):
return self.get_ctrl().get_upload(filename)
-
# Override exception logging
def log_exception(self, typ, value, tb):
- if (isinstance(value, HTTPError) and
- 400 <= value.status_code and value.status_code < 500): return
+ if (isinstance(value, HTTPError) and 400 <= value.status_code
+ and value.status_code < 500):
+ return
log = self.get_log()
log.set_level(bbctrl.log.DEBUG)
diff --git a/src/py/bbctrl/State.py b/src/py/bbctrl/State.py
index 03e8a24..b988850 100644
--- a/src/py/bbctrl/State.py
+++ b/src/py/bbctrl/State.py
@@ -1,15 +1,15 @@
-import traceback
+from watchdog.events import FileSystemEventHandler
+from watchdog.observers import Observer
import copy
+import iw_parse
import json
-import uuid
import os
import socket
-import iw_parse
-import threading
import subprocess
+import threading
import time
-from watchdog.observers import Observer
-from watchdog.events import FileSystemEventHandler
+import traceback
+import uuid
def call_get_output(cmd):
diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py
index bfb8679..cce7869 100644
--- a/src/py/bbctrl/Web.py
+++ b/src/py/bbctrl/Web.py
@@ -1,14 +1,13 @@
+from tornado import gen
+from tornado.web import HTTPError
+import bbctrl
+import datetime
import os
import re
-import tornado
-import sockjs.tornado
-import datetime
-import subprocess
import socket
-from tornado.web import HTTPError
-from tornado import gen
-
-import bbctrl
+import sockjs.tornado
+import subprocess
+import tornado
def call_get_output(cmd):
@@ -20,6 +19,7 @@ def call_get_output(cmd):
class RebootHandler(bbctrl.APIHandler):
+
def put_ok(self):
subprocess.Popen(['plymouth', 'show-splash'])
subprocess.Popen(['plymouth', 'change-mode', '--shutdown'])
@@ -28,6 +28,7 @@ class RebootHandler(bbctrl.APIHandler):
class ShutdownHandler(bbctrl.APIHandler):
+
def put_ok(self):
subprocess.Popen(['plymouth', 'show-splash'])
subprocess.Popen(['plymouth', 'change-mode', '--shutdown'])
@@ -36,6 +37,7 @@ class ShutdownHandler(bbctrl.APIHandler):
class LogHandler(bbctrl.RequestHandler):
+
def get(self):
with open(self.get_ctrl().log.get_path(), 'r') as f:
self.write(f.read())
@@ -48,11 +50,13 @@ class LogHandler(bbctrl.RequestHandler):
class MessageAckHandler(bbctrl.APIHandler):
+
def put_ok(self, id):
self.get_ctrl().state.ack_message(int(id))
class BugReportHandler(bbctrl.RequestHandler):
+
def get(self):
import tarfile
import io
@@ -91,13 +95,15 @@ class BugReportHandler(bbctrl.RequestHandler):
class HostnameHandler(bbctrl.APIHandler):
+
def put(self):
if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot set hostname in demo mode')
if 'hostname' in self.json:
- if subprocess.call(['/usr/local/bin/sethostname',
- self.json['hostname'].strip()]) == 0:
+ if subprocess.call(
+ ['/usr/local/bin/sethostname',
+ self.json['hostname'].strip()]) == 0:
self.write_json('ok')
return
@@ -105,6 +111,7 @@ class HostnameHandler(bbctrl.APIHandler):
class NetworkHandler(bbctrl.APIHandler):
+
def put(self):
if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot configure WiFi in demo mode')
@@ -133,11 +140,13 @@ class NetworkHandler(bbctrl.APIHandler):
class ConfigLoadHandler(bbctrl.APIHandler):
+
def get(self):
self.write_json(self.get_ctrl().config.load())
class ConfigDownloadHandler(bbctrl.APIHandler):
+
def set_default_headers(self):
fmt = socket.gethostname() + '-%Y%m%d.json'
filename = datetime.date.today().strftime(fmt)
@@ -150,15 +159,21 @@ class ConfigDownloadHandler(bbctrl.APIHandler):
class ConfigSaveHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().config.save(self.json)
+
+ def put_ok(self):
+ self.get_ctrl().config.save(self.json)
class ConfigResetHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().config.reset()
+
+ def put_ok(self):
+ self.get_ctrl().config.reset()
class FirmwareUpdateHandler(bbctrl.APIHandler):
- def prepare(self): pass
+
+ def prepare(self):
+ pass
def put_ok(self):
if not 'firmware' in self.request.files:
@@ -176,11 +191,13 @@ class FirmwareUpdateHandler(bbctrl.APIHandler):
class UpgradeHandler(bbctrl.APIHandler):
+
def put_ok(self):
subprocess.Popen(['/usr/local/bin/upgrade-bbctrl'])
class PathHandler(bbctrl.APIHandler):
+
@gen.coroutine
def get(self, filename, dataType, *args):
if not os.path.exists(self.get_upload(filename)):
@@ -230,6 +247,7 @@ class PathHandler(bbctrl.APIHandler):
class HomeHandler(bbctrl.APIHandler):
+
def put_ok(self, axis, action, *args):
if axis is not None:
axis = ord(axis[1:2].lower())
@@ -247,62 +265,86 @@ class HomeHandler(bbctrl.APIHandler):
class StartHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.start()
+
+ def put_ok(self):
+ self.get_ctrl().mach.start()
class EStopHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.estop()
+
+ def put_ok(self):
+ self.get_ctrl().mach.estop()
class ClearHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.clear()
+
+ def put_ok(self):
+ self.get_ctrl().mach.clear()
class StopHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.stop()
+
+ def put_ok(self):
+ self.get_ctrl().mach.stop()
class PauseHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.pause()
+
+ def put_ok(self):
+ self.get_ctrl().mach.pause()
class UnpauseHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.unpause()
+
+ def put_ok(self):
+ self.get_ctrl().mach.unpause()
class OptionalPauseHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.optional_pause()
+
+ def put_ok(self):
+ self.get_ctrl().mach.optional_pause()
class StepHandler(bbctrl.APIHandler):
- def put_ok(self): self.get_ctrl().mach.step()
+
+ def put_ok(self):
+ self.get_ctrl().mach.step()
class PositionHandler(bbctrl.APIHandler):
+
def put_ok(self, axis):
self.get_ctrl().mach.set_position(axis, float(self.json['position']))
class OverrideFeedHandler(bbctrl.APIHandler):
- def put_ok(self, value): self.get_ctrl().mach.override_feed(float(value))
+
+ def put_ok(self, value):
+ self.get_ctrl().mach.override_feed(float(value))
class OverrideSpeedHandler(bbctrl.APIHandler):
- def put_ok(self, value): self.get_ctrl().mach.override_speed(float(value))
+
+ def put_ok(self, value):
+ self.get_ctrl().mach.override_speed(float(value))
class ModbusReadHandler(bbctrl.APIHandler):
+
def put_ok(self):
self.get_ctrl().mach.modbus_read(int(self.json['address']))
class ModbusWriteHandler(bbctrl.APIHandler):
+
def put_ok(self):
self.get_ctrl().mach.modbus_write(int(self.json['address']),
int(self.json['value']))
class JogHandler(bbctrl.APIHandler):
+
def put_ok(self):
# Handle possible out of order jog command processing
if 'ts' in self.json:
@@ -323,12 +365,14 @@ class JogHandler(bbctrl.APIHandler):
displayRotatePattern = re.compile(r'display_rotate\s*=\s*(\d)')
transformationMatrixPattern = re.compile(
- r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(\s+Option\s+\"TransformationMatrix\".*?\n)(.*?EndSection)', re.DOTALL)
+ r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(\s+Option\s+\"TransformationMatrix\".*?\n)(.*?EndSection)',
+ re.DOTALL)
matchIsTouchscreenPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(.*?EndSection)', re.DOTALL)
class ScreenRotationHandler(bbctrl.APIHandler):
+
@gen.coroutine
def get(self):
with open("/boot/config.txt", 'rt') as config:
@@ -336,7 +380,8 @@ class ScreenRotationHandler(bbctrl.APIHandler):
for line in lines:
if line.startswith('display_rotate'):
self.write_json({
- 'rotated': int(displayRotatePattern.search(line).group(1)) != 0
+ 'rotated':
+ int(displayRotatePattern.search(line).group(1)) != 0
})
return
@@ -347,33 +392,36 @@ class ScreenRotationHandler(bbctrl.APIHandler):
def put_ok(self):
rotated = self.json['rotated']
- subprocess.Popen(
- ['/usr/local/bin/edit-boot-config', 'display_rotate={}'.format(2 if rotated else 0)])
+ subprocess.Popen([
+ '/usr/local/bin/edit-boot-config',
+ 'display_rotate={}'.format(2 if rotated else 0)
+ ])
- with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'rt') as config:
+ with open("/usr/share/X11/xorg.conf.d/40-libinput.conf",
+ 'rt') as config:
text = config.read()
text = transformationMatrixPattern.sub(r'\1\2\3\5', text)
if rotated:
text = matchIsTouchscreenPattern.sub(
- r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4', text)
- with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'wt') as config:
+ r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4',
+ text)
+ with open("/usr/share/X11/xorg.conf.d/40-libinput.conf",
+ 'wt') as config:
config.write(text)
subprocess.run('reboot')
class TimeHandler(bbctrl.APIHandler):
+
def get(self):
timeinfo = call_get_output(['timedatectl'])
timezones = call_get_output(
['timedatectl', 'list-timezones', '--no-pager'])
- self.get_log('TimeHandler').info(
- 'Time stuff: {}, {}'.format(timeinfo, timezones))
+ self.get_log('TimeHandler').info('Time stuff: {}, {}'.format(
+ timeinfo, timezones))
- self.write_json({
- 'timeinfo': timeinfo,
- 'timezones': timezones
- })
+ self.write_json({'timeinfo': timeinfo, 'timezones': timezones})
def put_ok(self):
datetime = self.json['datetime']
@@ -384,6 +432,7 @@ class TimeHandler(bbctrl.APIHandler):
# Base class for Web Socket connections
class ClientConnection(object):
+
def __init__(self, app):
self.app = app
self.count = 0
@@ -393,7 +442,8 @@ class ClientConnection(object):
self.send({'heartbeat': self.count})
self.count += 1
- def send(self, msg): raise HTTPError(400, 'Not implemented')
+ def send(self, msg):
+ raise HTTPError(400, 'Not implemented')
def on_open(self, id=None):
self.ctrl = self.app.get_ctrl(id)
@@ -417,10 +467,11 @@ class ClientConnection(object):
# Used by CAMotics
class WSConnection(ClientConnection, tornado.websocket.WebSocketHandler):
+
def __init__(self, app, request, **kwargs):
ClientConnection.__init__(self, app)
- tornado.websocket.WebSocketHandler.__init__(
- self, app, request, **kwargs)
+ tornado.websocket.WebSocketHandler.__init__(self, app, request,
+ **kwargs)
def send(self, msg):
self.write_message(msg)
@@ -431,6 +482,7 @@ class WSConnection(ClientConnection, tornado.websocket.WebSocketHandler):
# Used by Web frontend
class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
+
def __init__(self, session):
ClientConnection.__init__(self, session.server.app)
sockjs.tornado.SockJSConnection.__init__(self, session)
@@ -451,18 +503,20 @@ class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
ip = info.ip
if 'X-Real-IP' in info.headers:
ip = info.headers['X-Real-IP']
- self.app.get_ctrl(id).log.get(
- 'Web').info('Connection from %s' % ip)
+ self.app.get_ctrl(id).log.get('Web').info('Connection from %s' %
+ ip)
super().on_open(id)
class StaticFileHandler(tornado.web.StaticFileHandler):
+
def set_extra_headers(self, path):
self.set_header('Cache-Control',
'no-store, no-cache, must-revalidate, max-age=0')
class Web(tornado.web.Application):
+
def __init__(self, args, ioloop):
self.args = args
self.ioloop = ioloop
@@ -518,9 +572,10 @@ class Web(tornado.web.Application):
(r'/api/video', bbctrl.VideoHandler),
(r'/api/screen-rotation', ScreenRotationHandler),
(r'/api/time', TimeHandler),
- (r'/(.*)', StaticFileHandler,
- {'path': bbctrl.get_resource('http/'),
- 'default_filename': 'index.html'}),
+ (r'/(.*)', StaticFileHandler, {
+ 'path': bbctrl.get_resource('http/'),
+ 'default_filename': 'index.html'
+ }),
]
router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs')
@@ -532,8 +587,8 @@ class Web(tornado.web.Application):
self.listen(args.port, address=args.addr)
except Exception as e:
- raise Exception('Failed to bind %s:%d: %s' % (
- args.addr, args.port, e))
+ raise Exception('Failed to bind %s:%d: %s' %
+ (args.addr, args.port, e))
print('Listening on http://%s:%d/' % (args.addr, args.port))
diff --git a/src/py/bbctrl/__init__.py b/src/py/bbctrl/__init__.py
index 3b5c03f..93754e9 100644
--- a/src/py/bbctrl/__init__.py
+++ b/src/py/bbctrl/__init__.py
@@ -1,36 +1,35 @@
#!/usr/bin/env python3
-import sys
-import signal
-import tornado
import argparse
import datetime
+import signal
+import sys
+import tornado
from pkg_resources import Requirement, resource_filename
-from bbctrl.RequestHandler import RequestHandler
from bbctrl.APIHandler import APIHandler
-from bbctrl.FileHandler import FileHandler
-from bbctrl.Config import Config
-from bbctrl.Mach import Mach
-from bbctrl.Web import Web
-from bbctrl.Jog import Jog
-from bbctrl.Ctrl import Ctrl
-from bbctrl.Pwr import Pwr
-from bbctrl.I2C import I2C
-from bbctrl.Planner import Planner
-from bbctrl.Preplanner import Preplanner
-from bbctrl.State import State
+from bbctrl.AVR import AVR
+from bbctrl.Camera import Camera, VideoHandler
from bbctrl.Comm import Comm
from bbctrl.CommandQueue import CommandQueue
-from bbctrl.Camera import Camera, VideoHandler
-from bbctrl.AVR import AVR
+from bbctrl.Config import Config
+from bbctrl.Ctrl import Ctrl
+from bbctrl.FileHandler import FileHandler
+from bbctrl.I2C import I2C
from bbctrl.IOLoop import IOLoop
+from bbctrl.Jog import Jog
+from bbctrl.Mach import Mach
from bbctrl.MonitorTemp import MonitorTemp
+from bbctrl.Planner import Planner
+from bbctrl.Preplanner import Preplanner
+from bbctrl.Pwr import Pwr
+from bbctrl.RequestHandler import RequestHandler
+from bbctrl.State import State
+from bbctrl.Web import Web
import bbctrl.Cmd as Cmd
-import bbctrl.v4l2 as v4l2
import bbctrl.Log as log
-
+import bbctrl.v4l2 as v4l2
ctrl = None
@@ -59,41 +58,61 @@ 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',
+ 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',
+ parser.add_argument('-s',
+ '--serial',
+ default='/dev/ttyAMA0',
help='Serial device')
- parser.add_argument('-b', '--baud', default=230400, type=int,
+ parser.add_argument('-b',
+ '--baud',
+ default=230400,
+ type=int,
help='Serial baud rate')
- parser.add_argument('--i2c-port', default=1, type=int,
- help='I2C port')
- parser.add_argument('--avr-addr', default=0x2b, type=int,
+ parser.add_argument('--i2c-port', default=1, type=int, help='I2C port')
+ parser.add_argument('--avr-addr',
+ default=0x2b,
+ type=int,
help='AVR I2C address')
- parser.add_argument('--pwr-addr', default=0x60, type=int,
+ parser.add_argument('--pwr-addr',
+ default=0x60,
+ type=int,
help='Power AVR I2C address')
- parser.add_argument('-v', '--verbose', action='store_true',
+ parser.add_argument('-v',
+ '--verbose',
+ action='store_true',
help='Verbose output')
- parser.add_argument('-l', '--log', metavar="FILE",
- help='Set a log file')
- parser.add_argument('--disable-camera', action='store_true',
+ parser.add_argument('-l', '--log', metavar="FILE", help='Set a log file')
+ parser.add_argument('--disable-camera',
+ action='store_true',
help='Disable the camera')
- parser.add_argument('--width', default=640, type=int,
- help='Camera width')
- parser.add_argument('--height', default=480, type=int,
+ parser.add_argument('--width', default=640, type=int, help='Camera width')
+ parser.add_argument('--height',
+ default=480,
+ type=int,
help='Camera height')
- parser.add_argument('--fps', default=15, type=int,
+ parser.add_argument('--fps',
+ default=15,
+ type=int,
help='Camera frames per second')
- parser.add_argument('--camera-clients', default=4,
+ parser.add_argument('--camera-clients',
+ default=4,
help='Maximum simultaneous camera clients')
- parser.add_argument('--demo', action='store_true',
- help='Enter demo mode')
- parser.add_argument('--debug', default=0, type=int,
+ parser.add_argument('--demo', action='store_true', help='Enter demo mode')
+ parser.add_argument('--debug',
+ default=0,
+ type=int,
help='Enable debug mode and set frequency in seconds')
- parser.add_argument('--fast-emu', action='store_true',
+ parser.add_argument('--fast-emu',
+ action='store_true',
help='Enter demo mode')
- parser.add_argument('--client-timeout', default=5 * 60, type=int,
+ parser.add_argument('--client-timeout',
+ default=5 * 60,
+ type=int,
help='Demo client timeout in seconds')
return parser.parse_args()
diff --git a/src/py/bbctrl/plan.py b/src/py/bbctrl/plan.py
index 0961970..94a6886 100644
--- a/src/py/bbctrl/plan.py
+++ b/src/py/bbctrl/plan.py
@@ -1,51 +1,22 @@
#!/usr/bin/env python3
-################################################################################
-# #
-# This file is part of the Buildbotics firmware. #
-# #
-# Copyright (c) 2015 - 2018, Buildbotics LLC #
-# All rights reserved. #
-# #
-# This file ("the software") is free software: you can redistribute it #
-# and/or modify it under the terms of the GNU General Public License, #
-# version 2 as published by the Free Software Foundation. You should #
-# have received a copy of the GNU General Public License, version 2 #
-# along with the software. If not, see . #
-# #
-# The software is distributed in the hope that it will be useful, but #
-# WITHOUT ANY WARRANTY; without even the implied warranty of #
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
-# Lesser General Public License for more details. #
-# #
-# You should have received a copy of the GNU Lesser General Public #
-# License along with the software. If not, see #
-# . #
-# #
-# For information regarding this software email: #
-# "Joseph Coffland" #
-# #
-################################################################################
-
-import sys
import argparse
+import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error
+import gzip
import json
-import time
+import math
import math
import os
import re
-import gzip
import struct
-import math
-import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error
+import sys
+import time
-
-reLogLine = re.compile(
- r'^(?P[A-Z])[0-9 ]:'
- r'((?P[^:]+):)?'
- r'((?P\d+):)?'
- r'((?P\d+):)?'
- r'(?P.*)$')
+reLogLine = re.compile(r'^(?P[A-Z])[0-9 ]:'
+ r'((?P[^:]+):)?'
+ r'((?P\d+):)?'
+ r'((?P\d+):)?'
+ r'(?P.*)$')
def compute_unit(a, b):
@@ -77,6 +48,7 @@ def compute_move(start, unit, dist):
class Plan(object):
+
def __init__(self, path, state, config):
self.path = path
self.state = state
@@ -90,11 +62,14 @@ class Plan(object):
self.planner.load(self.path, config)
self.messages = []
- self.levels = dict(I = 'info', D = 'debug', W = 'warning', E = 'error',
- C = 'critical')
+ self.levels = dict(I='info',
+ D='debug',
+ W='warning',
+ E='error',
+ C='critical')
# Initialized axis states and bounds
- self.bounds = dict(min = {}, max = {})
+ self.bounds = dict(min={}, max={})
for axis in 'xyz':
self.bounds['min'][axis] = math.inf
self.bounds['max'][axis] = -math.inf
@@ -105,12 +80,10 @@ class Plan(object):
self.lastProgressTime = 0
self.time = 0
-
def add_to_bounds(self, axis, value):
if value < self.bounds['min'][axis]: self.bounds['min'][axis] = value
if self.bounds['max'][axis] < value: self.bounds['max'][axis] = value
-
def get_bounds(self):
# Remove infinity from bounds
for axis in 'xyz':
@@ -121,7 +94,6 @@ class Plan(object):
return self.bounds
-
def update_speed(self, s):
if self.currentSpeed == s: return False
self.currentSpeed = s
@@ -129,7 +101,6 @@ class Plan(object):
return True
-
def get_var_cb(self, name, units):
value = 0
@@ -139,19 +110,20 @@ class Plan(object):
return value
-
def log_cb(self, level, msg, filename, line, column):
if level in self.levels: level = self.levels[level]
# Ignore missing tool warning
- if (level == 'warning' and
- msg.startswith('Auto-creating missing tool')):
+ if (level == 'warning'
+ and msg.startswith('Auto-creating missing tool')):
return
self.messages.append(
- dict(level = level, msg = msg, filename = filename, line = line,
- column = column))
-
+ dict(level=level,
+ msg=msg,
+ filename=filename,
+ line=line,
+ column=column))
def _log_cb(self, line):
line = line.strip()
@@ -171,7 +143,6 @@ class Plan(object):
self.log_cb(level, msg, filename, line, column)
-
def progress(self, x):
if time.time() - self.lastProgressTime < 1 and x != 1: return
self.lastProgressTime = time.time()
@@ -184,7 +155,6 @@ class Plan(object):
sys.stdout.write(p)
sys.stdout.flush()
-
def _run(self):
start = time.clock()
line = 0
@@ -197,14 +167,14 @@ class Plan(object):
try:
while self.planner.has_more():
cmd = self.planner.next()
- self.planner.set_active(cmd['id']) # Release plan
+ self.planner.set_active(cmd['id']) # Release plan
# Cannot synchronize with actual machine so fake it
if self.planner.is_synchronizing(): self.planner.synchronize(0)
if cmd['type'] == 'line':
- if not (cmd.get('first', False) or
- cmd.get('seeking', False)):
+ if not (cmd.get('first', False)
+ or cmd.get('seeking', False)):
self.time += sum(cmd['times']) / 1000
target = cmd['target']
@@ -263,7 +233,6 @@ class Plan(object):
except Exception as e:
self.log_cb('error', str(e), os.path.basename(self.path), line, 0)
-
def run(self):
lastS = 0
speed = 0
@@ -292,27 +261,32 @@ class Plan(object):
f2.write(s)
with open('meta.json', 'w') as f:
- meta = dict(
- time = self.time,
- lines = self.lines,
- maxSpeed = self.maxSpeed,
- bounds = self.get_bounds(),
- messages = self.messages)
+ meta = dict(time=self.time,
+ lines=self.lines,
+ maxSpeed=self.maxSpeed,
+ bounds=self.get_bounds(),
+ messages=self.messages)
json.dump(meta, f)
-parser = argparse.ArgumentParser(description = 'Buildbotics GCode Planner')
-parser.add_argument('gcode', help = 'The GCode file to plan')
-parser.add_argument('state', help = 'GCode state variables')
-parser.add_argument('config', help = 'Planner config')
+parser = argparse.ArgumentParser(description='Buildbotics GCode Planner')
+parser.add_argument('gcode', help='The GCode file to plan')
+parser.add_argument('state', help='GCode state variables')
+parser.add_argument('config', help='Planner config')
-parser.add_argument('--max-time', default = 600,
- type = int, help = 'Maximum planning time in seconds')
-parser.add_argument('--max-loop', default = 30,
- type = int, help = 'Maximum time in loop in seconds')
-parser.add_argument('--nice', default = 10,
- type = int, help = 'Set "nice" process priority')
+parser.add_argument('--max-time',
+ default=600,
+ type=int,
+ help='Maximum planning time in seconds')
+parser.add_argument('--max-loop',
+ default=30,
+ type=int,
+ help='Maximum time in loop in seconds')
+parser.add_argument('--nice',
+ default=10,
+ type=int,
+ help='Set "nice" process priority')
args = parser.parse_args()
diff --git a/src/py/bbctrl/v4l2.py b/src/py/bbctrl/v4l2.py
index 02c49b3..0c79fc9 100644
--- a/src/py/bbctrl/v4l2.py
+++ b/src/py/bbctrl/v4l2.py
@@ -39,7 +39,6 @@
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
"""
Python bindings for the v4l2 userspace api in Linux 2.6.34
"""
@@ -49,7 +48,6 @@ Python bindings for the v4l2 userspace api in Linux 2.6.34
import ctypes
import platform
-
# See ioctl.h of your architecture for appropriate constants here.
# This has been tested only on x86 and MIPS
_IOC_NRBITS = 8
@@ -62,24 +60,25 @@ _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
if (platform.machine() == "mips"):
_IOC_NONE = 1
_IOC_READ = 2
- _IOC_WRITE = 4
+ _IOC_WRITE = 4
_IOC_SIZEBITS = 13
_IOC_DIRBITS = 3
else:
_IOC_NONE = 0
_IOC_WRITE = 1
- _IOC_READ = 2
+ _IOC_READ = 2
_IOC_SIZEBITS = 14
_IOC_DIRBITS = 2
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
+
def _IOC(dir_, type_, nr, size):
- return (
- ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value |
- ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value |
- ctypes.c_int32(nr << _IOC_NRSHIFT).value |
- ctypes.c_int32(size << _IOC_SIZESHIFT).value)
+ return (ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value
+ | ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value
+ | ctypes.c_int32(nr << _IOC_NRSHIFT).value
+ | ctypes.c_int32(size << _IOC_SIZESHIFT).value)
+
def _IOC_TYPECHECK(t):
return ctypes.sizeof(t)
@@ -96,6 +95,7 @@ def _IOW(type_, nr, size):
def _IOR(type_, nr, size):
return _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK(size))
+
def _IOWR(type_, nr, size):
return _IOC(_IOC_READ | _IOC_WRITE, type_, nr, _IOC_TYPECHECK(size))
@@ -107,11 +107,11 @@ def _IOWR(type_, nr, size):
enum = ctypes.c_uint
c_int = ctypes.c_int
-
#
# time
#
+
class timeval(ctypes.Structure):
_fields_ = [
('secs', ctypes.c_long),
@@ -123,10 +123,8 @@ class timeval(ctypes.Structure):
# v4l2
#
-
VIDEO_MAX_FRAME = 32
-
VID_TYPE_CAPTURE = 1
VID_TYPE_TUNER = 2
VID_TYPE_TELETEXT = 4
@@ -134,7 +132,7 @@ VID_TYPE_OVERLAY = 8
VID_TYPE_CHROMAKEY = 16
VID_TYPE_CLIPPING = 32
VID_TYPE_FRAMERAM = 64
-VID_TYPE_SCALES = 128
+VID_TYPE_SCALES = 128
VID_TYPE_MONOCHROME = 256
VID_TYPE_SUBCAPTURE = 512
VID_TYPE_MPEG_DECODER = 1024
@@ -163,32 +161,23 @@ v4l2_field = enum
def V4L2_FIELD_HAS_TOP(field):
- return (
- field == V4L2_FIELD_TOP or
- field == V4L2_FIELD_INTERLACED or
- field == V4L2_FIELD_INTERLACED_TB or
- field == V4L2_FIELD_INTERLACED_BT or
- field == V4L2_FIELD_SEQ_TB or
- field == V4L2_FIELD_SEQ_BT)
+ return (field == V4L2_FIELD_TOP or field == V4L2_FIELD_INTERLACED
+ or field == V4L2_FIELD_INTERLACED_TB
+ or field == V4L2_FIELD_INTERLACED_BT or field == V4L2_FIELD_SEQ_TB
+ or field == V4L2_FIELD_SEQ_BT)
def V4L2_FIELD_HAS_BOTTOM(field):
- return (
- field == V4L2_FIELD_BOTTOM or
- field == V4L2_FIELD_INTERLACED or
- field == V4L2_FIELD_INTERLACED_TB or
- field == V4L2_FIELD_INTERLACED_BT or
- field == V4L2_FIELD_SEQ_TB or
- field == V4L2_FIELD_SEQ_BT)
+ return (field == V4L2_FIELD_BOTTOM or field == V4L2_FIELD_INTERLACED
+ or field == V4L2_FIELD_INTERLACED_TB
+ or field == V4L2_FIELD_INTERLACED_BT or field == V4L2_FIELD_SEQ_TB
+ or field == V4L2_FIELD_SEQ_BT)
def V4L2_FIELD_HAS_BOTH(field):
- return (
- field == V4L2_FIELD_INTERLACED or
- field == V4L2_FIELD_INTERLACED_TB or
- field == V4L2_FIELD_INTERLACED_BT or
- field == V4L2_FIELD_SEQ_TB or
- field == V4L2_FIELD_SEQ_BT)
+ return (field == V4L2_FIELD_INTERLACED or field == V4L2_FIELD_INTERLACED_TB
+ or field == V4L2_FIELD_INTERLACED_BT or field == V4L2_FIELD_SEQ_TB
+ or field == V4L2_FIELD_SEQ_BT)
v4l2_buf_type = enum
@@ -206,7 +195,6 @@ v4l2_buf_type = enum
V4L2_BUF_TYPE_PRIVATE,
) = list(range(1, 11)) + [0x80]
-
v4l2_ctrl_type = enum
(
V4L2_CTRL_TYPE_INTEGER,
@@ -218,7 +206,6 @@ v4l2_ctrl_type = enum
V4L2_CTRL_TYPE_STRING,
) = range(1, 8)
-
v4l2_tuner_type = enum
(
V4L2_TUNER_RADIO,
@@ -226,7 +213,6 @@ v4l2_tuner_type = enum
V4L2_TUNER_DIGITAL_TV,
) = range(1, 4)
-
v4l2_memory = enum
(
V4L2_MEMORY_MMAP,
@@ -234,7 +220,6 @@ v4l2_memory = enum
V4L2_MEMORY_OVERLAY,
) = range(1, 4)
-
v4l2_colorspace = enum
(
V4L2_COLORSPACE_SMPTE170M,
@@ -247,7 +232,6 @@ v4l2_colorspace = enum
V4L2_COLORSPACE_SRGB,
) = range(1, 9)
-
v4l2_priority = enum
(
V4L2_PRIORITY_UNSET,
@@ -278,6 +262,7 @@ class v4l2_fract(ctypes.Structure):
# Driver capabilities
#
+
class v4l2_capability(ctypes.Structure):
_fields_ = [
('driver', ctypes.c_uint8 * 16),
@@ -315,11 +300,11 @@ V4L2_CAP_READWRITE = 0x01000000
V4L2_CAP_ASYNCIO = 0x02000000
V4L2_CAP_STREAMING = 0x04000000
-
#
# Video image format
#
+
class v4l2_pix_format(ctypes.Structure):
_fields_ = [
('width', ctypes.c_uint32),
@@ -332,6 +317,7 @@ class v4l2_pix_format(ctypes.Structure):
('priv', ctypes.c_uint32),
]
+
# RGB formats
V4L2_PIX_FMT_RGB332 = v4l2_fourcc('R', 'G', 'B', '1')
V4L2_PIX_FMT_RGB444 = v4l2_fourcc('R', '4', '4', '4')
@@ -346,7 +332,7 @@ V4L2_PIX_FMT_RGB32 = v4l2_fourcc('R', 'G', 'B', '4')
# Grey formats
V4L2_PIX_FMT_GREY = v4l2_fourcc('G', 'R', 'E', 'Y')
-V4L2_PIX_FMT_Y10 = v4l2_fourcc('Y', '1', '0', ' ')
+V4L2_PIX_FMT_Y10 = v4l2_fourcc('Y', '1', '0', ' ')
V4L2_PIX_FMT_Y16 = v4l2_fourcc('Y', '1', '6', ' ')
# Palette formats
@@ -417,11 +403,11 @@ V4L2_PIX_FMT_OV511 = v4l2_fourcc('O', '5', '1', '1')
V4L2_PIX_FMT_OV518 = v4l2_fourcc('O', '5', '1', '8')
V4L2_PIX_FMT_STV0680 = v4l2_fourcc('S', '6', '8', '0')
-
#
# Format enumeration
#
+
class v4l2_fmtdesc(ctypes.Structure):
_fields_ = [
('index', ctypes.c_uint32),
@@ -432,10 +418,10 @@ class v4l2_fmtdesc(ctypes.Structure):
('reserved', ctypes.c_uint32 * 4),
]
+
V4L2_FMT_FLAG_COMPRESSED = 0x0001
V4L2_FMT_FLAG_EMULATED = 0x0002
-
#
# Experimental frame size and frame rate enumeration
#
@@ -467,21 +453,18 @@ class v4l2_frmsize_stepwise(ctypes.Structure):
class v4l2_frmsizeenum(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('discrete', v4l2_frmsize_discrete),
('stepwise', v4l2_frmsize_stepwise),
]
- _fields_ = [
- ('index', ctypes.c_uint32),
- ('pixel_format', ctypes.c_uint32),
- ('type', ctypes.c_uint32),
- ('_u', _u),
- ('reserved', ctypes.c_uint32 * 2)
- ]
+ _fields_ = [('index', ctypes.c_uint32), ('pixel_format', ctypes.c_uint32),
+ ('type', ctypes.c_uint32), ('_u', _u),
+ ('reserved', ctypes.c_uint32 * 2)]
- _anonymous_ = ('_u',)
+ _anonymous_ = ('_u', )
#
@@ -505,6 +488,7 @@ class v4l2_frmival_stepwise(ctypes.Structure):
class v4l2_frmivalenum(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('discrete', v4l2_fract),
@@ -521,13 +505,14 @@ class v4l2_frmivalenum(ctypes.Structure):
('reserved', ctypes.c_uint32 * 2),
]
- _anonymous_ = ('_u',)
+ _anonymous_ = ('_u', )
#
# Timecode
#
+
class v4l2_timecode(ctypes.Structure):
_fields_ = [
('type', ctypes.c_uint32),
@@ -571,11 +556,11 @@ V4L2_JPEG_MARKER_DRI = 1 << 5
V4L2_JPEG_MARKER_COM = 1 << 6
V4L2_JPEG_MARKER_APP = 1 << 7
-
#
# Memory-mapping buffers
#
+
class v4l2_requestbuffers(ctypes.Structure):
_fields_ = [
('count', ctypes.c_uint32),
@@ -586,6 +571,7 @@ class v4l2_requestbuffers(ctypes.Structure):
class v4l2_plane(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('mem_offset', ctypes.c_uint32),
@@ -600,13 +586,12 @@ class v4l2_plane(ctypes.Structure):
('reserved', ctypes.c_uint32 * 11),
]
+
class v4l2_buffer(ctypes.Structure):
+
class _u(ctypes.Union):
- _fields_ = [
- ('offset', ctypes.c_uint32),
- ('userptr', ctypes.c_ulong),
- ('planes', ctypes.POINTER(v4l2_plane))
- ]
+ _fields_ = [('offset', ctypes.c_uint32), ('userptr', ctypes.c_ulong),
+ ('planes', ctypes.POINTER(v4l2_plane))]
_fields_ = [
('index', ctypes.c_uint32),
@@ -634,11 +619,11 @@ V4L2_BUF_FLAG_BFRAME = 0x0020
V4L2_BUF_FLAG_TIMECODE = 0x0100
V4L2_BUF_FLAG_INPUT = 0x0200
-
#
# Overlay preview
#
+
class v4l2_framebuffer(ctypes.Structure):
_fields_ = [
('capability', ctypes.c_uint32),
@@ -647,8 +632,9 @@ class v4l2_framebuffer(ctypes.Structure):
('fmt', v4l2_pix_format),
]
+
V4L2_FBUF_CAP_EXTERNOVERLAY = 0x0001
-V4L2_FBUF_CAP_CHROMAKEY = 0x0002
+V4L2_FBUF_CAP_CHROMAKEY = 0x0002
V4L2_FBUF_CAP_LIST_CLIPPING = 0x0004
V4L2_FBUF_CAP_BITMAP_CLIPPING = 0x0008
V4L2_FBUF_CAP_LOCAL_ALPHA = 0x0010
@@ -667,6 +653,8 @@ V4L2_FBUF_FLAG_SRC_CHROMAKEY = 0x0040
class v4l2_clip(ctypes.Structure):
pass
+
+
v4l2_clip._fields_ = [
('c', v4l2_rect),
('next', ctypes.POINTER(v4l2_clip)),
@@ -689,6 +677,7 @@ class v4l2_window(ctypes.Structure):
# Capture parameters
#
+
class v4l2_captureparm(ctypes.Structure):
_fields_ = [
('capability', ctypes.c_uint32),
@@ -719,6 +708,7 @@ class v4l2_outputparm(ctypes.Structure):
# Input image cropping
#
+
class v4l2_cropcap(ctypes.Structure):
_fields_ = [
('type', v4l2_buf_type),
@@ -741,7 +731,6 @@ class v4l2_crop(ctypes.Structure):
v4l2_std_id = ctypes.c_uint64
-
V4L2_STD_PAL_B = 0x00000001
V4L2_STD_PAL_B1 = 0x00000002
V4L2_STD_PAL_G = 0x00000004
@@ -773,26 +762,31 @@ V4L2_STD_SECAM_LC = 0x00800000
V4L2_STD_ATSC_8_VSB = 0x01000000
V4L2_STD_ATSC_16_VSB = 0x02000000
-
# some common needed stuff
V4L2_STD_PAL_BG = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_PAL_G)
V4L2_STD_PAL_DK = (V4L2_STD_PAL_D | V4L2_STD_PAL_D1 | V4L2_STD_PAL_K)
-V4L2_STD_PAL = (V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_H | V4L2_STD_PAL_I)
+V4L2_STD_PAL = (V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_H
+ | V4L2_STD_PAL_I)
V4L2_STD_NTSC = (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_M_KR)
V4L2_STD_SECAM_DK = (V4L2_STD_SECAM_D | V4L2_STD_SECAM_K | V4L2_STD_SECAM_K1)
-V4L2_STD_SECAM = (V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H | V4L2_STD_SECAM_DK | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)
+V4L2_STD_SECAM = (V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H
+ | V4L2_STD_SECAM_DK | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)
-V4L2_STD_525_60 = (V4L2_STD_PAL_M | V4L2_STD_PAL_60 | V4L2_STD_NTSC | V4L2_STD_NTSC_443)
-V4L2_STD_625_50 = (V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_SECAM)
+V4L2_STD_525_60 = (V4L2_STD_PAL_M | V4L2_STD_PAL_60 | V4L2_STD_NTSC
+ | V4L2_STD_NTSC_443)
+V4L2_STD_625_50 = (V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc
+ | V4L2_STD_SECAM)
V4L2_STD_ATSC = (V4L2_STD_ATSC_8_VSB | V4L2_STD_ATSC_16_VSB)
V4L2_STD_UNKNOWN = 0
V4L2_STD_ALL = (V4L2_STD_525_60 | V4L2_STD_625_50)
# some merged standards
-V4L2_STD_MN = (V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_NTSC)
+V4L2_STD_MN = (V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc
+ | V4L2_STD_NTSC)
V4L2_STD_B = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_SECAM_B)
-V4L2_STD_GH = (V4L2_STD_PAL_G | V4L2_STD_PAL_H|V4L2_STD_SECAM_G | V4L2_STD_SECAM_H)
+V4L2_STD_GH = (V4L2_STD_PAL_G | V4L2_STD_PAL_H | V4L2_STD_SECAM_G
+ | V4L2_STD_SECAM_H)
V4L2_STD_DK = (V4L2_STD_PAL_DK | V4L2_STD_SECAM_DK)
@@ -811,17 +805,16 @@ class v4l2_standard(ctypes.Structure):
# Video timings dv preset
#
+
class v4l2_dv_preset(ctypes.Structure):
- _fields_ = [
- ('preset', ctypes.c_uint32),
- ('reserved', ctypes.c_uint32 * 4)
- ]
+ _fields_ = [('preset', ctypes.c_uint32), ('reserved', ctypes.c_uint32 * 4)]
#
# DV preset enumeration
#
+
class v4l2_dv_enum_preset(ctypes.Structure):
_fields_ = [
('index', ctypes.c_uint32),
@@ -832,6 +825,7 @@ class v4l2_dv_enum_preset(ctypes.Structure):
('reserved', ctypes.c_uint32 * 4),
]
+
#
# DV preset values
#
@@ -846,21 +840,21 @@ V4L2_DV_720P50 = 6
V4L2_DV_720P59_94 = 7
V4L2_DV_720P60 = 8
V4L2_DV_1080I29_97 = 9
-V4L2_DV_1080I30 = 10
-V4L2_DV_1080I25 = 11
-V4L2_DV_1080I50 = 12
-V4L2_DV_1080I60 = 13
-V4L2_DV_1080P24 = 14
-V4L2_DV_1080P25 = 15
-V4L2_DV_1080P30 = 16
-V4L2_DV_1080P50 = 17
-V4L2_DV_1080P60 = 18
-
+V4L2_DV_1080I30 = 10
+V4L2_DV_1080I25 = 11
+V4L2_DV_1080I50 = 12
+V4L2_DV_1080I60 = 13
+V4L2_DV_1080P24 = 14
+V4L2_DV_1080P25 = 15
+V4L2_DV_1080P30 = 16
+V4L2_DV_1080P50 = 17
+V4L2_DV_1080P60 = 18
#
# DV BT timings
#
+
class v4l2_bt_timings(ctypes.Structure):
_fields_ = [
('width', ctypes.c_uint32),
@@ -882,6 +876,7 @@ class v4l2_bt_timings(ctypes.Structure):
_pack_ = True
+
# Interlaced or progressive format
V4L2_DV_PROGRESSIVE = 0
V4L2_DV_INTERLACED = 1
@@ -892,6 +887,7 @@ V4L2_DV_HSYNC_POS_POL = 0x00000002
class v4l2_dv_timings(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('bt', v4l2_bt_timings),
@@ -903,18 +899,18 @@ class v4l2_dv_timings(ctypes.Structure):
('_u', _u),
]
- _anonymous_ = ('_u',)
+ _anonymous_ = ('_u', )
_pack_ = True
# Values for the type field
V4L2_DV_BT_656_1120 = 0
-
#
# Video inputs
#
+
class v4l2_input(ctypes.Structure):
_fields_ = [
('index', ctypes.c_uint32),
@@ -957,6 +953,7 @@ V4L2_IN_CAP_STD = 0x00000004
# Video outputs
#
+
class v4l2_output(ctypes.Structure):
_fields_ = [
('index', ctypes.c_uint32),
@@ -970,7 +967,7 @@ class v4l2_output(ctypes.Structure):
V4L2_OUTPUT_TYPE_MODULATOR = 1
-V4L2_OUTPUT_TYPE_ANALOG = 2
+V4L2_OUTPUT_TYPE_ANALOG = 2
V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY = 3
V4L2_OUT_CAP_PRESETS = 0x00000001
@@ -981,6 +978,7 @@ V4L2_OUT_CAP_STD = 0x00000004
# Controls
#
+
class v4l2_control(ctypes.Structure):
_fields_ = [
('id', ctypes.c_uint32),
@@ -989,6 +987,7 @@ class v4l2_control(ctypes.Structure):
class v4l2_ext_control(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('value', ctypes.c_int32),
@@ -996,13 +995,10 @@ class v4l2_ext_control(ctypes.Structure):
('reserved', ctypes.c_void_p),
]
- _fields_ = [
- ('id', ctypes.c_uint32),
- ('reserved2', ctypes.c_uint32 * 2),
- ('_u', _u)
- ]
+ _fields_ = [('id', ctypes.c_uint32), ('reserved2', ctypes.c_uint32 * 2),
+ ('_u', _u)]
- _anonymous_ = ('_u',)
+ _anonymous_ = ('_u', )
_pack_ = True
@@ -1016,25 +1012,26 @@ class v4l2_ext_controls(ctypes.Structure):
]
-V4L2_CTRL_CLASS_USER = 0x00980000 # Old-style 'user' controls
-V4L2_CTRL_CLASS_MPEG = 0x00990000 # MPEG-compression controls
-V4L2_CTRL_CLASS_CAMERA = 0x009a0000 # Camera class controls
-V4L2_CTRL_CLASS_FM_TX = 0x009b0000 # FM Modulator controls
-V4L2_CTRL_CLASS_FLASH = 0x009c0000 # Camera flash controls
-V4L2_CTRL_CLASS_JPEG = 0x009d0000 # JPEG-compression controls
-V4L2_CTRL_CLASS_IMAGE_SOURCE = 0x009e0000 # Image source controls
-V4L2_CTRL_CLASS_IMAGE_PROC = 0x009f0000 # Image processing controls
-V4L2_CTRL_CLASS_DV = 0x00a00000 # Digital Video controls
-V4L2_CTRL_CLASS_FM_RX = 0x00a10000 # FM Receiver controls
-V4L2_CTRL_CLASS_RF_TUNER = 0x00a20000 # RF tuner controls
-V4L2_CTRL_CLASS_DETECT = 0x00a30000 # Detection controls
+V4L2_CTRL_CLASS_USER = 0x00980000 # Old-style 'user' controls
+V4L2_CTRL_CLASS_MPEG = 0x00990000 # MPEG-compression controls
+V4L2_CTRL_CLASS_CAMERA = 0x009a0000 # Camera class controls
+V4L2_CTRL_CLASS_FM_TX = 0x009b0000 # FM Modulator controls
+V4L2_CTRL_CLASS_FLASH = 0x009c0000 # Camera flash controls
+V4L2_CTRL_CLASS_JPEG = 0x009d0000 # JPEG-compression controls
+V4L2_CTRL_CLASS_IMAGE_SOURCE = 0x009e0000 # Image source controls
+V4L2_CTRL_CLASS_IMAGE_PROC = 0x009f0000 # Image processing controls
+V4L2_CTRL_CLASS_DV = 0x00a00000 # Digital Video controls
+V4L2_CTRL_CLASS_FM_RX = 0x00a10000 # FM Receiver controls
+V4L2_CTRL_CLASS_RF_TUNER = 0x00a20000 # RF tuner controls
+V4L2_CTRL_CLASS_DETECT = 0x00a30000 # Detection controls
+
def V4L2_CTRL_ID_MASK():
return 0x0fffffff
def V4L2_CTRL_ID2CLASS(id_):
- return id_ & 0x0fff0000 # unsigned long
+ return id_ & 0x0fff0000 # unsigned long
def V4L2_CTRL_DRIVER_PRIV(id_):
@@ -1089,13 +1086,13 @@ V4L2_CID_AUDIO_BASS = V4L2_CID_BASE + 7
V4L2_CID_AUDIO_TREBLE = V4L2_CID_BASE + 8
V4L2_CID_AUDIO_MUTE = V4L2_CID_BASE + 9
V4L2_CID_AUDIO_LOUDNESS = V4L2_CID_BASE + 10
-V4L2_CID_BLACK_LEVEL = V4L2_CID_BASE + 11 # Deprecated
+V4L2_CID_BLACK_LEVEL = V4L2_CID_BASE + 11 # Deprecated
V4L2_CID_AUTO_WHITE_BALANCE = V4L2_CID_BASE + 12
V4L2_CID_DO_WHITE_BALANCE = V4L2_CID_BASE + 13
V4L2_CID_RED_BALANCE = V4L2_CID_BASE + 14
V4L2_CID_BLUE_BALANCE = V4L2_CID_BASE + 15
V4L2_CID_GAMMA = V4L2_CID_BASE + 16
-V4L2_CID_WHITENESS = V4L2_CID_GAMMA # Deprecated
+V4L2_CID_WHITENESS = V4L2_CID_GAMMA # Deprecated
V4L2_CID_EXPOSURE = V4L2_CID_BASE + 17
V4L2_CID_AUTOGAIN = V4L2_CID_BASE + 18
V4L2_CID_GAIN = V4L2_CID_BASE + 19
@@ -1286,7 +1283,7 @@ v4l2_mpeg_audio_crc = enum
V4L2_CID_MPEG_AUDIO_MUTE = V4L2_CID_MPEG_BASE + 109
V4L2_CID_MPEG_AUDIO_AAC_BITRATE = V4L2_CID_MPEG_BASE + 110
-V4L2_CID_MPEG_AUDIO_AC3_BITRATE = V4L2_CID_MPEG_BASE + 111
+V4L2_CID_MPEG_AUDIO_AC3_BITRATE = V4L2_CID_MPEG_BASE + 111
v4l2_mpeg_audio_ac3_bitrate = enum
(
@@ -1472,7 +1469,6 @@ v4l2_preemphasis = enum
V4L2_CID_TUNE_POWER_LEVEL = V4L2_CID_FM_TX_CLASS_BASE + 113
V4L2_CID_TUNE_ANTENNA_CAPACITOR = V4L2_CID_FM_TX_CLASS_BASE + 114
-
# JPEG-class control IDs
V4L2_CID_JPEG_CLASS_BASE = V4L2_CTRL_CLASS_JPEG | 0x900
@@ -1480,7 +1476,7 @@ V4L2_CID_JPEG_CLASS = V4L2_CTRL_CLASS_JPEG | 1
V4L2_CID_JPEG_CHROMA_SUBSAMPLING = V4L2_CID_JPEG_CLASS_BASE + 1
-v4l2_jpeg_chroma_subsampling = enum
+v4l2_jpeg_chroma_subsampling = enum
(
V4L2_JPEG_CHROMA_SUBSAMPLING_444,
V4L2_JPEG_CHROMA_SUBSAMPLING_422,
@@ -1493,18 +1489,18 @@ v4l2_jpeg_chroma_subsampling = enum
V4L2_CID_JPEG_RESTART_INTERVAL = V4L2_CID_JPEG_CLASS_BASE + 2
V4L2_CID_JPEG_COMPRESSION_QUALITY = V4L2_CID_JPEG_CLASS_BASE + 3
-V4L2_CID_JPEG_ACTIVE_MARKER = V4L2_CID_JPEG_CLASS_BASE + 4
+V4L2_CID_JPEG_ACTIVE_MARKER = V4L2_CID_JPEG_CLASS_BASE + 4
V4L2_JPEG_ACTIVE_MARKER_APP0 = 1 << 0
V4L2_JPEG_ACTIVE_MARKER_APP1 = 1 << 1
-V4L2_JPEG_ACTIVE_MARKER_COM = 1 << 16
-V4L2_JPEG_ACTIVE_MARKER_DQT = 1 << 17
-V4L2_JPEG_ACTIVE_MARKER_DHT = 1 << 18
-
+V4L2_JPEG_ACTIVE_MARKER_COM = 1 << 16
+V4L2_JPEG_ACTIVE_MARKER_DQT = 1 << 17
+V4L2_JPEG_ACTIVE_MARKER_DHT = 1 << 18
#
# Tuning
#
+
class v4l2_tuner(ctypes.Structure):
_fields_ = [
('index', ctypes.c_uint32),
@@ -1579,6 +1575,7 @@ class v4l2_hw_freq_seek(ctypes.Structure):
# RDS
#
+
class v4l2_rds_data(ctypes.Structure):
_fields_ = [
('lsb', ctypes.c_char),
@@ -1589,7 +1586,7 @@ class v4l2_rds_data(ctypes.Structure):
_pack_ = True
-V4L2_RDS_BLOCK_MSK = 0x7
+V4L2_RDS_BLOCK_MSK = 0x7
V4L2_RDS_BLOCK_A = 0
V4L2_RDS_BLOCK_B = 1
V4L2_RDS_BLOCK_C = 2
@@ -1600,11 +1597,11 @@ V4L2_RDS_BLOCK_INVALID = 7
V4L2_RDS_BLOCK_CORRECTED = 0x40
V4L2_RDS_BLOCK_ERROR = 0x80
-
#
# Audio
#
+
class v4l2_audio(ctypes.Structure):
_fields_ = [
('index', ctypes.c_uint32),
@@ -1672,7 +1669,9 @@ V4L2_ENC_CMD_STOP_AT_GOP_END = 1 << 0
class v4l2_encoder_cmd(ctypes.Structure):
+
class _u(ctypes.Union):
+
class _s(ctypes.Structure):
_fields_ = [
('data', ctypes.c_uint32 * 8),
@@ -1688,13 +1687,14 @@ class v4l2_encoder_cmd(ctypes.Structure):
('_u', _u),
]
- _anonymous_ = ('_u',)
+ _anonymous_ = ('_u', )
#
# Data services (VBI)
#
+
class v4l2_vbi_format(ctypes.Structure):
_fields_ = [
('sampling_rate', ctypes.c_uint32),
@@ -1726,8 +1726,8 @@ V4L2_SLICED_VPS = 0x0400
V4L2_SLICED_CAPTION_525 = 0x1000
V4L2_SLICED_WSS_625 = 0x4000
V4L2_SLICED_VBI_525 = V4L2_SLICED_CAPTION_525
-V4L2_SLICED_VBI_625 = (
- V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS | V4L2_SLICED_WSS_625)
+V4L2_SLICED_VBI_625 = (V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS
+ | V4L2_SLICED_WSS_625)
class v4l2_sliced_vbi_cap(ctypes.Structure):
@@ -1753,7 +1753,6 @@ class v4l2_sliced_vbi_data(ctypes.Structure):
# Sliced VBI data inserted into MPEG Streams
#
-
V4L2_MPEG_VBI_IVTV_TELETEXT_B = 1
V4L2_MPEG_VBI_IVTV_CAPTION_525 = 4
V4L2_MPEG_VBI_IVTV_WSS_625 = 5
@@ -1771,7 +1770,7 @@ class v4l2_mpeg_vbi_itv0_line(ctypes.Structure):
class v4l2_mpeg_vbi_itv0(ctypes.Structure):
_fields_ = [
- ('linemask', ctypes.c_uint32 * 2), # how to define __le32 in ctypes?
+ ('linemask', ctypes.c_uint32 * 2), # how to define __le32 in ctypes?
('line', v4l2_mpeg_vbi_itv0_line * 35),
]
@@ -1791,18 +1790,16 @@ V4L2_MPEG_VBI_IVTV_MAGIC1 = "ITV0"
class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('itv0', v4l2_mpeg_vbi_itv0),
('ITV0', v4l2_mpeg_vbi_ITV0),
]
- _fields_ = [
- ('magic', ctypes.c_char * 4),
- ('_u', _u)
- ]
+ _fields_ = [('magic', ctypes.c_char * 4), ('_u', _u)]
- _anonymous_ = ('_u',)
+ _anonymous_ = ('_u', )
_pack_ = True
@@ -1810,7 +1807,9 @@ class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure):
# Aggregate structures
#
+
class v4l2_format(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('pix', v4l2_pix_format),
@@ -1827,6 +1826,7 @@ class v4l2_format(ctypes.Structure):
class v4l2_streamparm(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('capture', v4l2_captureparm),
@@ -1834,10 +1834,7 @@ class v4l2_streamparm(ctypes.Structure):
('raw_data', ctypes.c_char * 200),
]
- _fields_ = [
- ('type', v4l2_buf_type),
- ('parm', _u)
- ]
+ _fields_ = [('type', v4l2_buf_type), ('parm', _u)]
#
@@ -1851,6 +1848,7 @@ V4L2_CHIP_MATCH_AC97 = 3
class v4l2_dbg_match(ctypes.Structure):
+
class _u(ctypes.Union):
_fields_ = [
('addr', ctypes.c_uint32),
@@ -1862,7 +1860,7 @@ class v4l2_dbg_match(ctypes.Structure):
('_u', _u),
]
- _anonymous_ = ('_u',)
+ _anonymous_ = ('_u', )
_pack_ = True
@@ -1897,7 +1895,7 @@ VIDIOC_ENUM_FMT = _IOWR('V', 2, v4l2_fmtdesc)
VIDIOC_G_FMT = _IOWR('V', 4, v4l2_format)
VIDIOC_S_FMT = _IOWR('V', 5, v4l2_format)
VIDIOC_REQBUFS = _IOWR('V', 8, v4l2_requestbuffers)
-VIDIOC_QUERYBUF = _IOWR('V', 9, v4l2_buffer)
+VIDIOC_QUERYBUF = _IOWR('V', 9, v4l2_buffer)
VIDIOC_G_FBUF = _IOR('V', 10, v4l2_framebuffer)
VIDIOC_S_FBUF = _IOW('V', 11, v4l2_framebuffer)
VIDIOC_OVERLAY = _IOW('V', 14, ctypes.c_int)
@@ -1925,7 +1923,7 @@ VIDIOC_G_OUTPUT = _IOR('V', 46, ctypes.c_int)
VIDIOC_S_OUTPUT = _IOWR('V', 47, ctypes.c_int)
VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output)
VIDIOC_G_AUDOUT = _IOR('V', 49, v4l2_audioout)
-VIDIOC_S_AUDOUT = _IOW('V', 50, v4l2_audioout)
+VIDIOC_S_AUDOUT = _IOW('V', 50, v4l2_audioout)
VIDIOC_G_MODULATOR = _IOWR('V', 54, v4l2_modulator)
VIDIOC_S_MODULATOR = _IOW('V', 55, v4l2_modulator)
VIDIOC_G_FREQUENCY = _IOWR('V', 56, v4l2_frequency)