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)