Reformatting of python files.

This commit is contained in:
David Carley
2022-08-31 14:26:54 +00:00
parent 0c58292347
commit d94bf96a56
23 changed files with 877 additions and 1264 deletions

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import json
import traceback
import bbctrl
from tornado.web import HTTPError from tornado.web import HTTPError
import bbctrl
import json
import tornado.httpclient import tornado.httpclient
class APIHandler(bbctrl.RequestHandler): class APIHandler(bbctrl.RequestHandler):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
self.delete_ok(*args, **kwargs) self.delete_ok(*args, **kwargs)
self.write_json('ok') self.write_json('ok')
def delete_ok(self):
def delete_ok(self): raise HTTPError(405) raise HTTPError(405)
def put(self, *args, **kwargs): def put(self, *args, **kwargs):
self.put_ok(*args, **kwargs) self.put_ok(*args, **kwargs)
self.write_json('ok') self.write_json('ok')
def put_ok(self):
def put_ok(self): raise HTTPError(405) raise HTTPError(405)
def prepare(self): def prepare(self):
self.json = {} self.json = {}
@@ -59,11 +29,9 @@ class APIHandler(bbctrl.RequestHandler):
except ValueError: except ValueError:
raise HTTPError(400, 'Unable to parse JSON') raise HTTPError(400, 'Unable to parse JSON')
def set_default_headers(self): def set_default_headers(self):
self.set_header('Content-Type', 'application/json') self.set_header('Content-Type', 'application/json')
def write_error(self, status_code, **kwargs): def write_error(self, status_code, **kwargs):
e = {} e = {}
@@ -73,16 +41,17 @@ class APIHandler(bbctrl.RequestHandler):
typ, value, tb = kwargs['exc_info'] typ, value, tb = kwargs['exc_info']
if isinstance(value, HTTPError) and value.log_message: if isinstance(value, HTTPError) and value.log_message:
e['message'] = value.log_message % value.args 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 e['code'] = status_code
self.write_json(e) self.write_json(e)
def write_json(self, data, pretty=False):
def write_json(self, data, pretty = False): if pretty: data = json.dumps(data, indent=2, separators=(',', ': '))
if pretty: data = json.dumps(data, indent = 2, separators = (',', ': ')) else: data = json.dumps(data, separators=(',', ':'))
else: data = json.dumps(data, separators = (',', ':'))
self.write(data) self.write(data)

View File

@@ -1,59 +1,29 @@
################################################################################ import ctypes
# #
# 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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import serial import serial
import time import time
import traceback import traceback
import ctypes
import bbctrl
import bbctrl.Cmd as Cmd
class serial_struct(ctypes.Structure): class serial_struct(ctypes.Structure):
_fields_ = [ _fields_ = [
('type', ctypes.c_int), ('type', ctypes.c_int),
('line', ctypes.c_int), ('line', ctypes.c_int),
('port', ctypes.c_uint), ('port', ctypes.c_uint),
('irq', ctypes.c_int), ('irq', ctypes.c_int),
('flags', ctypes.c_int), ('flags', ctypes.c_int),
('xmit_fifo_size', ctypes.c_int), ('xmit_fifo_size', ctypes.c_int),
('custom_divisor', ctypes.c_int), ('custom_divisor', ctypes.c_int),
('baud_base', ctypes.c_int), ('baud_base', ctypes.c_int),
('close_delay', ctypes.c_ushort), ('close_delay', ctypes.c_ushort),
('io_type', ctypes.c_byte), ('io_type', ctypes.c_byte),
('reserved', ctypes.c_byte), ('reserved', ctypes.c_byte),
('hub6', ctypes.c_int), ('hub6', ctypes.c_int),
('closing_wait', ctypes.c_ushort), ('closing_wait', ctypes.c_ushort),
('closing_wait2', ctypes.c_ushort), ('closing_wait2', ctypes.c_ushort),
('iomem_base', ctypes.c_char_p), ('iomem_base', ctypes.c_char_p),
('iomem_reg_shift', ctypes.c_ushort), ('iomem_reg_shift', ctypes.c_ushort),
('port_high', ctypes.c_uint), ('port_high', ctypes.c_uint),
('iomap_base', ctypes.c_ulong), ('iomap_base', ctypes.c_ulong),
] ]
@@ -65,11 +35,12 @@ def serial_set_low_latency(sp):
ss = serial_struct() ss = serial_struct()
fcntl.ioctl(sp, termios.TIOCGSERIAL, ss) 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) fcntl.ioctl(sp, termios.TIOCSSERIAL, ss)
class AVR(object): class AVR(object):
def __init__(self, ctrl): def __init__(self, ctrl):
self.ctrl = ctrl self.ctrl = ctrl
self.log = ctrl.log.get('AVR') self.log = ctrl.log.get('AVR')
@@ -79,14 +50,16 @@ class AVR(object):
self.read_cb = None self.read_cb = None
self.write_cb = None self.write_cb = None
def close(self):
def close(self): pass pass
def _start(self): def _start(self):
try: try:
self.sp = serial.Serial(self.ctrl.args.serial, self.ctrl.args.baud, self.sp = serial.Serial(self.ctrl.args.serial,
rtscts = 1, timeout = 0, write_timeout = 0) self.ctrl.args.baud,
rtscts=1,
timeout=0,
write_timeout=0)
self.sp.nonblocking() self.sp.nonblocking()
#serial_set_low_latency(self.sp) #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.add_handler(self.sp, self._serial_handler,
self.ctrl.ioloop.READ) self.ctrl.ioloop.READ)
def set_handlers(self, read_cb, write_cb): def set_handlers(self, read_cb, write_cb):
if self.read_cb is not None or self.write_cb is not None: if self.read_cb is not None or self.write_cb is not None:
raise Exception('Handler already set') raise Exception('Handler already set')
@@ -107,7 +79,6 @@ class AVR(object):
self.write_cb = write_cb self.write_cb = write_cb
self._start() self._start()
def enable_write(self, enable): def enable_write(self, enable):
if self.sp is None: return if self.sp is None: return
@@ -115,11 +86,9 @@ class AVR(object):
if enable: flags |= self.ctrl.ioloop.WRITE if enable: flags |= self.ctrl.ioloop.WRITE
self.ctrl.ioloop.update_handler(self.sp, flags) self.ctrl.ioloop.update_handler(self.sp, flags)
def _serial_write(self): def _serial_write(self):
self.write_cb(lambda data: self.sp.write(data)) self.write_cb(lambda data: self.sp.write(data))
def _serial_read(self): def _serial_read(self):
try: try:
data = '' data = ''
@@ -129,17 +98,16 @@ class AVR(object):
except Exception as e: except Exception as e:
self.log.warning('%s: %s', e, data) self.log.warning('%s: %s', e, data)
def _serial_handler(self, fd, events): def _serial_handler(self, fd, events):
try: try:
if self.ctrl.ioloop.READ & events: self._serial_read() if self.ctrl.ioloop.READ & events: self._serial_read()
if self.ctrl.ioloop.WRITE & events: self._serial_write() if self.ctrl.ioloop.WRITE & events: self._serial_write()
except Exception as e: 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)) self.log.info('I2C: %s b=%s w=%s d=%s' % (cmd, byte, word, block))
retry = 10 retry = 10
cmd = ord(cmd[0]) cmd = ord(cmd[0])

View File

@@ -1,42 +1,9 @@
#!/usr/bin/env python3 from tornado import web, iostream
################################################################################
# #
# 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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/icenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import os
import fcntl
import select
import struct
import mmap
import pyudev
import base64
import socket
import ctypes
from tornado import gen, web, iostream
import bbctrl import bbctrl
import fcntl
import mmap
import os
import pyudev
try: try:
import v4l2 import v4l2
@@ -45,6 +12,7 @@ except:
def array_to_string(a): def array_to_string(a):
def until_zero(a): def until_zero(a):
for c in a: for c in a:
if c == 0: return if c == 0: return
@@ -61,13 +29,17 @@ def fourcc_to_string(i):
chr((i >> 24) & 0xff) 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): def format_frame(frame):
frame = [b'--', VideoHandler.boundary.encode('utf8'), b'\r\n', frame = [
b'Content-type: image/jpeg\r\n', b'--',
b'Content-length: %d\r\n\r\n' % len(frame), frame] 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) return b''.join(frame)
@@ -79,13 +51,13 @@ def get_image_resource(path):
class VideoDevice(object): 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.fd = os.open(path, os.O_RDWR | os.O_NONBLOCK | os.O_CLOEXEC)
self.buffers = [] self.buffers = []
def fileno(self):
def fileno(self): return self.fd return self.fd
def get_audio(self): def get_audio(self):
b = v4l2.v4l2_audio() b = v4l2.v4l2_audio()
@@ -99,11 +71,11 @@ class VideoDevice(object):
l.append((array_to_string(b.name), b.capability, b.mode)) l.append((array_to_string(b.name), b.capability, b.mode))
b.index += 1 b.index += 1
except OSError: break except OSError:
break
return l return l
def get_formats(self): def get_formats(self):
b = v4l2.v4l2_fmtdesc() b = v4l2.v4l2_fmtdesc()
b.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE b.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -120,11 +92,11 @@ class VideoDevice(object):
b.index += 1 b.index += 1
except OSError: break except OSError:
break
return l return l
def get_frame_sizes(self, fourcc): def get_frame_sizes(self, fourcc):
b = v4l2.v4l2_frmsizeenum() b = v4l2.v4l2_frmsizeenum()
b.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE b.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -140,18 +112,18 @@ class VideoDevice(object):
sizes.append((b.discrete.width, b.discrete.height)) sizes.append((b.discrete.width, b.discrete.height))
else: else:
sizes.append((b.stepwise.min_width, b.stepwise.max_width, sizes.append(
b.stepwise.step_width, b.stepwise.min_height, (b.stepwise.min_width, b.stepwise.max_width,
b.stepwise.max_height, b.stepwise.step_width, b.stepwise.min_height,
b.stepwise.step_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 return sizes
def set_format(self, width, height, fourcc): def set_format(self, width, height, fourcc):
fmt = v4l2.v4l2_format() fmt = v4l2.v4l2_format()
fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE fmt.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -163,13 +135,12 @@ class VideoDevice(object):
fcntl.ioctl(self, v4l2.VIDIOC_S_FMT, fmt) fcntl.ioctl(self, v4l2.VIDIOC_S_FMT, fmt)
def create_buffers(self, count): def create_buffers(self, count):
# Create buffers # Create buffers
rbuf = v4l2.v4l2_requestbuffers() rbuf = v4l2.v4l2_requestbuffers()
rbuf.count = count; rbuf.count = count
rbuf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE; rbuf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
rbuf.memory = v4l2.V4L2_MEMORY_MMAP; rbuf.memory = v4l2.V4L2_MEMORY_MMAP
fcntl.ioctl(self, v4l2.VIDIOC_REQBUFS, rbuf) fcntl.ioctl(self, v4l2.VIDIOC_REQBUFS, rbuf)
@@ -182,15 +153,16 @@ class VideoDevice(object):
fcntl.ioctl(self, v4l2.VIDIOC_QUERYBUF, buf) fcntl.ioctl(self, v4l2.VIDIOC_QUERYBUF, buf)
# Mem map buffer # 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, mmap.PROT_READ | mmap.PROT_WRITE,
offset = buf.m.offset) offset=buf.m.offset)
self.buffers.append(mm) self.buffers.append(mm)
# Queue the buffer for capture # Queue the buffer for capture
fcntl.ioctl(self, v4l2.VIDIOC_QBUF, buf) fcntl.ioctl(self, v4l2.VIDIOC_QBUF, buf)
def _dqbuf(self): def _dqbuf(self):
buf = v4l2.v4l2_buffer() buf = v4l2.v4l2_buffer()
buf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE buf.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
@@ -199,11 +171,9 @@ class VideoDevice(object):
return buf return buf
def _qbuf(self, buf): def _qbuf(self, buf):
fcntl.ioctl(self, v4l2.VIDIOC_QBUF, buf) fcntl.ioctl(self, v4l2.VIDIOC_QBUF, buf)
def read_frame(self): def read_frame(self):
buf = self._dqbuf() buf = self._dqbuf()
mm = self.buffers[buf.index] mm = self.buffers[buf.index]
@@ -214,16 +184,15 @@ class VideoDevice(object):
return frame return frame
def flush_frame(self):
def flush_frame(self): self._qbuf(self._dqbuf()) self._qbuf(self._dqbuf())
def get_info(self): def get_info(self):
caps = v4l2.v4l2_capability() caps = v4l2.v4l2_capability()
fcntl.ioctl(self, v4l2.VIDIOC_QUERYCAP, caps) fcntl.ioctl(self, v4l2.VIDIOC_QUERYCAP, caps)
caps._driver = array_to_string(caps.driver) caps._driver = array_to_string(caps.driver)
caps._card = array_to_string(caps.card) caps._card = array_to_string(caps.card)
caps._bus_info = array_to_string(caps.bus_info) caps._bus_info = array_to_string(caps.bus_info)
l = [] l = []
@@ -251,33 +220,31 @@ class VideoDevice(object):
return caps return caps
def set_fps(self, fps): def set_fps(self, fps):
setfps = v4l2.v4l2_streamparm() 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.numerator = 1
setfps.parm.capture.timeperframe.denominator = fps setfps.parm.capture.timeperframe.denominator = fps
fcntl.ioctl(self, v4l2.VIDIOC_S_PARM, setfps) fcntl.ioctl(self, v4l2.VIDIOC_S_PARM, setfps)
def start(self): def start(self):
buf_type = v4l2.v4l2_buf_type(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE) buf_type = v4l2.v4l2_buf_type(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)
fcntl.ioctl(self, v4l2.VIDIOC_STREAMON, buf_type) fcntl.ioctl(self, v4l2.VIDIOC_STREAMON, buf_type)
def stop(self): def stop(self):
buf_type = v4l2.v4l2_buf_type(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE) buf_type = v4l2.v4l2_buf_type(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE)
fcntl.ioctl(self, v4l2.VIDIOC_STREAMOFF, buf_type) fcntl.ioctl(self, v4l2.VIDIOC_STREAMOFF, buf_type)
def close(self): def close(self):
if self.fd is None: return if self.fd is None: return
try: try:
os.close(self.fd) os.close(self.fd)
finally: self.fd = None finally:
self.fd = None
class Camera(object): class Camera(object):
def __init__(self, ioloop, args, log): def __init__(self, ioloop, args, log):
self.ioloop = ioloop self.ioloop = ioloop
self.log = log.get('Camera') self.log = log.get('Camera')
@@ -305,11 +272,10 @@ class Camera(object):
# Get notifications of camera (un)plug events # Get notifications of camera (un)plug events
self.udevCtx = pyudev.Context() self.udevCtx = pyudev.Context()
self.udevMon = pyudev.Monitor.from_netlink(self.udevCtx) 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) ioloop.add_handler(self.udevMon, self._udev_handler, ioloop.READ)
self.udevMon.start() self.udevMon.start()
def _udev_handler(self, fd, events): def _udev_handler(self, fd, events):
action, device = self.udevMon.receive_device() action, device = self.udevMon.receive_device()
if device is None or self.dev is not None: return if device is None or self.dev is not None: return
@@ -324,7 +290,6 @@ class Camera(object):
self.have_camera = False self.have_camera = False
self.close() self.close()
def _send_frame(self, frame): def _send_frame(self, frame):
if not len(self.clients): return if not len(self.clients): return
@@ -337,14 +302,14 @@ class Camera(object):
except Exception as e: except Exception as e:
self.log.warning('Failed to write frame to client: %s' % e) self.log.warning('Failed to write frame to client: %s' % e)
def _fd_handler(self, fd, events): def _fd_handler(self, fd, events):
try: try:
if len(self.clients): if len(self.clients):
frame = self.dev.read_frame() frame = self.dev.read_frame()
self._send_frame(frame) self._send_frame(frame)
else: self.dev.flush_frame() else:
self.dev.flush_frame()
except Exception as e: except Exception as e:
if isinstance(e, BlockingIOError): return if isinstance(e, BlockingIOError): return
@@ -353,7 +318,6 @@ class Camera(object):
self.ioloop.remove_handler(fd) self.ioloop.remove_handler(fd)
self.close() self.close()
def _update_client_image(self): def _update_client_image(self):
if self.have_camera and not self.overtemp: return if self.have_camera and not self.overtemp: return
if self.overtemp and self.have_camera: img = 'overtemp' 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) if len(self.clients): self.clients[-1].write_img(img)
def open(self, path): def open(self, path):
try: try:
self._update_client_image() self._update_client_image()
@@ -376,9 +339,9 @@ class Camera(object):
if caps.capabilities & v4l2.V4L2_CAP_VIDEO_CAPTURE == 0: if caps.capabilities & v4l2.V4L2_CAP_VIDEO_CAPTURE == 0:
raise Exception('Video capture not supported.') raise Exception('Video capture not supported.')
fourcc = string_to_fourcc(self.fourcc) fourcc = string_to_fourcc(self.fourcc)
formats = self.dev.get_formats() 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('Formats: %s', formats)
self.log.info('Sizes: %s', sizes) self.log.info('Sizes: %s', sizes)
@@ -391,7 +354,7 @@ class Camera(object):
if not hasFormat: if not hasFormat:
raise Exception(self.fourcc + ' video format not supported.') 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.set_fps(self.fps)
self.dev.create_buffers(4) self.dev.create_buffers(4)
self.dev.start() self.dev.start()
@@ -401,22 +364,20 @@ class Camera(object):
self.log.info('Opened camera ' + path) self.log.info('Opened camera ' + path)
except Exception as e: except Exception as e:
self.log.warning('While loading camera: %s' % e) self.log.warning('While loading camera: %s' % e)
self._close_dev() self._close_dev()
def _close_dev(self): def _close_dev(self):
if self.dev is None: return if self.dev is None: return
try: try:
self.dev.close() 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 self.dev = None
def close(self, overtemp=False):
def close(self, overtemp = False):
self._update_client_image() self._update_client_image()
if self.dev is None: return if self.dev is None: return
@@ -424,14 +385,17 @@ class Camera(object):
self.ioloop.remove_handler(self.dev) self.ioloop.remove_handler(self.dev)
try: try:
self.dev.stop() self.dev.stop()
except: pass except:
pass
self._close_dev() self._close_dev()
self.log.info('Closed camera') self.log.info('Closed camera')
except: self.log.exception('Internal error: Exception while closing camera') except:
finally: self.dev = None self.log.exception(
'Internal error: Exception while closing camera')
finally:
self.dev = None
def add_client(self, client): def add_client(self, client):
self.log.info('Adding camera client: %d' % len(self.clients)) self.log.info('Adding camera client: %d' % len(self.clients))
@@ -442,13 +406,12 @@ class Camera(object):
self.clients.append(client) self.clients.append(client)
self._update_client_image() self._update_client_image()
def remove_client(self, client): def remove_client(self, client):
self.log.info('Removing camera client') self.log.info('Removing camera client')
try: try:
self.clients.remove(client) self.clients.remove(client)
except: pass except:
pass
def set_overtemp(self, overtemp): def set_overtemp(self, overtemp):
if self.overtemp == overtemp: return if self.overtemp == overtemp: return
@@ -458,36 +421,32 @@ class Camera(object):
elif self.path is not None: self.open(self.path) elif self.path is not None: self.open(self.path)
class VideoHandler(web.RequestHandler): class VideoHandler(web.RequestHandler):
boundary = '-f36a3a39e5c955484390e0e3a6b031d1---' boundary = '-f36a3a39e5c955484390e0e3a6b031d1---'
def __init__(self, app, request, **kwargs): def __init__(self, app, request, **kwargs):
super().__init__(app, request, **kwargs) super().__init__(app, request, **kwargs)
self.camera = app.camera self.camera = app.camera
@web.asynchronous @web.asynchronous
def get(self): def get(self):
self.request.connection.stream.max_write_buffer_size = 10000 self.request.connection.stream.max_write_buffer_size = 10000
self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, ' self.set_header(
'pre-check=0, post-check=0, max-age=0') '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('Connection', 'close')
self.set_header('Content-Type', 'multipart/x-mixed-replace;boundary=' + self.set_header('Content-Type',
self.boundary) 'multipart/x-mixed-replace;boundary=' + self.boundary)
self.set_header('Expires', 'Mon, 3 Jan 2000 12:34:56 GMT') self.set_header('Expires', 'Mon, 3 Jan 2000 12:34:56 GMT')
self.set_header('Pragma', 'no-cache') self.set_header('Pragma', 'no-cache')
if self.camera is None: self.write_img('offline') if self.camera is None: self.write_img('offline')
else: self.camera.add_client(self) else: self.camera.add_client(self)
def write_img(self, name): def write_img(self, name):
self.write_frame_twice(get_image_resource('http/images/%s.jpg' % name)) self.write_frame_twice(get_image_resource('http/images/%s.jpg' % name))
def write_frame(self, frame): def write_frame(self, frame):
# Don't allow too many frames to queue up # Don't allow too many frames to queue up
min_size = len(frame) * 2 min_size = len(frame) * 2
@@ -499,12 +458,11 @@ class VideoHandler(web.RequestHandler):
self.flush() self.flush()
except iostream.StreamBufferFullError: except iostream.StreamBufferFullError:
pass # Drop frame if buffer is full pass # Drop frame if buffer is full
def write_frame_twice(self, frame): def write_frame_twice(self, frame):
self.write_frame(frame) self.write_frame(frame)
self.write_frame(frame) self.write_frame(frame)
def on_connection_close(self):
def on_connection_close(self): self.camera.remove_client(self) self.camera.remove_client(self)

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import struct
import base64 import base64
import json import json
import struct
# Keep this in sync with AVR code command.def # Keep this in sync with AVR code command.def
SET = '$' SET = '$'
SET_SYNC = '#' SET_SYNC = '#'
MODBUS_READ = 'm' MODBUS_READ = 'm'
MODBUS_WRITE = 'M' MODBUS_WRITE = 'M'
SEEK = 's' SEEK = 's'
SET_AXIS = 'a' SET_AXIS = 'a'
LINE = 'l' LINE = 'l'
SYNC_SPEED = '%' SYNC_SPEED = '%'
SPEED = 'p' SPEED = 'p'
INPUT = 'I' INPUT = 'I'
DWELL = 'd' DWELL = 'd'
PAUSE = 'P' PAUSE = 'P'
STOP = 'S' STOP = 'S'
UNPAUSE = 'U' UNPAUSE = 'U'
JOG = 'j' JOG = 'j'
REPORT = 'r' REPORT = 'r'
REBOOT = 'R' REBOOT = 'R'
RESUME = 'c' RESUME = 'c'
ESTOP = 'E' ESTOP = 'E'
SHUTDOWN = 'X' SHUTDOWN = 'X'
CLEAR = 'C' CLEAR = 'C'
FLUSH = 'F' FLUSH = 'F'
DUMP = 'D' DUMP = 'D'
HELP = 'h' HELP = 'h'
SEEK_ACTIVE = 1 << 0 SEEK_ACTIVE = 1 << 0
SEEK_ERROR = 1 << 1 SEEK_ERROR = 1 << 1
def encode_float(x): def encode_float(x):
@@ -95,9 +66,16 @@ def set_float(name, value):
return SET_SYNC + '%s=:%s' % (name, encode_float(value)) return SET_SYNC + '%s=:%s' % (name, encode_float(value))
def modbus_read(addr): return MODBUS_READ + '%d' % addr def modbus_read(addr):
def modbus_write(addr, value): return MODBUS_WRITE + '%d=%d' % (addr, value) return MODBUS_READ + '%d' % addr
def set_axis(axis, position): return SET_AXIS + axis + encode_float(position)
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): def line(target, exitVel, maxAccel, maxJerk, times, speeds):
@@ -111,7 +89,7 @@ def line(target, exitVel, maxAccel, maxJerk, times, speeds):
# S-Curve time parameters # S-Curve time parameters
for i in range(7): for i in range(7):
if times[i]: if times[i]:
cmd += str(i) + encode_float(times[i] / 60000) # to mins cmd += str(i) + encode_float(times[i] / 60000) # to mins
# Speeds # Speeds
for dist, speed in speeds: for dist, speed in speeds:
@@ -120,7 +98,8 @@ def line(target, exitVel, maxAccel, maxJerk, times, speeds):
return cmd return cmd
def speed(value): return SPEED + encode_float(value) def speed(value):
return SPEED + encode_float(value)
def sync_speed(dist, speed): 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-1': type, index = 'd', 1
if port == 'digital-in-2': type, index = 'd', 2 if port == 'digital-in-2': type, index = 'd', 2
if port == 'digital-in-3': type, index = 'd', 3 if port == 'digital-in-3': type, index = 'd', 3
if port == 'analog-in-0': type, index = 'a', 0 if port == 'analog-in-0': type, index = 'a', 0
if port == 'analog-in-1': type, index = 'a', 1 if port == 'analog-in-1': type, index = 'a', 1
if port == 'analog-in-2': type, index = 'a', 2 if port == 'analog-in-2': type, index = 'a', 2
if port == 'analog-in-3': type, index = 'a', 3 if port == 'analog-in-3': type, index = 'a', 3
# Mode # Mode
if mode == 'immediate': m = 0 if mode == 'immediate': m = 0
if mode == 'rise': m = 1 if mode == 'rise': m = 1
if mode == 'fall': m = 2 if mode == 'fall': m = 2
if mode == 'high': m = 3 if mode == 'high': m = 3
if mode == 'low': m = 4 if mode == 'low': m = 4
return '%s%s%d%d%s' % (INPUT, type, index, m, encode_float(timeout)) return '%s%s%d%d%s' % (INPUT, type, index, m, encode_float(timeout))
def output(port, value): 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') if port == 'flood': return '#2oa=' + ('1' if value else '0')
raise Exception('Unsupported output "%s"' % port) 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): def pause(type):
@@ -168,7 +148,8 @@ def pause(type):
return '%s%d' % (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): def seek(switch, active, error):
@@ -177,7 +158,7 @@ def seek(switch, active, error):
flags = 0 flags = 0
if active: flags |= SEEK_ACTIVE if active: flags |= SEEK_ACTIVE
if error: flags |= SEEK_ERROR if error: flags |= SEEK_ERROR
cmd += chr(flags + ord('0')) cmd += chr(flags + ord('0'))
return cmd return cmd
@@ -213,7 +194,6 @@ def decode_command(cmd):
if name in 'xyzabcuvw': data[name] = value if name in 'xyzabcuvw': data[name] = value
elif cmd[0] == SEEK: elif cmd[0] == SEEK:
data['type'] = 'seek' data['type'] = 'seek'
@@ -225,9 +205,9 @@ def decode_command(cmd):
elif cmd[0] == LINE: elif cmd[0] == LINE:
data['type'] = '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-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['target'] = {}
data['times'] = [0] * 7 data['times'] = [0] * 7
@@ -244,16 +224,24 @@ def decode_command(cmd):
elif cmd[0] == SYNC_SPEED: elif cmd[0] == SYNC_SPEED:
data['type'] = 'speed' data['type'] = 'speed'
data['offset'] = decode_float(cmd[1:7]) 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] == REPORT:
elif cmd[0] == PAUSE: data['type'] = 'pause' data['type'] = 'report'
elif cmd[0] == UNPAUSE: data['type'] = 'unpause' elif cmd[0] == PAUSE:
elif cmd[0] == ESTOP: data['type'] = 'estop' data['type'] = 'pause'
elif cmd[0] == SHUTDOWN: data['type'] = 'shutdown' elif cmd[0] == UNPAUSE:
elif cmd[0] == CLEAR: data['type'] = 'clear' data['type'] = 'unpause'
elif cmd[0] == FLUSH: data['type'] = 'flush' elif cmd[0] == ESTOP:
elif cmd[0] == RESUME: data['type'] = 'resume' 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 return data

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import serial
import json
import time
import traceback
from collections import deque from collections import deque
import bbctrl
import bbctrl.Cmd as Cmd import bbctrl.Cmd as Cmd
import json
import traceback
# Must be kept in sync with drv8711.h # Must be kept in sync with drv8711.h
DRV8711_STATUS_OTS_bm = 1 << 0 DRV8711_STATUS_OTS_bm = 1 << 0
DRV8711_STATUS_AOCP_bm = 1 << 1 DRV8711_STATUS_AOCP_bm = 1 << 1
DRV8711_STATUS_BOCP_bm = 1 << 2 DRV8711_STATUS_BOCP_bm = 1 << 2
DRV8711_STATUS_APDF_bm = 1 << 3 DRV8711_STATUS_APDF_bm = 1 << 3
DRV8711_STATUS_BPDF_bm = 1 << 4 DRV8711_STATUS_BPDF_bm = 1 << 4
DRV8711_STATUS_UVLO_bm = 1 << 5 DRV8711_STATUS_UVLO_bm = 1 << 5
DRV8711_STATUS_STD_bm = 1 << 6 DRV8711_STATUS_STD_bm = 1 << 6
DRV8711_STATUS_STDLAT_bm = 1 << 7 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 # Ignoring stall and stall latch flags for now
DRV8711_MASK = ~(DRV8711_STATUS_STD_bm | DRV8711_STATUS_STDLAT_bm) DRV8711_MASK = ~(DRV8711_STATUS_STD_bm | DRV8711_STATUS_STDLAT_bm)
def _driver_flags_to_string(flags): def _driver_flags_to_string(flags):
if DRV8711_STATUS_OTS_bm & flags: yield 'over temp' if DRV8711_STATUS_OTS_bm & flags: yield 'over temp'
if DRV8711_STATUS_AOCP_bm & flags: yield 'over current a' if DRV8711_STATUS_AOCP_bm & flags: yield 'over current a'
if DRV8711_STATUS_BOCP_bm & flags: yield 'over current b' if DRV8711_STATUS_BOCP_bm & flags: yield 'over current b'
if DRV8711_STATUS_APDF_bm & flags: yield 'driver fault a' if DRV8711_STATUS_APDF_bm & flags: yield 'driver fault a'
if DRV8711_STATUS_BPDF_bm & flags: yield 'driver fault b' if DRV8711_STATUS_BPDF_bm & flags: yield 'driver fault b'
if DRV8711_STATUS_UVLO_bm & flags: yield 'undervoltage' if DRV8711_STATUS_UVLO_bm & flags: yield 'undervoltage'
if DRV8711_STATUS_STD_bm & flags: yield 'stall' if DRV8711_STATUS_STD_bm & flags: yield 'stall'
if DRV8711_STATUS_STDLAT_bm & flags: yield 'stall latch' 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): def driver_flags_to_string(flags):
@@ -67,6 +35,7 @@ def driver_flags_to_string(flags):
class Comm(object): class Comm(object):
def __init__(self, ctrl, avr): def __init__(self, ctrl, avr):
self.ctrl = ctrl self.ctrl = ctrl
self.avr = avr self.avr = avr
@@ -79,41 +48,38 @@ class Comm(object):
avr.set_handlers(self._read, self._write) avr.set_handlers(self._read, self._write)
self._poll_cb(False) self._poll_cb(False)
def comm_next(self):
raise Exception('Not implemented')
def comm_next(self): raise Exception('Not implemented') def comm_error(self):
def comm_error(self): raise Exception('Not implemented') 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.log.info('I2C: %s b=%s w=%s d=%s' % (cmd, byte, word, block))
self.avr.i2c_command(cmd, byte, word, block) self.avr.i2c_command(cmd, byte, word, block)
def flush(self):
def flush(self): self.avr.enable_write(True) self.avr.enable_write(True)
def _load_next_command(self, cmd): def _load_next_command(self, cmd):
self.log.info('< ' + json.dumps(cmd).strip('"')) self.log.info('< ' + json.dumps(cmd).strip('"'))
self.command = bytes(cmd.strip() + '\n', 'utf-8') self.command = bytes(cmd.strip() + '\n', 'utf-8')
def resume(self):
def resume(self): self.queue_command(Cmd.RESUME) self.queue_command(Cmd.RESUME)
def queue_command(self, cmd): def queue_command(self, cmd):
self.queue.append(cmd) self.queue.append(cmd)
self.flush() self.flush()
def _poll_cb(self, now=True):
def _poll_cb(self, now = True):
# Checks periodically for new commands from planner via comm_next() # Checks periodically for new commands from planner via comm_next()
if now: self.flush() if now: self.flush()
self.ctrl.ioloop.call_later(1, self._poll_cb) self.ctrl.ioloop.call_later(1, self._poll_cb)
def _write(self, write_cb): def _write(self, write_cb):
# Finish writing current command # Finish writing current command
if self.command is not None: if self.command is not None:
@@ -125,25 +91,25 @@ class Comm(object):
raise e raise e
self.command = self.command[count:] self.command = self.command[count:]
if len(self.command): return # There's more if len(self.command): return # There's more
self.command = None self.command = None
# Load next command from queue # 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: 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) else: self._load_next_command(cmd)
def _update_vars(self, msg): def _update_vars(self, msg):
try: try:
self.ctrl.state.set_machine_vars(msg['variables']) self.ctrl.state.set_machine_vars(msg['variables'])
self.ctrl.configure() self.ctrl.configure()
self.queue_command(Cmd.DUMP) # Refresh all vars self.queue_command(Cmd.DUMP) # Refresh all vars
# Set axis positions # Set axis positions
for axis in 'xyzabc': for axis in 'xyzabc':
@@ -154,16 +120,15 @@ class Comm(object):
self.log.warning('AVR reload failed: %s', traceback.format_exc()) self.log.warning('AVR reload failed: %s', traceback.format_exc())
self.ctrl.ioloop.call_later(1, self.connect) self.ctrl.ioloop.call_later(1, self.connect)
def _log_msg(self, msg): def _log_msg(self, msg):
level = msg.get('level', 'info') level = msg.get('level', 'info')
where = msg.get('where') where = msg.get('where')
msg = msg['msg'] msg = msg['msg']
if level == 'info': self.log.info(msg, where = where) if level == 'info': self.log.info(msg, where=where)
elif level == 'debug': self.log.debug(msg, where = where) elif level == 'debug': self.log.debug(msg, where=where)
elif level == 'warning': self.log.warning(msg, where = where) elif level == 'warning': self.log.warning(msg, where=where)
elif level == 'error': self.log.error(msg, where = where) elif level == 'error': self.log.error(msg, where=where)
if level == 'error': self.comm_error() if level == 'error': self.comm_error()
@@ -171,7 +136,6 @@ class Comm(object):
if level == 'warning' and 'code' in msg and msg['code'] == 11: if level == 'warning' and 'code' in msg and msg['code'] == 11:
self.comm_error() self.comm_error()
def _log_motor_flags(self, update): def _log_motor_flags(self, update):
for motor in range(3): for motor in range(3):
var = '%ddf' % motor var = '%ddf' % motor
@@ -185,17 +149,15 @@ class Comm(object):
flags = driver_flags_to_string(flags) flags = driver_flags_to_string(flags)
self.log.info('Motor %d flags: %s' % (motor, flags)) self.log.info('Motor %d flags: %s' % (motor, flags))
def _update_state(self, update): def _update_state(self, update):
self.ctrl.state.update(update) self.ctrl.state.update(update)
if 'xx' in update: # State change if 'xx' in update: # State change
self.ctrl.ready() # We've received data from AVR self.ctrl.ready() # We've received data from AVR
self.flush() # May have more data to send now self.flush() # May have more data to send now
self._log_motor_flags(update) self._log_motor_flags(update)
def _read(self, data): def _read(self, data):
self.in_buf += data.decode('utf-8') self.in_buf += data.decode('utf-8')
@@ -227,29 +189,25 @@ class Comm(object):
else: else:
self._update_state(msg) self._update_state(msg)
def estop(self): def estop(self):
if self.ctrl.state.get('xx', '') != 'ESTOPPED': if self.ctrl.state.get('xx', '') != 'ESTOPPED':
self.i2c_command(Cmd.ESTOP) self.i2c_command(Cmd.ESTOP)
def clear(self): def clear(self):
if self.ctrl.state.get('xx', '') == 'ESTOPPED': if self.ctrl.state.get('xx', '') == 'ESTOPPED':
self.i2c_command(Cmd.CLEAR) self.i2c_command(Cmd.CLEAR)
def pause(self): def pause(self):
self.i2c_command(Cmd.PAUSE, byte = ord('0')) # User pause self.i2c_command(Cmd.PAUSE, byte=ord('0')) # User pause
def reboot(self): self.queue_command(Cmd.REBOOT)
def reboot(self):
self.queue_command(Cmd.REBOOT)
def connect(self): def connect(self):
try: try:
# Resume once current queue of GCode commands has flushed # Resume once current queue of GCode commands has flushed
self.queue_command(Cmd.RESUME) 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: except Exception as e:
self.log.warning('Connect failed: %s', e) self.log.warning('Connect failed: %s', e)

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import bbctrl
from collections import deque from collections import deque
import bbctrl
# 16-bit less with wrap around # 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(): class CommandQueue():
def __init__(self, ctrl): def __init__(self, ctrl):
self.log = ctrl.log.get('CmdQ') self.log = ctrl.log.get('CmdQ')
self.log.set_level(bbctrl.log.WARNING) self.log.set_level(bbctrl.log.WARNING)
@@ -42,23 +17,20 @@ class CommandQueue():
self.releaseID = 0 self.releaseID = 0
self.q = deque() self.q = deque()
def is_active(self):
def is_active(self): return len(self.q) return len(self.q)
def clear(self): def clear(self):
self.lastEnqueueID = 0 self.lastEnqueueID = 0
self.releaseID = 0 self.releaseID = 0
self.q.clear() self.q.clear()
def enqueue(self, id, cb, *args, **kwargs): def enqueue(self, id, cb, *args, **kwargs):
self.log.info('add(#%d) releaseID=%d', id, self.releaseID) self.log.info('add(#%d) releaseID=%d', id, self.releaseID)
self.lastEnqueueID = id self.lastEnqueueID = id
self.q.append([id, cb, args, kwargs]) self.q.append([id, cb, args, kwargs])
self._release() self._release()
def _release(self): def _release(self):
while len(self.q): while len(self.q):
id, cb, args, kwargs = self.q[0] id, cb, args, kwargs = self.q[0]
@@ -72,9 +44,8 @@ class CommandQueue():
try: try:
if cb is not None: cb(*args, **kwargs) if cb is not None: cb(*args, **kwargs)
except Exception: except Exception:
self.log.exception('Internal error: Command queue callback error') self.log.exception(
'Internal error: Command queue callback error')
def release(self, id): def release(self, id):
if id and not id_less(self.releaseID, id): if id and not id_less(self.releaseID, id):

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import os
import json
import pkg_resources
from pkg_resources import Requirement, resource_filename from pkg_resources import Requirement, resource_filename
import json
import os
import pkg_resources
def get_resource(path): def get_resource(path):
@@ -36,6 +9,7 @@ def get_resource(path):
class Config(object): class Config(object):
def __init__(self, ctrl): def __init__(self, ctrl):
self.ctrl = ctrl self.ctrl = ctrl
self.log = ctrl.log.get('Config') self.log = ctrl.log.get('Config')
@@ -46,24 +20,29 @@ class Config(object):
self.version = pkg_resources.require('bbctrl')[0].version self.version = pkg_resources.require('bbctrl')[0].version
# Load config template # Load config template
with open(get_resource('http/config-template.json'), 'r', with open(get_resource('http/config-template.json'),
encoding = 'utf-8') as f: 'r',
encoding='utf-8') as f:
self.template = json.load(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): def load(self):
path = self.ctrl.get_path('config.json') path = self.ctrl.get_path('config.json')
try: try:
if os.path.exists(path): if os.path.exists(path):
with open(path, 'r') as f: config = json.load(f) with open(path, 'r') as f:
else: config = {'version': self.version} config = json.load(f)
else:
config = {'version': self.version}
try: try:
self._upgrade(config) 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: except Exception as e:
self.log.warning('%s', e) self.log.warning('%s', e)
@@ -72,15 +51,12 @@ class Config(object):
self._defaults(config) self._defaults(config)
return config return config
def reload(self): def reload(self):
self._update(self.load(), True) self._update(self.load(), True)
def get(self, name, default=None):
def get(self, name, default = None):
return self.values.get(name, default) return self.values.get(name, default)
def save(self, config): def save(self, config):
self._upgrade(config) self._upgrade(config)
self._update(config, False) self._update(config, False)
@@ -93,21 +69,19 @@ class Config(object):
self.ctrl.preplanner.invalidate_all() self.ctrl.preplanner.invalidate_all()
self.log.info('Saved') self.log.info('Saved')
def reset(self): def reset(self):
if os.path.exists('config.json'): os.unlink('config.json') if os.path.exists('config.json'): os.unlink('config.json')
self.reload() self.reload()
self.ctrl.preplanner.invalidate_all() self.ctrl.preplanner.invalidate_all()
def _valid_value(self, template, value): def _valid_value(self, template, value):
type = template['type'] type = template['type']
try: try:
if type == 'int': value = int(value) if type == 'int': value = int(value)
if type == 'float': value = float(value) if type == 'float': value = float(value)
if type == 'text': value = str(value) if type == 'text': value = str(value)
if type == 'bool': value = bool(value) if type == 'bool': value = bool(value)
except: except:
return False return False
@@ -116,11 +90,10 @@ class Config(object):
return True return True
def __defaults(self, config, name, template): def __defaults(self, config, name, template):
if 'type' in template: if 'type' in template:
if (not name in config or if (not name in config
not self._valid_value(template, config[name])): or not self._valid_value(template, config[name])):
config[name] = template['default'] config[name] = template['default']
elif 'max' in template and template['max'] < config[name]: elif 'max' in template and template['max'] < config[name]:
@@ -142,21 +115,21 @@ class Config(object):
for name, tmpl in template.items(): for name, tmpl in template.items():
self.__defaults(config, name, tmpl) self.__defaults(config, name, tmpl)
def _defaults(self, config): def _defaults(self, config):
for name, tmpl in self.template.items(): for name, tmpl in self.template.items():
if not 'type' in tmpl: if not 'type' in tmpl:
if not name in config: config[name] = {} if not name in config: config[name] = {}
conf = config[name] conf = config[name]
else: conf = config else:
conf = config
self.__defaults(conf, name, tmpl) self.__defaults(conf, name, tmpl)
def _upgrade(self, config): def _upgrade(self, config):
version = config['version'] version = config['version']
version = version.split('b')[0] # Strip off any "beta" suffix version = version.split('b')[0] # Strip off any "beta" suffix
version = tuple(map(int, version.split('.'))) # Break it into a tuple of integers version = tuple(map(
int, version.split('.'))) # Break it into a tuple of integers
if version < (1, 0, 7): if version < (1, 0, 7):
config['settings']['max-deviation'] = 0.001 config['settings']['max-deviation'] = 0.001
@@ -186,21 +159,24 @@ class Config(object):
config['settings']['junction-accel'] = 200000 config['settings']['junction-accel'] = 200000
if version < (1, 0, 9): 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) 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['version'] = self.version.split('b')[0]
config['full_version'] = self.version config['full_version'] = self.version
def _encode(self, name, index, config, tmpl, with_defaults): def _encode(self, name, index, config, tmpl, with_defaults):
# Handle category # Handle category
if not 'type' in tmpl: if not 'type' in tmpl:
for name, entry in tmpl.items(): for name, entry in tmpl.items():
if 'type' in entry and config is not None: if 'type' in entry and config is not None:
conf = config.get(name, None) conf = config.get(name, None)
else: conf = config else:
conf = config
self._encode(name, index, conf, entry, with_defaults) self._encode(name, index, conf, entry, with_defaults)
return return
@@ -223,7 +199,8 @@ class Config(object):
if not name in self.values: self.values[name] = {} if not name in self.values: self.values[name] = {}
self.values[name][index] = value self.values[name][index] = value
else: self.values[name] = value else:
self.values[name] = value
# Update state variable # Update state variable
if not 'code' in tmpl: return if not 'code' in tmpl: return
@@ -237,7 +214,6 @@ class Config(object):
self.ctrl.state.config(index + tmpl['code'], value) self.ctrl.state.config(index + tmpl['code'], value)
def _update(self, config, with_defaults): def _update(self, config, with_defaults):
for name, tmpl in self.template.items(): for name, tmpl in self.template.items():
conf = config.get(name, None) conf = config.get(name, None)

View File

@@ -1,8 +1,9 @@
import os
import bbctrl import bbctrl
import os
class Ctrl(object): class Ctrl(object):
def __init__(self, args, ioloop, id): def __init__(self, args, ioloop, id):
self.args = args self.args = args
self.ioloop = bbctrl.IOLoop(ioloop) self.ioloop = bbctrl.IOLoop(ioloop)
@@ -42,7 +43,8 @@ class Ctrl(object):
self.log.get('Ctrl').exception( self.log.get('Ctrl').exception(
'Internal error: Control initialization failed') 'Internal error: Control initialization failed')
def __del__(self): print('Ctrl deleted') def __del__(self):
print('Ctrl deleted')
def clear_timeout(self): def clear_timeout(self):
if self.timeout is not None: if self.timeout is not None:

View File

@@ -1,11 +1,12 @@
import os from tornado import gen
import tempfile from tornado.escape import url_unescape
from tornado.web import HTTPError
import bbctrl import bbctrl
import glob import glob
import os
import tempfile
import tornado import tornado
from tornado import gen
from tornado.web import HTTPError
from tornado.escape import url_unescape;
def safe_remove(path): def safe_remove(path):
try: try:
@@ -16,9 +17,10 @@ def safe_remove(path):
@tornado.web.stream_request_body @tornado.web.stream_request_body
class FileHandler(bbctrl.APIHandler): class FileHandler(bbctrl.APIHandler):
def prepare(self): def prepare(self):
if self.request.method == 'PUT': 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] filename = self.request.path.split('/')[-1]
self.uploadFilename = url_unescape(filename) \ self.uploadFilename = url_unescape(filename) \
@@ -57,14 +59,14 @@ class FileHandler(bbctrl.APIHandler):
self.uploadFile.close() self.uploadFile.close()
del(self.uploadFile) del (self.uploadFile)
self.get_ctrl().preplanner.invalidate(self.uploadFilename) self.get_ctrl().preplanner.invalidate(self.uploadFilename)
self.get_ctrl().state.add_file(self.uploadFilename) self.get_ctrl().state.add_file(self.uploadFilename)
self.get_log('FileHandler').info( self.get_log('FileHandler').info('GCode received: ' +
'GCode received: ' + self.uploadFilename) self.uploadFilename)
del(self.uploadFilename) del (self.uploadFilename)
@gen.coroutine @gen.coroutine
def get(self, filename): def get(self, filename):

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import errno import errno
try: try:
@@ -37,12 +10,12 @@ except:
class I2C(object): class I2C(object):
def __init__(self, port, disabled): def __init__(self, port, disabled):
self.port = port self.port = port
self.i2c_bus = None self.i2c_bus = None
self.disabled = disabled or smbus is None self.disabled = disabled or smbus is None
def connect(self): def connect(self):
if self.disabled: return if self.disabled: return
if self.i2c_bus is None: if self.i2c_bus is None:
@@ -54,7 +27,6 @@ class I2C(object):
if e.errno == errno.ENOENT: self.disabled = True if e.errno == errno.ENOENT: self.disabled = True
else: raise type(e)('I2C failed to open device: %s' % e) else: raise type(e)('I2C failed to open device: %s' % e)
def read_word(self, addr): def read_word(self, addr):
self.connect() self.connect()
if self.disabled: return if self.disabled: return
@@ -67,8 +39,7 @@ class I2C(object):
self.i2c_bus = None self.i2c_bus = None
raise type(e)('I2C read word failed: %s' % e) 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() self.connect()
if self.disabled: return if self.disabled: return
@@ -83,7 +54,8 @@ class I2C(object):
if isinstance(block, str): block = list(map(ord, block)) if isinstance(block, str): block = list(map(ord, block))
self.i2c_bus.write_i2c_block_data(addr, cmd, 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: except IOError as e:
self.i2c_bus.close() self.i2c_bus.close()

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import tornado.ioloop import tornado.ioloop
import bbctrl
class CB(object): class CB(object):
def __init__(self, ioloop, delay, cb, *args, **kwargs): def __init__(self, ioloop, delay, cb, *args, **kwargs):
self.ioloop = ioloop self.ioloop = ioloop
self.cb = cb self.cb = cb
@@ -49,45 +22,41 @@ class IOLoop(object):
WRITE = tornado.ioloop.IOLoop.WRITE WRITE = tornado.ioloop.IOLoop.WRITE
ERROR = tornado.ioloop.IOLoop.ERROR ERROR = tornado.ioloop.IOLoop.ERROR
def __init__(self, ioloop): def __init__(self, ioloop):
self.ioloop = ioloop self.ioloop = ioloop
self.fds = set() self.fds = set()
self.handles = set() self.handles = set()
self.callbacks = {} self.callbacks = {}
def close(self): def close(self):
for fd in list(self.fds): self.ioloop.remove_handler(fd) for fd in list(self.fds):
for h in list(self.handles): self.ioloop.remove_timeout(h) self.ioloop.remove_handler(fd)
for h in list(self.callbacks): self.ioloop.remove_timeout(h) 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): def add_handler(self, fd, handler, events):
self.ioloop.add_handler(fd, handler, events) self.ioloop.add_handler(fd, handler, events)
if hasattr(fd, 'fileno'): fd = fd.fileno() if hasattr(fd, 'fileno'): fd = fd.fileno()
self.fds.add(fd) self.fds.add(fd)
def remove_handler(self, h): def remove_handler(self, h):
self.ioloop.remove_handler(h) self.ioloop.remove_handler(h)
if hasattr(h, 'fileno'): h = h.fileno() if hasattr(h, 'fileno'): h = h.fileno()
self.fds.remove(h) self.fds.remove(h)
def update_handler(self, fd, events):
def update_handler(self, fd, events): self.ioloop.update_handler(fd, events) self.ioloop.update_handler(fd, events)
def call_later(self, delay, callback, *args, **kwargs): def call_later(self, delay, callback, *args, **kwargs):
cb = CB(self, delay, callback, *args, **kwargs) cb = CB(self, delay, callback, *args, **kwargs)
return cb.h return cb.h
def remove_timeout(self, h): def remove_timeout(self, h):
self.ioloop.remove_timeout(h) self.ioloop.remove_timeout(h)
if h in self.handles: self.handles.remove(h) if h in self.handles: self.handles.remove(h)
if h in self.callbacks: del self.callbacks[h] if h in self.callbacks: del self.callbacks[h]
def add_callback(self, cb, *args, **kwargs): def add_callback(self, cb, *args, **kwargs):
self.ioloop.add_callback(cb, *args, **kwargs) self.ioloop.add_callback(cb, *args, **kwargs)

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import os
import sys
import io
import datetime import datetime
import traceback import os
import pkg_resources import pkg_resources
from inspect import getframeinfo, stack import sys
import bbctrl import traceback
DEBUG = 0
INFO = 1
MESSAGE = 2
WARNING = 3
ERROR = 4
DEBUG = 0
INFO = 1
MESSAGE = 2
WARNING = 3
ERROR = 4
level_names = 'debug info message warning error'.split() 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 # Get this file's name
@@ -52,15 +22,17 @@ _srcfile = os.path.normcase(get_level_name.__code__.co_filename)
class Logger(object): class Logger(object):
def __init__(self, log, name, level): def __init__(self, log, name, level):
self.log = log self.log = log
self.name = name self.name = name
self.level = level self.level = level
def set_level(self, level):
self.level = level
def set_level(self, level): self.level = level def _enabled(self, level):
def _enabled(self, level): return self.level <= level and level <= ERROR return self.level <= level and level <= ERROR
def _find_caller(self): def _find_caller(self):
f = sys._getframe() f = sys._getframe()
@@ -78,7 +50,6 @@ class Logger(object):
return '(unknown file)', 0, '(unknown function)' return '(unknown file)', 0, '(unknown function)'
def _log(self, level, msg, *args, **kwargs): def _log(self, level, msg, *args, **kwargs):
if not self._enabled(level): return if not self._enabled(level): return
@@ -88,15 +59,22 @@ class Logger(object):
if len(args): msg %= args 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):
def message(self, *args, **kwargs): self._log(MESSAGE, *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 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): def exception(self, *args, **kwargs):
msg = traceback.format_exc() msg = traceback.format_exc()
@@ -104,7 +82,9 @@ class Logger(object):
self._log(INFO, msg, **kwargs) self._log(INFO, msg, **kwargs)
self._log(ERROR, *args, **kwargs) self._log(ERROR, *args, **kwargs)
class Log(object): class Log(object):
def __init__(self, args, ioloop, path): def __init__(self, args, ioloop, path):
self.path = path self.path = path
self.listeners = [] self.listeners = []
@@ -121,29 +101,29 @@ class Log(object):
self._log('Log started v%s' % version) self._log('Log started v%s' % version)
self._log_time(ioloop) 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):
def remove_listener(self, listener): self.listeners.remove(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: if not name in self.loggers:
self.loggers[name] = Logger(self, name, self.level) self.loggers[name] = Logger(self, name, self.level)
return self.loggers[name] return self.loggers[name]
def _log_time(self, ioloop): def _log_time(self, ioloop):
self._log(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')) self._log(datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S'))
ioloop.call_later(60 * 60, self._log_time, ioloop) ioloop.call_later(60 * 60, self._log_time, ioloop)
def broadcast(self, msg): 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 if not msg: return
hdr = '%s:%s:' % ('DIMWE'[level], prefix) hdr = '%s:%s:' % ('DIMWE'[level], prefix)
@@ -160,11 +140,10 @@ class Log(object):
# Broadcast to log listeners # Broadcast to log listeners
if level == INFO: return 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 if where is not None: msg['where'] = where
self.broadcast(dict(log = msg)) self.broadcast(dict(log=msg))
def _open(self): def _open(self):
if self.path is None: return if self.path is None: return
@@ -173,8 +152,7 @@ class Log(object):
self.f = open(self.path, 'a') self.f = open(self.path, 'a')
self.bytes_written = 0 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 fullpath = '%s.%d' % (path, n) if n is not None else path
nextN = (0 if n is None else n) + 1 nextN = (0 if n is None else n) + 1

View File

@@ -1,8 +1,7 @@
import bbctrl
from bbctrl.Comm import Comm from bbctrl.Comm import Comm
import bbctrl
import bbctrl.Cmd as Cmd import bbctrl.Cmd as Cmd
# Axis homing procedure: # Axis homing procedure:
# #
# Mark axis unhomed # Mark axis unhomed
@@ -49,10 +48,11 @@ for more information.\
def overrides(interface_class): def overrides(interface_class):
def overrider(method): def overrider(method):
if not method.__name__ in dir(interface_class): if not method.__name__ in dir(interface_class):
raise Exception('%s does not override %s' % ( raise Exception('%s does not override %s' %
method.__name__, interface_class.__name__)) (method.__name__, interface_class.__name__))
return method return method
@@ -60,6 +60,7 @@ def overrides(interface_class):
class Mach(Comm): class Mach(Comm):
def __init__(self, ctrl, avr): def __init__(self, ctrl, avr):
super().__init__(ctrl, avr) super().__init__(ctrl, avr)
@@ -76,20 +77,32 @@ class Mach(Comm):
super().reboot() super().reboot()
def _get_state(self): return self.ctrl.state.get('xx', '') def _get_state(self):
def _is_estopped(self): return self._get_state() == 'ESTOPPED' return self.ctrl.state.get('xx', '')
def _is_holding(self): return self._get_state() == 'HOLDING'
def _is_ready(self): return self._get_state() == 'READY' def _is_estopped(self):
def _get_pause_reason(self): return self.ctrl.state.get('pr', '') return self._get_state() == 'ESTOPPED'
def _get_cycle(self): return self.ctrl.state.get('cycle', 'idle')
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): def _is_paused(self):
if not self._is_holding() or self.unpausing: if not self._is_holding() or self.unpausing:
return False return False
return self._get_pause_reason() in ( return self._get_pause_reason() in ('User pause', 'Program pause',
'User pause', 'Program pause', 'Optional 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): def _begin_cycle(self, cycle):
current = self._get_cycle() current = self._get_cycle()
@@ -126,9 +139,8 @@ class Mach(Comm):
self.planner.reset(stop=False) self.planner.reset(stop=False)
# Exit cycle if state changed to READY # Exit cycle if state changed to READY
if (state_changed and self._get_cycle() != 'idle' and if (state_changed and self._get_cycle() != 'idle' and self._is_ready()
self._is_ready() and not self.planner.is_busy() and and not self.planner.is_busy() and not super().is_active()):
not super().is_active()):
self.planner.position_change() self.planner.position_change()
self._set_cycle('idle') self._set_cycle('idle')
@@ -152,9 +164,9 @@ class Mach(Comm):
# Must be after holding commands above # Must be after holding commands above
op = self.ctrl.state.get('optional_pause', False) op = self.ctrl.state.get('optional_pause', False)
pr = self._get_pause_reason() pr = self._get_pause_reason()
if ((state_changed or 'pr' in update) and self._is_holding() and if ((state_changed or 'pr' in update) and self._is_holding()
(pr in ('Switch found', 'User stop') or and (pr in ('Switch found', 'User stop') or
(pr == 'Optional pause' and not op))): (pr == 'Optional pause' and not op))):
self._unpause() self._unpause()
def _unpause(self): def _unpause(self):
@@ -174,7 +186,8 @@ class Mach(Comm):
def _i2c_block(self, block): def _i2c_block(self, block):
super().i2c_command(block[0], block=block[1:]) 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) @overrides(Comm)
def comm_next(self): def comm_next(self):
@@ -255,8 +268,8 @@ class Mach(Comm):
# Error when axes cannot be homed # Error when axes cannot be homed
reason = state.axis_home_fail_reason(axis) reason = state.axis_home_fail_reason(axis)
if reason is not None: if reason is not None:
self.mlog.error('Cannot home %s axis: %s' % ( self.mlog.error('Cannot home %s axis: %s' %
axis.upper(), reason)) (axis.upper(), reason))
continue continue
if mode == 'manual': if mode == 'manual':
@@ -279,8 +292,11 @@ class Mach(Comm):
self.planner.mdi(gcode, False) self.planner.mdi(gcode, False)
super().resume() super().resume()
def unhome(self, axis): self.mdi('G28.2 %c0' % axis) def unhome(self, axis):
def estop(self): super().estop() self.mdi('G28.2 %c0' % axis)
def estop(self):
super().estop()
def clear(self): def clear(self):
if self._is_estopped(): if self._is_estopped():
@@ -290,7 +306,8 @@ class Mach(Comm):
def fake_probe_contact(self): def fake_probe_contact(self):
self._i2c_set('pt', 2) self._i2c_set('pt', 2)
self.ctrl.state.set('pw', 0) 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): def clear_fake_probe_contact(self):
self._i2c_set('pt', 1) self._i2c_set('pt', 1)
@@ -316,7 +333,8 @@ class Mach(Comm):
self.stopping = True self.stopping = True
super().i2c_command(Cmd.STOP) super().i2c_command(Cmd.STOP)
def pause(self): super().pause() def pause(self):
super().pause()
def unpause(self): def unpause(self):
if self._is_paused(): if self._is_paused():
@@ -350,7 +368,8 @@ class Mach(Comm):
def override_speed(self, override): def override_speed(self, override):
self._i2c_set('so', int(1000 * 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): def modbus_write(self, addr, value):
self._i2c_block(Cmd.modbus_write(addr, value)) self._i2c_block(Cmd.modbus_write(addr, value))

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import time import time
@@ -35,10 +8,12 @@ def read_temp():
def set_max_freq(freq): def set_max_freq(freq):
filename = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_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): class MonitorTemp(object):
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
@@ -57,7 +32,6 @@ class MonitorTemp(object):
self.callback() self.callback()
# Scale max CPU based on temperature # Scale max CPU based on temperature
def scale_cpu(self, temp): def scale_cpu(self, temp):
if temp < self.min_temp: cpu_freq = self.max_freq if temp < self.min_temp: cpu_freq = self.max_freq
@@ -69,7 +43,6 @@ class MonitorTemp(object):
set_max_freq(cpu_freq) set_max_freq(cpu_freq)
def update_camera(self, temp): def update_camera(self, temp):
if self.app.camera is None: return if self.app.camera is None: return
@@ -78,7 +51,6 @@ class MonitorTemp(object):
elif self.high_camera_temp < temp: elif self.high_camera_temp < temp:
self.app.camera.set_overtemp(True) self.app.camera.set_overtemp(True)
def log_warnings(self, temp): def log_warnings(self, temp):
# Reset temperature warning threshold after timeout # Reset temperature warning threshold after timeout
if time.time() < self.last_temp_warn + 60: self.temp_thresh = 80 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) self.log.info('Hot RaspberryPi at %d°C' % temp)
def callback(self): def callback(self):
try: try:
temp = read_temp() temp = read_temp()
@@ -99,6 +70,7 @@ class MonitorTemp(object):
self.update_camera(temp) self.update_camera(temp)
self.log_warnings(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) self.ioloop.call_later(5, self.callback)

View File

@@ -1,46 +1,16 @@
################################################################################ from bbctrl.CommandQueue import CommandQueue
# # import bbctrl.Cmd as Cmd
# This file is part of the Buildbotics firmware. # import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error
# #
# 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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import json import json
import math import math
import re import re
import time 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<level>[A-Z])[0-9 ]:'
reLogLine = re.compile( r'((?P<file>[^:]+):)?'
r'^(?P<level>[A-Z])[0-9 ]:' r'((?P<line>\d+):)?'
r'((?P<file>[^:]+):)?' r'((?P<column>\d+):)?'
r'((?P<line>\d+):)?' r'(?P<msg>.*)$')
r'((?P<column>\d+):)?'
r'(?P<msg>.*)$')
def log_floats(o): def log_floats(o):
@@ -50,10 +20,12 @@ def log_floats(o):
return 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(): class Planner():
def __init__(self, ctrl): def __init__(self, ctrl):
self.ctrl = ctrl self.ctrl = ctrl
self.log = ctrl.log.get('Planner') self.log = ctrl.log.get('Planner')
@@ -64,21 +36,23 @@ class Planner():
ctrl.state.add_listener(self._update) ctrl.state.add_listener(self._update)
self.reset(stop = False) self.reset(stop=False)
self._report_time() 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):
def is_running(self): return self.planner.is_running() return self.planner.is_running()
def position_change(self): self._position_dirty = True
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 if not force and not self._position_dirty: return
self._position_dirty = False self._position_dirty = False
self.planner.set_position(self.ctrl.state.get_position()) self.planner.set_position(self.ctrl.state.get_position())
def get_config(self, mdi, with_limits): def get_config(self, mdi, with_limits):
state = self.ctrl.state state = self.ctrl.state
config = self.ctrl.config config = self.ctrl.config
@@ -88,21 +62,22 @@ class Planner():
cfg = { cfg = {
# NOTE Must get current units not configured default units # NOTE Must get current units not configured default units
'default-units': 'METRIC' if state.get('metric') else 'IMPERIAL', '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-accel': state.get_axis_vector('am', 1000000),
'max-jerk': state.get_axis_vector('jm', 1000000), 'max-jerk': state.get_axis_vector('jm', 1000000),
'rapid-auto-off': config.get('rapid-auto-off') and is_pwm, 'rapid-auto-off': config.get('rapid-auto-off') and is_pwm,
'max-blend-error': deviation, 'max-blend-error': deviation,
'max-merge-error': deviation, 'max-merge-error': deviation,
'max-arc-error': deviation / 10, 'max-arc-error': deviation / 10,
'junction-accel': config.get('junction-accel'), 'junction-accel': config.get('junction-accel'),
} }
# We place an upper limit of 1000 km/min^3 on jerk for MDI movements # We place an upper limit of 1000 km/min^3 on jerk for MDI movements
if mdi: if mdi:
for axis in 'xyzabc': for axis in 'xyzabc':
if axis in cfg['max-jerk']: 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: if with_limits:
minLimit = state.get_soft_limit_vector('tn', -math.inf) minLimit = state.get_soft_limit_vector('tn', -math.inf)
@@ -134,13 +109,11 @@ class Planner():
return cfg return cfg
def _update(self, update): def _update(self, update):
if 'id' in update: if 'id' in update:
id = update['id'] id = update['id']
self.planner.set_active(id) # Release planner commands self.planner.set_active(id) # Release planner commands
self.cmdq.release(id) # Synchronize planner variables self.cmdq.release(id) # Synchronize planner variables
def _get_var_cb(self, name, units): def _get_var_cb(self, name, units):
value = 0 value = 0
@@ -149,37 +122,36 @@ class Planner():
value = self.ctrl.state.get(name[1:], 0) value = self.ctrl.state.get(name[1:], 0)
try: try:
float(value) float(value)
if units == 'IMPERIAL': value /= 25.4 # Assume metric if units == 'IMPERIAL': value /= 25.4 # Assume metric
except ValueError: value = 0 except ValueError:
value = 0
self.log.info('Get: %s=%s (units=%s)' % (name, value, units)) self.log.info('Get: %s=%s (units=%s)' % (name, value, units))
return value return value
def _log_cb(self, line): def _log_cb(self, line):
line = line.strip() line = line.strip()
m = reLogLine.match(line) m = reLogLine.match(line)
if not m: return if not m: return
level = m.group('level') level = m.group('level')
msg = m.group('msg') msg = m.group('msg')
filename = m.group('file') filename = m.group('file')
line = m.group('line') line = m.group('line')
column = m.group('column') column = m.group('column')
where = ':'.join(filter(None.__ne__, [filename, line, column])) where = ':'.join(filter(None.__ne__, [filename, line, column]))
if line is not None: line = int(line) if line is not None: line = int(line)
if column is not None: column = int(column) if column is not None: column = int(column)
if level == 'I': self.log.info (msg, where = where) if level == 'I': self.log.info(msg, where=where)
elif level == 'D': self.log.debug (msg, where = where) elif level == 'D': self.log.debug(msg, where=where)
elif level == 'W': self.log.warning (msg, where = where) elif level == 'W': self.log.warning(msg, where=where)
elif level == 'E': self.log.error (msg, where = where) elif level == 'E': self.log.error(msg, where=where)
else: self.log.error('Could not parse planner log line: ' + line) else: self.log.error('Could not parse planner log line: ' + line)
def _add_message(self, text): def _add_message(self, text):
self.ctrl.state.add_message(text) self.ctrl.state.add_message(text)
@@ -187,14 +159,12 @@ class Planner():
if 0 <= line: where = '%s:%d' % (self.where, line) if 0 <= line: where = '%s:%d' % (self.where, line)
else: where = self.where else: where = self.where
self.log.message(text, where = where) self.log.message(text, where=where)
def _enqueue_set_cmd(self, id, name, value): def _enqueue_set_cmd(self, id, name, value):
self.log.info('set(#%d, %s, %s)', id, name, value) self.log.info('set(#%d, %s, %s)', id, name, value)
self.cmdq.enqueue(id, self.ctrl.state.set, name, value) self.cmdq.enqueue(id, self.ctrl.state.set, name, value)
def _report_time(self): def _report_time(self):
state = self.ctrl.state.get('xx', '') state = self.ctrl.state.get('xx', '')
@@ -205,39 +175,35 @@ class Planner():
self.ctrl.state.set('plan_time', round(plan_time)) 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) self.ctrl.ioloop.call_later(1, self._report_time)
def _plan_time_restart(self): def _plan_time_restart(self):
self.plan_time = self.ctrl.state.get('plan_time', 0) self.plan_time = self.ctrl.state.get('plan_time', 0)
def _update_time(self, plan_time, move_time): def _update_time(self, plan_time, move_time):
self.current_plan_time = plan_time self.current_plan_time = plan_time
self.move_time = move_time self.move_time = move_time
self.move_start = time.time() self.move_start = time.time()
def _enqueue_line_time(self, block): def _enqueue_line_time(self, block):
if block.get('first', False) or block.get('seeking', False): return if block.get('first', False) or block.get('seeking', False): return
# Sum move times # 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, self.cmdq.enqueue(block['id'], self._update_time, self.plan_time,
move_time) move_time)
self.plan_time += move_time self.plan_time += move_time
def _enqueue_dwell_time(self, block): def _enqueue_dwell_time(self, block):
self.cmdq.enqueue(block['id'], self._update_time, self.plan_time, self.cmdq.enqueue(block['id'], self._update_time, self.plan_time,
block['seconds']) block['seconds'])
self.plan_time += block['seconds'] self.plan_time += block['seconds']
def __encode(self, block): def __encode(self, block):
type, id = block['type'], block['id'] type, id = block['type'], block['id']
@@ -266,7 +232,7 @@ class Planner():
if len(name) != 2 or name[1] not in 'xyzabc': if len(name) != 2 or name[1] not in 'xyzabc':
self._enqueue_set_cmd(id, name[1:], value) 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) return Cmd.set_sync('if', 1 / value if value else 0)
if name[0:1] == '_' and name[1:2] in 'xyzabc': if name[0:1] == '_' and name[1:2] in 'xyzabc':
@@ -281,7 +247,7 @@ class Planner():
if type == 'input': if type == 'input':
# TODO handle timeout # 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']) return Cmd.input(block['port'], block['mode'], block['timeout'])
if type == 'output': if type == 'output':
@@ -297,11 +263,10 @@ class Planner():
sw = self.ctrl.state.get_switch_id(block['switch']) sw = self.ctrl.state.get_switch_id(block['switch'])
return Cmd.seek(sw, block['active'], block['error']) 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) raise Exception('Unknown planner command "%s"' % type)
def _encode(self, block): def _encode(self, block):
cmd = self.__encode(block) cmd = self.__encode(block)
@@ -309,21 +274,18 @@ class Planner():
self.cmdq.enqueue(block['id'], None) self.cmdq.enqueue(block['id'], None)
return Cmd.set_sync('id', block['id']) + '\n' + cmd return Cmd.set_sync('id', block['id']) + '\n' + cmd
def reset_times(self): def reset_times(self):
self.move_start = 0 self.move_start = 0
self.move_time = 0 self.move_time = 0
self.plan_time = 0 self.plan_time = 0
self.current_plan_time = 0 self.current_plan_time = 0
def close(self): def close(self):
# Release planner callbacks # Release planner callbacks
if self.planner is not None: if self.planner is not None:
self.planner.set_resolver(None) self.planner.set_resolver(None)
self.planner.set_logger(None) self.planner.set_logger(None)
def reset(self, *args, **kwargs): def reset(self, *args, **kwargs):
stop = kwargs.get('stop', True) stop = kwargs.get('stop', True)
if stop: if stop:
@@ -341,15 +303,13 @@ class Planner():
if resetState: if resetState:
self.ctrl.state.reset() self.ctrl.state.reset()
def mdi(self, cmd, with_limits=True):
def mdi(self, cmd, with_limits = True):
self.where = '<mdi>' self.where = '<mdi>'
self.log.info('MDI:' + cmd) self.log.info('MDI:' + cmd)
self._sync_position() self._sync_position()
self.planner.load_string(cmd, self.get_config(True, with_limits)) self.planner.load_string(cmd, self.get_config(True, with_limits))
self.reset_times() self.reset_times()
def load(self, path): def load(self, path):
self.where = path self.where = path
path = self.ctrl.get_path('upload', path) path = self.ctrl.get_path('upload', path)
@@ -358,7 +318,6 @@ class Planner():
self.planner.load(path, self.get_config(False, True)) self.planner.load(path, self.get_config(False, True))
self.reset_times() self.reset_times()
def stop(self): def stop(self):
try: try:
self.planner.stop() self.planner.stop()
@@ -368,7 +327,6 @@ class Planner():
self.log.exception('Internal error: Planner stop') self.log.exception('Internal error: Planner stop')
self.reset() self.reset()
def restart(self): def restart(self):
try: try:
id = self.ctrl.state.get('id') id = self.ctrl.state.get('id')
@@ -385,7 +343,6 @@ class Planner():
self.log.exception('Internal error: Planner restart') self.log.exception('Internal error: Planner restart')
self.stop() self.stop()
def next(self): def next(self):
try: try:
while self.planner.has_more(): while self.planner.has_more():

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import os
import time
import json
import hashlib
import glob
import tempfile
import signal
from concurrent.futures import Future from concurrent.futures import Future
from tornado import gen, process, iostream from tornado import gen, process, iostream
import bbctrl import bbctrl
import glob
import hashlib
import json
import os
import signal
import tempfile
def hash_dump(o): def hash_dump(o):
s = json.dumps(o, separators = (',', ':'), sort_keys = True) s = json.dumps(o, separators=(',', ':'), sort_keys=True)
return s.encode('utf8') return s.encode('utf8')
@@ -59,10 +31,12 @@ def plan_hash(path, config):
def safe_remove(path): def safe_remove(path):
try: try:
os.unlink(path) os.unlink(path)
except: pass except:
pass
class Plan(object): class Plan(object):
def __init__(self, preplanner, ctrl, filename): def __init__(self, preplanner, ctrl, filename):
self.preplanner = preplanner self.preplanner = preplanner
@@ -81,29 +55,27 @@ class Plan(object):
self.hid = plan_hash(self.gcode, self.config) self.hid = plan_hash(self.gcode, self.config)
fbase = '%s.%s.' % (self.base, self.hid) fbase = '%s.%s.' % (self.base, self.hid)
self.files = [ self.files = [
fbase + 'json', fbase + 'json', fbase + 'positions.gz', fbase + 'speeds.gz'
fbase + 'positions.gz', ]
fbase + 'speeds.gz']
self.future = Future() self.future = Future()
ctrl.ioloop.add_callback(self._load) ctrl.ioloop.add_callback(self._load)
def terminate(self): def terminate(self):
if self.cancel: return if self.cancel: return
self.cancel = True self.cancel = True
if self.pid is not None: if self.pid is not None:
try: try:
os.kill(self.pid, signal.SIGKILL) os.kill(self.pid, signal.SIGKILL)
except: pass except:
pass
def delete(self): def delete(self):
files = glob.glob(self.base + '.*') 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') plans = glob.glob(self.base + '.*.json')
if len(plans) <= max: return if len(plans) <= max: return
@@ -116,20 +88,21 @@ class Plan(object):
safe_remove(path[:-4] + 'positions.gz') safe_remove(path[:-4] + 'positions.gz')
safe_remove(path[:-4] + 'speeds.gz') safe_remove(path[:-4] + 'speeds.gz')
def _exists(self): def _exists(self):
for path in self.files: for path in self.files:
if not os.path.exists(path): return False if not os.path.exists(path): return False
return True return True
def _read(self): def _read(self):
if self.cancel: return if self.cancel: return
try: try:
with open(self.files[0], 'r') as f: meta = json.load(f) with open(self.files[0], 'r') as f:
with open(self.files[1], 'rb') as f: positions = f.read() meta = json.load(f)
with open(self.files[2], 'rb') as f: speeds = f.read() 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 return meta, positions, speeds
@@ -141,26 +114,23 @@ class Plan(object):
if os.path.exists(path): if os.path.exists(path):
os.remove(path) os.remove(path)
@gen.coroutine @gen.coroutine
def _exec(self): def _exec(self):
self.clean() # Clean up old plans self.clean() # Clean up old plans
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
cmd = ( cmd = ('/usr/bin/env', 'python3', bbctrl.get_resource('plan.py'),
'/usr/bin/env', 'python3', os.path.abspath(self.gcode), json.dumps(self.state),
bbctrl.get_resource('plan.py'), json.dumps(self.config),
os.path.abspath(self.gcode), json.dumps(self.state), '--max-time=%s' % self.preplanner.max_plan_time,
json.dumps(self.config), '--max-loop=%s' % self.preplanner.max_loop_time)
'--max-time=%s' % self.preplanner.max_plan_time,
'--max-loop=%s' % self.preplanner.max_loop_time
)
self.preplanner.log.info('Running: %s', cmd) self.preplanner.log.info('Running: %s', cmd)
proc = process.Subprocess(cmd, stdout = process.Subprocess.STREAM, proc = process.Subprocess(cmd,
stderr = process.Subprocess.STREAM, stdout=process.Subprocess.STREAM,
cwd = tmpdir) stderr=process.Subprocess.STREAM,
cwd=tmpdir)
errs = '' errs = ''
self.pid = proc.proc.pid self.pid = proc.proc.pid
@@ -170,7 +140,8 @@ class Plan(object):
line = yield proc.stdout.read_until(b'\n') line = yield proc.stdout.read_until(b'\n')
self.progress = float(line.strip()) self.progress = float(line.strip())
if self.cancel: return if self.cancel: return
except iostream.StreamClosedError: pass except iostream.StreamClosedError:
pass
self.progress = 1 self.progress = 1
@@ -184,12 +155,11 @@ class Plan(object):
proc.stdout.close() proc.stdout.close()
if not self.cancel: 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 + '/positions.gz', self.files[1])
os.rename(tmpdir + '/speeds.gz', self.files[2]) os.rename(tmpdir + '/speeds.gz', self.files[2])
os.sync() os.sync()
@gen.coroutine @gen.coroutine
def _load(self): def _load(self):
try: try:
@@ -203,11 +173,13 @@ class Plan(object):
self.future.set_result(self._read()) self.future.set_result(self._read())
except: 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): 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.ctrl = ctrl
self.log = ctrl.log.get('Preplanner') self.log = ctrl.log.get('Preplanner')
@@ -220,31 +192,27 @@ class Preplanner(object):
self.started = Future() self.started = Future()
self.plans = {} self.plans = {}
def start(self): def start(self):
if not self.started.done(): if not self.started.done():
self.log.info('Preplanner started') self.log.info('Preplanner started')
self.started.set_result(True) self.started.set_result(True)
def invalidate(self, filename): def invalidate(self, filename):
if filename in self.plans: if filename in self.plans:
self.plans[filename].terminate() self.plans[filename].terminate()
del self.plans[filename] del self.plans[filename]
def invalidate_all(self): def invalidate_all(self):
for filename, plan in self.plans.items(): for filename, plan in self.plans.items():
plan.terminate() plan.terminate()
self.plans = {} self.plans = {}
def delete_all_plans(self): def delete_all_plans(self):
files = glob.glob(self.ctrl.get_plan('*')) files = glob.glob(self.ctrl.get_plan('*'))
for path in files: safe_remove(path) for path in files:
safe_remove(path)
self.invalidate_all() self.invalidate_all()
def delete_plans(self, filename): def delete_plans(self, filename):
if filename in self.plans: if filename in self.plans:
self.plans[filename].delete() self.plans[filename].delete()
@@ -265,6 +233,5 @@ class Preplanner(object):
data = yield plan.future data = yield plan.future
return data return data
def get_plan_progress(self, filename): def get_plan_progress(self, filename):
return self.plans[filename].progress if filename in self.plans else 0 return self.plans[filename].progress if filename in self.plans else 0

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import bbctrl
import bbctrl.Cmd as Cmd import bbctrl.Cmd as Cmd
# Must match regs in pwr firmware # Must match regs in pwr firmware
TEMP_REG = 0 TEMP_REG = 0
VIN_REG = 1 VIN_REG = 1
VOUT_REG = 2 VOUT_REG = 2
MOTOR_REG = 3 MOTOR_REG = 3
LOAD1_REG = 4 LOAD1_REG = 4
LOAD2_REG = 5 LOAD2_REG = 5
VDD_REG = 6 VDD_REG = 6
FLAGS_REG = 7 FLAGS_REG = 7
VERSION_REG = 8 VERSION_REG = 8
# Must be kept in sync with pwr firmware # Must be kept in sync with pwr firmware
UNDER_VOLTAGE_FLAG = 1 << 0 UNDER_VOLTAGE_FLAG = 1 << 0
OVER_VOLTAGE_FLAG = 1 << 1 OVER_VOLTAGE_FLAG = 1 << 1
OVER_CURRENT_FLAG = 1 << 2 OVER_CURRENT_FLAG = 1 << 2
SENSE_ERROR_FLAG = 1 << 3 SENSE_ERROR_FLAG = 1 << 3
SHUNT_OVERLOAD_FLAG = 1 << 4 SHUNT_OVERLOAD_FLAG = 1 << 4
MOTOR_OVERLOAD_FLAG = 1 << 5 MOTOR_OVERLOAD_FLAG = 1 << 5
LOAD1_SHUTDOWN_FLAG = 1 << 6 LOAD1_SHUTDOWN_FLAG = 1 << 6
LOAD2_SHUTDOWN_FLAG = 1 << 7 LOAD2_SHUTDOWN_FLAG = 1 << 7
MOTOR_UNDER_VOLTAGE_FLAG = 1 << 8 MOTOR_UNDER_VOLTAGE_FLAG = 1 << 8
MOTOR_VOLTAGE_SENSE_ERROR_FLAG = 1 << 9 MOTOR_VOLTAGE_SENSE_ERROR_FLAG = 1 << 9
MOTOR_CURRENT_SENSE_ERROR_FLAG = 1 << 10 MOTOR_CURRENT_SENSE_ERROR_FLAG = 1 << 10
LOAD1_SENSE_ERROR_FLAG = 1 << 11 LOAD1_SENSE_ERROR_FLAG = 1 << 11
LOAD2_SENSE_ERROR_FLAG = 1 << 12 LOAD2_SENSE_ERROR_FLAG = 1 << 12
VDD_CURRENT_SENSE_ERROR_FLAG = 1 << 13 VDD_CURRENT_SENSE_ERROR_FLAG = 1 << 13
POWER_SHUTDOWN_FLAG = 1 << 14 POWER_SHUTDOWN_FLAG = 1 << 14
SHUNT_ERROR_FLAG = 1 << 15 SHUNT_ERROR_FLAG = 1 << 15
reg_names = 'temp vin vout motor load1 load2 vdd pwr_flags pwr_version'.split() reg_names = 'temp vin vout motor load1 load2 vdd pwr_flags pwr_version'.split()
class Pwr(): class Pwr():
def __init__(self, ctrl): def __init__(self, ctrl):
self.ctrl = ctrl self.ctrl = ctrl
self.log = ctrl.log.get('Pwr') self.log = ctrl.log.get('Pwr')
@@ -72,7 +44,6 @@ class Pwr():
self._update_cb(False) self._update_cb(False)
def check_fault(self, var, status): def check_fault(self, var, status):
status = bool(status) status = bool(status)
@@ -82,7 +53,6 @@ class Pwr():
return False return False
def check_faults(self): def check_faults(self):
flags = self.regs[FLAGS_REG] flags = self.regs[FLAGS_REG]
@@ -141,19 +111,17 @@ class Pwr():
if self.check_fault('shunt_error', flags & SHUNT_ERROR_FLAG): if self.check_fault('shunt_error', flags & SHUNT_ERROR_FLAG):
self.log.warning('Shunt error') self.log.warning('Shunt error')
def _update_cb(self, now=True):
def _update_cb(self, now = True):
if now: self._update() if now: self._update()
self.ctrl.ioloop.call_later(1, self._update_cb) self.ctrl.ioloop.call_later(1, self._update_cb)
def _update(self): def _update(self):
update = {} update = {}
try: try:
for i in range(len(self.regs)): for i in range(len(self.regs)):
value = self.ctrl.i2c.read_word(self.i2c_addr + i) 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 if i == TEMP_REG: value -= 273
elif i == FLAGS_REG or i == VERSION_REG: pass elif i == FLAGS_REG or i == VERSION_REG: pass
@@ -169,7 +137,7 @@ class Pwr():
if i == FLAGS_REG: self.check_faults() if i == FLAGS_REG: self.check_faults()
except Exception as e: 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 self.failures += 1
msg = 'Pwr communication failed at reg %d: %s' % (i, e) msg = 'Pwr communication failed at reg %d: %s' % (i, e)
if self.failures != 5: self.log.info(msg) if self.failures != 5: self.log.info(msg)

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import traceback
import bbctrl
from tornado.web import HTTPError from tornado.web import HTTPError
import bbctrl
import tornado.web import tornado.web
import traceback
class RequestHandler(tornado.web.RequestHandler): class RequestHandler(tornado.web.RequestHandler):
def __init__(self, app, request, **kwargs): def __init__(self, app, request, **kwargs):
super().__init__(app, request, **kwargs) super().__init__(app, request, **kwargs)
self.app = app 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'):
def get_log(self, name = 'API'): return self.get_ctrl().log.get(name) 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) 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) return self.get_ctrl().get_upload(filename)
# Override exception logging # Override exception logging
def log_exception(self, typ, value, tb): def log_exception(self, typ, value, tb):
if (isinstance(value, HTTPError) and if (isinstance(value, HTTPError) and 400 <= value.status_code
400 <= value.status_code and value.status_code < 500): return and value.status_code < 500):
return
log = self.get_log() log = self.get_log()
log.set_level(bbctrl.log.DEBUG) log.set_level(bbctrl.log.DEBUG)

View File

@@ -1,15 +1,15 @@
import traceback from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
import copy import copy
import iw_parse
import json import json
import uuid
import os import os
import socket import socket
import iw_parse
import threading
import subprocess import subprocess
import threading
import time import time
from watchdog.observers import Observer import traceback
from watchdog.events import FileSystemEventHandler import uuid
def call_get_output(cmd): def call_get_output(cmd):

View File

@@ -1,14 +1,13 @@
from tornado import gen
from tornado.web import HTTPError
import bbctrl
import datetime
import os import os
import re import re
import tornado
import sockjs.tornado
import datetime
import subprocess
import socket import socket
from tornado.web import HTTPError import sockjs.tornado
from tornado import gen import subprocess
import tornado
import bbctrl
def call_get_output(cmd): def call_get_output(cmd):
@@ -20,6 +19,7 @@ def call_get_output(cmd):
class RebootHandler(bbctrl.APIHandler): class RebootHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
subprocess.Popen(['plymouth', 'show-splash']) subprocess.Popen(['plymouth', 'show-splash'])
subprocess.Popen(['plymouth', 'change-mode', '--shutdown']) subprocess.Popen(['plymouth', 'change-mode', '--shutdown'])
@@ -28,6 +28,7 @@ class RebootHandler(bbctrl.APIHandler):
class ShutdownHandler(bbctrl.APIHandler): class ShutdownHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
subprocess.Popen(['plymouth', 'show-splash']) subprocess.Popen(['plymouth', 'show-splash'])
subprocess.Popen(['plymouth', 'change-mode', '--shutdown']) subprocess.Popen(['plymouth', 'change-mode', '--shutdown'])
@@ -36,6 +37,7 @@ class ShutdownHandler(bbctrl.APIHandler):
class LogHandler(bbctrl.RequestHandler): class LogHandler(bbctrl.RequestHandler):
def get(self): def get(self):
with open(self.get_ctrl().log.get_path(), 'r') as f: with open(self.get_ctrl().log.get_path(), 'r') as f:
self.write(f.read()) self.write(f.read())
@@ -48,11 +50,13 @@ class LogHandler(bbctrl.RequestHandler):
class MessageAckHandler(bbctrl.APIHandler): class MessageAckHandler(bbctrl.APIHandler):
def put_ok(self, id): def put_ok(self, id):
self.get_ctrl().state.ack_message(int(id)) self.get_ctrl().state.ack_message(int(id))
class BugReportHandler(bbctrl.RequestHandler): class BugReportHandler(bbctrl.RequestHandler):
def get(self): def get(self):
import tarfile import tarfile
import io import io
@@ -91,13 +95,15 @@ class BugReportHandler(bbctrl.RequestHandler):
class HostnameHandler(bbctrl.APIHandler): class HostnameHandler(bbctrl.APIHandler):
def put(self): def put(self):
if self.get_ctrl().args.demo: if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot set hostname in demo mode') raise HTTPError(400, 'Cannot set hostname in demo mode')
if 'hostname' in self.json: if 'hostname' in self.json:
if subprocess.call(['/usr/local/bin/sethostname', if subprocess.call(
self.json['hostname'].strip()]) == 0: ['/usr/local/bin/sethostname',
self.json['hostname'].strip()]) == 0:
self.write_json('ok') self.write_json('ok')
return return
@@ -105,6 +111,7 @@ class HostnameHandler(bbctrl.APIHandler):
class NetworkHandler(bbctrl.APIHandler): class NetworkHandler(bbctrl.APIHandler):
def put(self): def put(self):
if self.get_ctrl().args.demo: if self.get_ctrl().args.demo:
raise HTTPError(400, 'Cannot configure WiFi in demo mode') raise HTTPError(400, 'Cannot configure WiFi in demo mode')
@@ -133,11 +140,13 @@ class NetworkHandler(bbctrl.APIHandler):
class ConfigLoadHandler(bbctrl.APIHandler): class ConfigLoadHandler(bbctrl.APIHandler):
def get(self): def get(self):
self.write_json(self.get_ctrl().config.load()) self.write_json(self.get_ctrl().config.load())
class ConfigDownloadHandler(bbctrl.APIHandler): class ConfigDownloadHandler(bbctrl.APIHandler):
def set_default_headers(self): def set_default_headers(self):
fmt = socket.gethostname() + '-%Y%m%d.json' fmt = socket.gethostname() + '-%Y%m%d.json'
filename = datetime.date.today().strftime(fmt) filename = datetime.date.today().strftime(fmt)
@@ -150,15 +159,21 @@ class ConfigDownloadHandler(bbctrl.APIHandler):
class ConfigSaveHandler(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): 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): class FirmwareUpdateHandler(bbctrl.APIHandler):
def prepare(self): pass
def prepare(self):
pass
def put_ok(self): def put_ok(self):
if not 'firmware' in self.request.files: if not 'firmware' in self.request.files:
@@ -176,11 +191,13 @@ class FirmwareUpdateHandler(bbctrl.APIHandler):
class UpgradeHandler(bbctrl.APIHandler): class UpgradeHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
subprocess.Popen(['/usr/local/bin/upgrade-bbctrl']) subprocess.Popen(['/usr/local/bin/upgrade-bbctrl'])
class PathHandler(bbctrl.APIHandler): class PathHandler(bbctrl.APIHandler):
@gen.coroutine @gen.coroutine
def get(self, filename, dataType, *args): def get(self, filename, dataType, *args):
if not os.path.exists(self.get_upload(filename)): if not os.path.exists(self.get_upload(filename)):
@@ -230,6 +247,7 @@ class PathHandler(bbctrl.APIHandler):
class HomeHandler(bbctrl.APIHandler): class HomeHandler(bbctrl.APIHandler):
def put_ok(self, axis, action, *args): def put_ok(self, axis, action, *args):
if axis is not None: if axis is not None:
axis = ord(axis[1:2].lower()) axis = ord(axis[1:2].lower())
@@ -247,62 +265,86 @@ class HomeHandler(bbctrl.APIHandler):
class StartHandler(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): 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): 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): 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): 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): 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): 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): 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): class PositionHandler(bbctrl.APIHandler):
def put_ok(self, axis): def put_ok(self, axis):
self.get_ctrl().mach.set_position(axis, float(self.json['position'])) self.get_ctrl().mach.set_position(axis, float(self.json['position']))
class OverrideFeedHandler(bbctrl.APIHandler): 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): 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): class ModbusReadHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
self.get_ctrl().mach.modbus_read(int(self.json['address'])) self.get_ctrl().mach.modbus_read(int(self.json['address']))
class ModbusWriteHandler(bbctrl.APIHandler): class ModbusWriteHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
self.get_ctrl().mach.modbus_write(int(self.json['address']), self.get_ctrl().mach.modbus_write(int(self.json['address']),
int(self.json['value'])) int(self.json['value']))
class JogHandler(bbctrl.APIHandler): class JogHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
# Handle possible out of order jog command processing # Handle possible out of order jog command processing
if 'ts' in self.json: if 'ts' in self.json:
@@ -323,12 +365,14 @@ class JogHandler(bbctrl.APIHandler):
displayRotatePattern = re.compile(r'display_rotate\s*=\s*(\d)') displayRotatePattern = re.compile(r'display_rotate\s*=\s*(\d)')
transformationMatrixPattern = re.compile( 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( matchIsTouchscreenPattern = re.compile(
r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(.*?EndSection)', re.DOTALL) r'(\n)(\s+)(MatchIsTouchscreen.*?\n)(.*?EndSection)', re.DOTALL)
class ScreenRotationHandler(bbctrl.APIHandler): class ScreenRotationHandler(bbctrl.APIHandler):
@gen.coroutine @gen.coroutine
def get(self): def get(self):
with open("/boot/config.txt", 'rt') as config: with open("/boot/config.txt", 'rt') as config:
@@ -336,7 +380,8 @@ class ScreenRotationHandler(bbctrl.APIHandler):
for line in lines: for line in lines:
if line.startswith('display_rotate'): if line.startswith('display_rotate'):
self.write_json({ self.write_json({
'rotated': int(displayRotatePattern.search(line).group(1)) != 0 'rotated':
int(displayRotatePattern.search(line).group(1)) != 0
}) })
return return
@@ -347,33 +392,36 @@ class ScreenRotationHandler(bbctrl.APIHandler):
def put_ok(self): def put_ok(self):
rotated = self.json['rotated'] rotated = self.json['rotated']
subprocess.Popen( subprocess.Popen([
['/usr/local/bin/edit-boot-config', 'display_rotate={}'.format(2 if rotated else 0)]) '/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 = config.read()
text = transformationMatrixPattern.sub(r'\1\2\3\5', text) text = transformationMatrixPattern.sub(r'\1\2\3\5', text)
if rotated: if rotated:
text = matchIsTouchscreenPattern.sub( text = matchIsTouchscreenPattern.sub(
r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4', text) r'\1\2\3\2Option "TransformationMatrix" "-1 0 1 0 -1 1 0 0 1"\1\4',
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf", 'wt') as config: text)
with open("/usr/share/X11/xorg.conf.d/40-libinput.conf",
'wt') as config:
config.write(text) config.write(text)
subprocess.run('reboot') subprocess.run('reboot')
class TimeHandler(bbctrl.APIHandler): class TimeHandler(bbctrl.APIHandler):
def get(self): def get(self):
timeinfo = call_get_output(['timedatectl']) timeinfo = call_get_output(['timedatectl'])
timezones = call_get_output( timezones = call_get_output(
['timedatectl', 'list-timezones', '--no-pager']) ['timedatectl', 'list-timezones', '--no-pager'])
self.get_log('TimeHandler').info( self.get_log('TimeHandler').info('Time stuff: {}, {}'.format(
'Time stuff: {}, {}'.format(timeinfo, timezones)) timeinfo, timezones))
self.write_json({ self.write_json({'timeinfo': timeinfo, 'timezones': timezones})
'timeinfo': timeinfo,
'timezones': timezones
})
def put_ok(self): def put_ok(self):
datetime = self.json['datetime'] datetime = self.json['datetime']
@@ -384,6 +432,7 @@ class TimeHandler(bbctrl.APIHandler):
# Base class for Web Socket connections # Base class for Web Socket connections
class ClientConnection(object): class ClientConnection(object):
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
self.count = 0 self.count = 0
@@ -393,7 +442,8 @@ class ClientConnection(object):
self.send({'heartbeat': self.count}) self.send({'heartbeat': self.count})
self.count += 1 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): def on_open(self, id=None):
self.ctrl = self.app.get_ctrl(id) self.ctrl = self.app.get_ctrl(id)
@@ -417,10 +467,11 @@ class ClientConnection(object):
# Used by CAMotics # Used by CAMotics
class WSConnection(ClientConnection, tornado.websocket.WebSocketHandler): class WSConnection(ClientConnection, tornado.websocket.WebSocketHandler):
def __init__(self, app, request, **kwargs): def __init__(self, app, request, **kwargs):
ClientConnection.__init__(self, app) ClientConnection.__init__(self, app)
tornado.websocket.WebSocketHandler.__init__( tornado.websocket.WebSocketHandler.__init__(self, app, request,
self, app, request, **kwargs) **kwargs)
def send(self, msg): def send(self, msg):
self.write_message(msg) self.write_message(msg)
@@ -431,6 +482,7 @@ class WSConnection(ClientConnection, tornado.websocket.WebSocketHandler):
# Used by Web frontend # Used by Web frontend
class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection): class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
def __init__(self, session): def __init__(self, session):
ClientConnection.__init__(self, session.server.app) ClientConnection.__init__(self, session.server.app)
sockjs.tornado.SockJSConnection.__init__(self, session) sockjs.tornado.SockJSConnection.__init__(self, session)
@@ -451,18 +503,20 @@ class SockJSConnection(ClientConnection, sockjs.tornado.SockJSConnection):
ip = info.ip ip = info.ip
if 'X-Real-IP' in info.headers: if 'X-Real-IP' in info.headers:
ip = info.headers['X-Real-IP'] ip = info.headers['X-Real-IP']
self.app.get_ctrl(id).log.get( self.app.get_ctrl(id).log.get('Web').info('Connection from %s' %
'Web').info('Connection from %s' % ip) ip)
super().on_open(id) super().on_open(id)
class StaticFileHandler(tornado.web.StaticFileHandler): class StaticFileHandler(tornado.web.StaticFileHandler):
def set_extra_headers(self, path): def set_extra_headers(self, path):
self.set_header('Cache-Control', self.set_header('Cache-Control',
'no-store, no-cache, must-revalidate, max-age=0') 'no-store, no-cache, must-revalidate, max-age=0')
class Web(tornado.web.Application): class Web(tornado.web.Application):
def __init__(self, args, ioloop): def __init__(self, args, ioloop):
self.args = args self.args = args
self.ioloop = ioloop self.ioloop = ioloop
@@ -518,9 +572,10 @@ class Web(tornado.web.Application):
(r'/api/video', bbctrl.VideoHandler), (r'/api/video', bbctrl.VideoHandler),
(r'/api/screen-rotation', ScreenRotationHandler), (r'/api/screen-rotation', ScreenRotationHandler),
(r'/api/time', TimeHandler), (r'/api/time', TimeHandler),
(r'/(.*)', StaticFileHandler, (r'/(.*)', StaticFileHandler, {
{'path': bbctrl.get_resource('http/'), 'path': bbctrl.get_resource('http/'),
'default_filename': 'index.html'}), 'default_filename': 'index.html'
}),
] ]
router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs') router = sockjs.tornado.SockJSRouter(SockJSConnection, '/sockjs')
@@ -532,8 +587,8 @@ class Web(tornado.web.Application):
self.listen(args.port, address=args.addr) self.listen(args.port, address=args.addr)
except Exception as e: except Exception as e:
raise Exception('Failed to bind %s:%d: %s' % ( raise Exception('Failed to bind %s:%d: %s' %
args.addr, args.port, e)) (args.addr, args.port, e))
print('Listening on http://%s:%d/' % (args.addr, args.port)) print('Listening on http://%s:%d/' % (args.addr, args.port))

View File

@@ -1,36 +1,35 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys
import signal
import tornado
import argparse import argparse
import datetime import datetime
import signal
import sys
import tornado
from pkg_resources import Requirement, resource_filename from pkg_resources import Requirement, resource_filename
from bbctrl.RequestHandler import RequestHandler
from bbctrl.APIHandler import APIHandler from bbctrl.APIHandler import APIHandler
from bbctrl.FileHandler import FileHandler from bbctrl.AVR import AVR
from bbctrl.Config import Config from bbctrl.Camera import Camera, VideoHandler
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.Comm import Comm from bbctrl.Comm import Comm
from bbctrl.CommandQueue import CommandQueue from bbctrl.CommandQueue import CommandQueue
from bbctrl.Camera import Camera, VideoHandler from bbctrl.Config import Config
from bbctrl.AVR import AVR from bbctrl.Ctrl import Ctrl
from bbctrl.FileHandler import FileHandler
from bbctrl.I2C import I2C
from bbctrl.IOLoop import IOLoop from bbctrl.IOLoop import IOLoop
from bbctrl.Jog import Jog
from bbctrl.Mach import Mach
from bbctrl.MonitorTemp import MonitorTemp 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.Cmd as Cmd
import bbctrl.v4l2 as v4l2
import bbctrl.Log as log import bbctrl.Log as log
import bbctrl.v4l2 as v4l2
ctrl = None ctrl = None
@@ -59,41 +58,61 @@ def parse_args():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Buildbotics Machine Controller') description='Buildbotics Machine Controller')
parser.add_argument('-p', '--port', default=80, parser.add_argument('-p', '--port', default=80, type=int, help='HTTP port')
type=int, help='HTTP port') parser.add_argument('-a',
parser.add_argument('-a', '--addr', metavar='IP', default='0.0.0.0', '--addr',
metavar='IP',
default='0.0.0.0',
help='HTTP address to bind') help='HTTP address to bind')
parser.add_argument('-s', '--serial', default='/dev/ttyAMA0', parser.add_argument('-s',
'--serial',
default='/dev/ttyAMA0',
help='Serial device') 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') help='Serial baud rate')
parser.add_argument('--i2c-port', default=1, type=int, parser.add_argument('--i2c-port', default=1, type=int, help='I2C port')
help='I2C port') parser.add_argument('--avr-addr',
parser.add_argument('--avr-addr', default=0x2b, type=int, default=0x2b,
type=int,
help='AVR I2C address') 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') help='Power AVR I2C address')
parser.add_argument('-v', '--verbose', action='store_true', parser.add_argument('-v',
'--verbose',
action='store_true',
help='Verbose output') help='Verbose output')
parser.add_argument('-l', '--log', metavar="FILE", parser.add_argument('-l', '--log', metavar="FILE", help='Set a log file')
help='Set a log file') parser.add_argument('--disable-camera',
parser.add_argument('--disable-camera', action='store_true', action='store_true',
help='Disable the camera') help='Disable the camera')
parser.add_argument('--width', default=640, type=int, parser.add_argument('--width', default=640, type=int, help='Camera width')
help='Camera width') parser.add_argument('--height',
parser.add_argument('--height', default=480, type=int, default=480,
type=int,
help='Camera height') help='Camera height')
parser.add_argument('--fps', default=15, type=int, parser.add_argument('--fps',
default=15,
type=int,
help='Camera frames per second') help='Camera frames per second')
parser.add_argument('--camera-clients', default=4, parser.add_argument('--camera-clients',
default=4,
help='Maximum simultaneous camera clients') help='Maximum simultaneous camera clients')
parser.add_argument('--demo', action='store_true', parser.add_argument('--demo', action='store_true', help='Enter demo mode')
help='Enter demo mode') parser.add_argument('--debug',
parser.add_argument('--debug', default=0, type=int, default=0,
type=int,
help='Enable debug mode and set frequency in seconds') 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') 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') help='Demo client timeout in seconds')
return parser.parse_args() return parser.parse_args()

View File

@@ -1,51 +1,22 @@
#!/usr/bin/env python3 #!/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 <http://www.gnu.org/licenses/>. #
# #
# 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 #
# <http://www.gnu.org/licenses/>. #
# #
# For information regarding this software email: #
# "Joseph Coffland" <joseph@buildbotics.com> #
# #
################################################################################
import sys
import argparse import argparse
import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error
import gzip
import json import json
import time import math
import math import math
import os import os
import re import re
import gzip
import struct import struct
import math import sys
import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error import time
reLogLine = re.compile(r'^(?P<level>[A-Z])[0-9 ]:'
reLogLine = re.compile( r'((?P<file>[^:]+):)?'
r'^(?P<level>[A-Z])[0-9 ]:' r'((?P<line>\d+):)?'
r'((?P<file>[^:]+):)?' r'((?P<column>\d+):)?'
r'((?P<line>\d+):)?' r'(?P<msg>.*)$')
r'((?P<column>\d+):)?'
r'(?P<msg>.*)$')
def compute_unit(a, b): def compute_unit(a, b):
@@ -77,6 +48,7 @@ def compute_move(start, unit, dist):
class Plan(object): class Plan(object):
def __init__(self, path, state, config): def __init__(self, path, state, config):
self.path = path self.path = path
self.state = state self.state = state
@@ -90,11 +62,14 @@ class Plan(object):
self.planner.load(self.path, config) self.planner.load(self.path, config)
self.messages = [] self.messages = []
self.levels = dict(I = 'info', D = 'debug', W = 'warning', E = 'error', self.levels = dict(I='info',
C = 'critical') D='debug',
W='warning',
E='error',
C='critical')
# Initialized axis states and bounds # Initialized axis states and bounds
self.bounds = dict(min = {}, max = {}) self.bounds = dict(min={}, max={})
for axis in 'xyz': for axis in 'xyz':
self.bounds['min'][axis] = math.inf self.bounds['min'][axis] = math.inf
self.bounds['max'][axis] = -math.inf self.bounds['max'][axis] = -math.inf
@@ -105,12 +80,10 @@ class Plan(object):
self.lastProgressTime = 0 self.lastProgressTime = 0
self.time = 0 self.time = 0
def add_to_bounds(self, axis, value): def add_to_bounds(self, axis, value):
if value < self.bounds['min'][axis]: self.bounds['min'][axis] = value if value < self.bounds['min'][axis]: self.bounds['min'][axis] = value
if self.bounds['max'][axis] < value: self.bounds['max'][axis] = value if self.bounds['max'][axis] < value: self.bounds['max'][axis] = value
def get_bounds(self): def get_bounds(self):
# Remove infinity from bounds # Remove infinity from bounds
for axis in 'xyz': for axis in 'xyz':
@@ -121,7 +94,6 @@ class Plan(object):
return self.bounds return self.bounds
def update_speed(self, s): def update_speed(self, s):
if self.currentSpeed == s: return False if self.currentSpeed == s: return False
self.currentSpeed = s self.currentSpeed = s
@@ -129,7 +101,6 @@ class Plan(object):
return True return True
def get_var_cb(self, name, units): def get_var_cb(self, name, units):
value = 0 value = 0
@@ -139,19 +110,20 @@ class Plan(object):
return value return value
def log_cb(self, level, msg, filename, line, column): def log_cb(self, level, msg, filename, line, column):
if level in self.levels: level = self.levels[level] if level in self.levels: level = self.levels[level]
# Ignore missing tool warning # Ignore missing tool warning
if (level == 'warning' and if (level == 'warning'
msg.startswith('Auto-creating missing tool')): and msg.startswith('Auto-creating missing tool')):
return return
self.messages.append( self.messages.append(
dict(level = level, msg = msg, filename = filename, line = line, dict(level=level,
column = column)) msg=msg,
filename=filename,
line=line,
column=column))
def _log_cb(self, line): def _log_cb(self, line):
line = line.strip() line = line.strip()
@@ -171,7 +143,6 @@ class Plan(object):
self.log_cb(level, msg, filename, line, column) self.log_cb(level, msg, filename, line, column)
def progress(self, x): def progress(self, x):
if time.time() - self.lastProgressTime < 1 and x != 1: return if time.time() - self.lastProgressTime < 1 and x != 1: return
self.lastProgressTime = time.time() self.lastProgressTime = time.time()
@@ -184,7 +155,6 @@ class Plan(object):
sys.stdout.write(p) sys.stdout.write(p)
sys.stdout.flush() sys.stdout.flush()
def _run(self): def _run(self):
start = time.clock() start = time.clock()
line = 0 line = 0
@@ -197,14 +167,14 @@ class Plan(object):
try: try:
while self.planner.has_more(): while self.planner.has_more():
cmd = self.planner.next() 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 # Cannot synchronize with actual machine so fake it
if self.planner.is_synchronizing(): self.planner.synchronize(0) if self.planner.is_synchronizing(): self.planner.synchronize(0)
if cmd['type'] == 'line': if cmd['type'] == 'line':
if not (cmd.get('first', False) or if not (cmd.get('first', False)
cmd.get('seeking', False)): or cmd.get('seeking', False)):
self.time += sum(cmd['times']) / 1000 self.time += sum(cmd['times']) / 1000
target = cmd['target'] target = cmd['target']
@@ -263,7 +233,6 @@ class Plan(object):
except Exception as e: except Exception as e:
self.log_cb('error', str(e), os.path.basename(self.path), line, 0) self.log_cb('error', str(e), os.path.basename(self.path), line, 0)
def run(self): def run(self):
lastS = 0 lastS = 0
speed = 0 speed = 0
@@ -292,27 +261,32 @@ class Plan(object):
f2.write(s) f2.write(s)
with open('meta.json', 'w') as f: with open('meta.json', 'w') as f:
meta = dict( meta = dict(time=self.time,
time = self.time, lines=self.lines,
lines = self.lines, maxSpeed=self.maxSpeed,
maxSpeed = self.maxSpeed, bounds=self.get_bounds(),
bounds = self.get_bounds(), messages=self.messages)
messages = self.messages)
json.dump(meta, f) json.dump(meta, f)
parser = argparse.ArgumentParser(description = 'Buildbotics GCode Planner') parser = argparse.ArgumentParser(description='Buildbotics GCode Planner')
parser.add_argument('gcode', help = 'The GCode file to plan') parser.add_argument('gcode', help='The GCode file to plan')
parser.add_argument('state', help = 'GCode state variables') parser.add_argument('state', help='GCode state variables')
parser.add_argument('config', help = 'Planner config') parser.add_argument('config', help='Planner config')
parser.add_argument('--max-time', default = 600, parser.add_argument('--max-time',
type = int, help = 'Maximum planning time in seconds') default=600,
parser.add_argument('--max-loop', default = 30, type=int,
type = int, help = 'Maximum time in loop in seconds') help='Maximum planning time in seconds')
parser.add_argument('--nice', default = 10, parser.add_argument('--max-loop',
type = int, help = 'Set "nice" process priority') 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() args = parser.parse_args()

View File

@@ -39,7 +39,6 @@
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" """
Python bindings for the v4l2 userspace api in Linux 2.6.34 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 ctypes
import platform import platform
# See ioctl.h of your architecture for appropriate constants here. # See ioctl.h of your architecture for appropriate constants here.
# This has been tested only on x86 and MIPS # This has been tested only on x86 and MIPS
_IOC_NRBITS = 8 _IOC_NRBITS = 8
@@ -62,24 +60,25 @@ _IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
if (platform.machine() == "mips"): if (platform.machine() == "mips"):
_IOC_NONE = 1 _IOC_NONE = 1
_IOC_READ = 2 _IOC_READ = 2
_IOC_WRITE = 4 _IOC_WRITE = 4
_IOC_SIZEBITS = 13 _IOC_SIZEBITS = 13
_IOC_DIRBITS = 3 _IOC_DIRBITS = 3
else: else:
_IOC_NONE = 0 _IOC_NONE = 0
_IOC_WRITE = 1 _IOC_WRITE = 1
_IOC_READ = 2 _IOC_READ = 2
_IOC_SIZEBITS = 14 _IOC_SIZEBITS = 14
_IOC_DIRBITS = 2 _IOC_DIRBITS = 2
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS _IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
def _IOC(dir_, type_, nr, size): def _IOC(dir_, type_, nr, size):
return ( return (ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value
ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value | | ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value
ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value | | ctypes.c_int32(nr << _IOC_NRSHIFT).value
ctypes.c_int32(nr << _IOC_NRSHIFT).value | | ctypes.c_int32(size << _IOC_SIZESHIFT).value)
ctypes.c_int32(size << _IOC_SIZESHIFT).value)
def _IOC_TYPECHECK(t): def _IOC_TYPECHECK(t):
return ctypes.sizeof(t) return ctypes.sizeof(t)
@@ -96,6 +95,7 @@ def _IOW(type_, nr, size):
def _IOR(type_, nr, size): def _IOR(type_, nr, size):
return _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK(size)) return _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK(size))
def _IOWR(type_, nr, size): def _IOWR(type_, nr, size):
return _IOC(_IOC_READ | _IOC_WRITE, type_, nr, _IOC_TYPECHECK(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 enum = ctypes.c_uint
c_int = ctypes.c_int c_int = ctypes.c_int
# #
# time # time
# #
class timeval(ctypes.Structure): class timeval(ctypes.Structure):
_fields_ = [ _fields_ = [
('secs', ctypes.c_long), ('secs', ctypes.c_long),
@@ -123,10 +123,8 @@ class timeval(ctypes.Structure):
# v4l2 # v4l2
# #
VIDEO_MAX_FRAME = 32 VIDEO_MAX_FRAME = 32
VID_TYPE_CAPTURE = 1 VID_TYPE_CAPTURE = 1
VID_TYPE_TUNER = 2 VID_TYPE_TUNER = 2
VID_TYPE_TELETEXT = 4 VID_TYPE_TELETEXT = 4
@@ -134,7 +132,7 @@ VID_TYPE_OVERLAY = 8
VID_TYPE_CHROMAKEY = 16 VID_TYPE_CHROMAKEY = 16
VID_TYPE_CLIPPING = 32 VID_TYPE_CLIPPING = 32
VID_TYPE_FRAMERAM = 64 VID_TYPE_FRAMERAM = 64
VID_TYPE_SCALES = 128 VID_TYPE_SCALES = 128
VID_TYPE_MONOCHROME = 256 VID_TYPE_MONOCHROME = 256
VID_TYPE_SUBCAPTURE = 512 VID_TYPE_SUBCAPTURE = 512
VID_TYPE_MPEG_DECODER = 1024 VID_TYPE_MPEG_DECODER = 1024
@@ -163,32 +161,23 @@ v4l2_field = enum
def V4L2_FIELD_HAS_TOP(field): def V4L2_FIELD_HAS_TOP(field):
return ( return (field == V4L2_FIELD_TOP or field == V4L2_FIELD_INTERLACED
field == V4L2_FIELD_TOP or or field == V4L2_FIELD_INTERLACED_TB
field == V4L2_FIELD_INTERLACED or or field == V4L2_FIELD_INTERLACED_BT or field == V4L2_FIELD_SEQ_TB
field == V4L2_FIELD_INTERLACED_TB or or field == V4L2_FIELD_SEQ_BT)
field == V4L2_FIELD_INTERLACED_BT or
field == V4L2_FIELD_SEQ_TB or
field == V4L2_FIELD_SEQ_BT)
def V4L2_FIELD_HAS_BOTTOM(field): def V4L2_FIELD_HAS_BOTTOM(field):
return ( return (field == V4L2_FIELD_BOTTOM or field == V4L2_FIELD_INTERLACED
field == V4L2_FIELD_BOTTOM or or field == V4L2_FIELD_INTERLACED_TB
field == V4L2_FIELD_INTERLACED or or field == V4L2_FIELD_INTERLACED_BT or field == V4L2_FIELD_SEQ_TB
field == V4L2_FIELD_INTERLACED_TB or or field == V4L2_FIELD_SEQ_BT)
field == V4L2_FIELD_INTERLACED_BT or
field == V4L2_FIELD_SEQ_TB or
field == V4L2_FIELD_SEQ_BT)
def V4L2_FIELD_HAS_BOTH(field): def V4L2_FIELD_HAS_BOTH(field):
return ( return (field == V4L2_FIELD_INTERLACED or field == V4L2_FIELD_INTERLACED_TB
field == V4L2_FIELD_INTERLACED or or field == V4L2_FIELD_INTERLACED_BT or field == V4L2_FIELD_SEQ_TB
field == V4L2_FIELD_INTERLACED_TB or or field == V4L2_FIELD_SEQ_BT)
field == V4L2_FIELD_INTERLACED_BT or
field == V4L2_FIELD_SEQ_TB or
field == V4L2_FIELD_SEQ_BT)
v4l2_buf_type = enum v4l2_buf_type = enum
@@ -206,7 +195,6 @@ v4l2_buf_type = enum
V4L2_BUF_TYPE_PRIVATE, V4L2_BUF_TYPE_PRIVATE,
) = list(range(1, 11)) + [0x80] ) = list(range(1, 11)) + [0x80]
v4l2_ctrl_type = enum v4l2_ctrl_type = enum
( (
V4L2_CTRL_TYPE_INTEGER, V4L2_CTRL_TYPE_INTEGER,
@@ -218,7 +206,6 @@ v4l2_ctrl_type = enum
V4L2_CTRL_TYPE_STRING, V4L2_CTRL_TYPE_STRING,
) = range(1, 8) ) = range(1, 8)
v4l2_tuner_type = enum v4l2_tuner_type = enum
( (
V4L2_TUNER_RADIO, V4L2_TUNER_RADIO,
@@ -226,7 +213,6 @@ v4l2_tuner_type = enum
V4L2_TUNER_DIGITAL_TV, V4L2_TUNER_DIGITAL_TV,
) = range(1, 4) ) = range(1, 4)
v4l2_memory = enum v4l2_memory = enum
( (
V4L2_MEMORY_MMAP, V4L2_MEMORY_MMAP,
@@ -234,7 +220,6 @@ v4l2_memory = enum
V4L2_MEMORY_OVERLAY, V4L2_MEMORY_OVERLAY,
) = range(1, 4) ) = range(1, 4)
v4l2_colorspace = enum v4l2_colorspace = enum
( (
V4L2_COLORSPACE_SMPTE170M, V4L2_COLORSPACE_SMPTE170M,
@@ -247,7 +232,6 @@ v4l2_colorspace = enum
V4L2_COLORSPACE_SRGB, V4L2_COLORSPACE_SRGB,
) = range(1, 9) ) = range(1, 9)
v4l2_priority = enum v4l2_priority = enum
( (
V4L2_PRIORITY_UNSET, V4L2_PRIORITY_UNSET,
@@ -278,6 +262,7 @@ class v4l2_fract(ctypes.Structure):
# Driver capabilities # Driver capabilities
# #
class v4l2_capability(ctypes.Structure): class v4l2_capability(ctypes.Structure):
_fields_ = [ _fields_ = [
('driver', ctypes.c_uint8 * 16), ('driver', ctypes.c_uint8 * 16),
@@ -315,11 +300,11 @@ V4L2_CAP_READWRITE = 0x01000000
V4L2_CAP_ASYNCIO = 0x02000000 V4L2_CAP_ASYNCIO = 0x02000000
V4L2_CAP_STREAMING = 0x04000000 V4L2_CAP_STREAMING = 0x04000000
# #
# Video image format # Video image format
# #
class v4l2_pix_format(ctypes.Structure): class v4l2_pix_format(ctypes.Structure):
_fields_ = [ _fields_ = [
('width', ctypes.c_uint32), ('width', ctypes.c_uint32),
@@ -332,6 +317,7 @@ class v4l2_pix_format(ctypes.Structure):
('priv', ctypes.c_uint32), ('priv', ctypes.c_uint32),
] ]
# RGB formats # RGB formats
V4L2_PIX_FMT_RGB332 = v4l2_fourcc('R', 'G', 'B', '1') V4L2_PIX_FMT_RGB332 = v4l2_fourcc('R', 'G', 'B', '1')
V4L2_PIX_FMT_RGB444 = v4l2_fourcc('R', '4', '4', '4') 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 # Grey formats
V4L2_PIX_FMT_GREY = v4l2_fourcc('G', 'R', 'E', 'Y') 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', ' ') V4L2_PIX_FMT_Y16 = v4l2_fourcc('Y', '1', '6', ' ')
# Palette formats # 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_OV518 = v4l2_fourcc('O', '5', '1', '8')
V4L2_PIX_FMT_STV0680 = v4l2_fourcc('S', '6', '8', '0') V4L2_PIX_FMT_STV0680 = v4l2_fourcc('S', '6', '8', '0')
# #
# Format enumeration # Format enumeration
# #
class v4l2_fmtdesc(ctypes.Structure): class v4l2_fmtdesc(ctypes.Structure):
_fields_ = [ _fields_ = [
('index', ctypes.c_uint32), ('index', ctypes.c_uint32),
@@ -432,10 +418,10 @@ class v4l2_fmtdesc(ctypes.Structure):
('reserved', ctypes.c_uint32 * 4), ('reserved', ctypes.c_uint32 * 4),
] ]
V4L2_FMT_FLAG_COMPRESSED = 0x0001 V4L2_FMT_FLAG_COMPRESSED = 0x0001
V4L2_FMT_FLAG_EMULATED = 0x0002 V4L2_FMT_FLAG_EMULATED = 0x0002
# #
# Experimental frame size and frame rate enumeration # Experimental frame size and frame rate enumeration
# #
@@ -467,21 +453,18 @@ class v4l2_frmsize_stepwise(ctypes.Structure):
class v4l2_frmsizeenum(ctypes.Structure): class v4l2_frmsizeenum(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('discrete', v4l2_frmsize_discrete), ('discrete', v4l2_frmsize_discrete),
('stepwise', v4l2_frmsize_stepwise), ('stepwise', v4l2_frmsize_stepwise),
] ]
_fields_ = [ _fields_ = [('index', ctypes.c_uint32), ('pixel_format', ctypes.c_uint32),
('index', ctypes.c_uint32), ('type', ctypes.c_uint32), ('_u', _u),
('pixel_format', ctypes.c_uint32), ('reserved', ctypes.c_uint32 * 2)]
('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 v4l2_frmivalenum(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('discrete', v4l2_fract), ('discrete', v4l2_fract),
@@ -521,13 +505,14 @@ class v4l2_frmivalenum(ctypes.Structure):
('reserved', ctypes.c_uint32 * 2), ('reserved', ctypes.c_uint32 * 2),
] ]
_anonymous_ = ('_u',) _anonymous_ = ('_u', )
# #
# Timecode # Timecode
# #
class v4l2_timecode(ctypes.Structure): class v4l2_timecode(ctypes.Structure):
_fields_ = [ _fields_ = [
('type', ctypes.c_uint32), ('type', ctypes.c_uint32),
@@ -571,11 +556,11 @@ V4L2_JPEG_MARKER_DRI = 1 << 5
V4L2_JPEG_MARKER_COM = 1 << 6 V4L2_JPEG_MARKER_COM = 1 << 6
V4L2_JPEG_MARKER_APP = 1 << 7 V4L2_JPEG_MARKER_APP = 1 << 7
# #
# Memory-mapping buffers # Memory-mapping buffers
# #
class v4l2_requestbuffers(ctypes.Structure): class v4l2_requestbuffers(ctypes.Structure):
_fields_ = [ _fields_ = [
('count', ctypes.c_uint32), ('count', ctypes.c_uint32),
@@ -586,6 +571,7 @@ class v4l2_requestbuffers(ctypes.Structure):
class v4l2_plane(ctypes.Structure): class v4l2_plane(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('mem_offset', ctypes.c_uint32), ('mem_offset', ctypes.c_uint32),
@@ -600,13 +586,12 @@ class v4l2_plane(ctypes.Structure):
('reserved', ctypes.c_uint32 * 11), ('reserved', ctypes.c_uint32 * 11),
] ]
class v4l2_buffer(ctypes.Structure): class v4l2_buffer(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [('offset', ctypes.c_uint32), ('userptr', ctypes.c_ulong),
('offset', ctypes.c_uint32), ('planes', ctypes.POINTER(v4l2_plane))]
('userptr', ctypes.c_ulong),
('planes', ctypes.POINTER(v4l2_plane))
]
_fields_ = [ _fields_ = [
('index', ctypes.c_uint32), ('index', ctypes.c_uint32),
@@ -634,11 +619,11 @@ V4L2_BUF_FLAG_BFRAME = 0x0020
V4L2_BUF_FLAG_TIMECODE = 0x0100 V4L2_BUF_FLAG_TIMECODE = 0x0100
V4L2_BUF_FLAG_INPUT = 0x0200 V4L2_BUF_FLAG_INPUT = 0x0200
# #
# Overlay preview # Overlay preview
# #
class v4l2_framebuffer(ctypes.Structure): class v4l2_framebuffer(ctypes.Structure):
_fields_ = [ _fields_ = [
('capability', ctypes.c_uint32), ('capability', ctypes.c_uint32),
@@ -647,8 +632,9 @@ class v4l2_framebuffer(ctypes.Structure):
('fmt', v4l2_pix_format), ('fmt', v4l2_pix_format),
] ]
V4L2_FBUF_CAP_EXTERNOVERLAY = 0x0001 V4L2_FBUF_CAP_EXTERNOVERLAY = 0x0001
V4L2_FBUF_CAP_CHROMAKEY = 0x0002 V4L2_FBUF_CAP_CHROMAKEY = 0x0002
V4L2_FBUF_CAP_LIST_CLIPPING = 0x0004 V4L2_FBUF_CAP_LIST_CLIPPING = 0x0004
V4L2_FBUF_CAP_BITMAP_CLIPPING = 0x0008 V4L2_FBUF_CAP_BITMAP_CLIPPING = 0x0008
V4L2_FBUF_CAP_LOCAL_ALPHA = 0x0010 V4L2_FBUF_CAP_LOCAL_ALPHA = 0x0010
@@ -667,6 +653,8 @@ V4L2_FBUF_FLAG_SRC_CHROMAKEY = 0x0040
class v4l2_clip(ctypes.Structure): class v4l2_clip(ctypes.Structure):
pass pass
v4l2_clip._fields_ = [ v4l2_clip._fields_ = [
('c', v4l2_rect), ('c', v4l2_rect),
('next', ctypes.POINTER(v4l2_clip)), ('next', ctypes.POINTER(v4l2_clip)),
@@ -689,6 +677,7 @@ class v4l2_window(ctypes.Structure):
# Capture parameters # Capture parameters
# #
class v4l2_captureparm(ctypes.Structure): class v4l2_captureparm(ctypes.Structure):
_fields_ = [ _fields_ = [
('capability', ctypes.c_uint32), ('capability', ctypes.c_uint32),
@@ -719,6 +708,7 @@ class v4l2_outputparm(ctypes.Structure):
# Input image cropping # Input image cropping
# #
class v4l2_cropcap(ctypes.Structure): class v4l2_cropcap(ctypes.Structure):
_fields_ = [ _fields_ = [
('type', v4l2_buf_type), ('type', v4l2_buf_type),
@@ -741,7 +731,6 @@ class v4l2_crop(ctypes.Structure):
v4l2_std_id = ctypes.c_uint64 v4l2_std_id = ctypes.c_uint64
V4L2_STD_PAL_B = 0x00000001 V4L2_STD_PAL_B = 0x00000001
V4L2_STD_PAL_B1 = 0x00000002 V4L2_STD_PAL_B1 = 0x00000002
V4L2_STD_PAL_G = 0x00000004 V4L2_STD_PAL_G = 0x00000004
@@ -773,26 +762,31 @@ V4L2_STD_SECAM_LC = 0x00800000
V4L2_STD_ATSC_8_VSB = 0x01000000 V4L2_STD_ATSC_8_VSB = 0x01000000
V4L2_STD_ATSC_16_VSB = 0x02000000 V4L2_STD_ATSC_16_VSB = 0x02000000
# some common needed stuff # some common needed stuff
V4L2_STD_PAL_BG = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_PAL_G) 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_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_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_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_525_60 = (V4L2_STD_PAL_M | V4L2_STD_PAL_60 | V4L2_STD_NTSC
V4L2_STD_625_50 = (V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_SECAM) | 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_ATSC = (V4L2_STD_ATSC_8_VSB | V4L2_STD_ATSC_16_VSB)
V4L2_STD_UNKNOWN = 0 V4L2_STD_UNKNOWN = 0
V4L2_STD_ALL = (V4L2_STD_525_60 | V4L2_STD_625_50) V4L2_STD_ALL = (V4L2_STD_525_60 | V4L2_STD_625_50)
# some merged standards # 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_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) V4L2_STD_DK = (V4L2_STD_PAL_DK | V4L2_STD_SECAM_DK)
@@ -811,17 +805,16 @@ class v4l2_standard(ctypes.Structure):
# Video timings dv preset # Video timings dv preset
# #
class v4l2_dv_preset(ctypes.Structure): class v4l2_dv_preset(ctypes.Structure):
_fields_ = [ _fields_ = [('preset', ctypes.c_uint32), ('reserved', ctypes.c_uint32 * 4)]
('preset', ctypes.c_uint32),
('reserved', ctypes.c_uint32 * 4)
]
# #
# DV preset enumeration # DV preset enumeration
# #
class v4l2_dv_enum_preset(ctypes.Structure): class v4l2_dv_enum_preset(ctypes.Structure):
_fields_ = [ _fields_ = [
('index', ctypes.c_uint32), ('index', ctypes.c_uint32),
@@ -832,6 +825,7 @@ class v4l2_dv_enum_preset(ctypes.Structure):
('reserved', ctypes.c_uint32 * 4), ('reserved', ctypes.c_uint32 * 4),
] ]
# #
# DV preset values # DV preset values
# #
@@ -846,21 +840,21 @@ V4L2_DV_720P50 = 6
V4L2_DV_720P59_94 = 7 V4L2_DV_720P59_94 = 7
V4L2_DV_720P60 = 8 V4L2_DV_720P60 = 8
V4L2_DV_1080I29_97 = 9 V4L2_DV_1080I29_97 = 9
V4L2_DV_1080I30 = 10 V4L2_DV_1080I30 = 10
V4L2_DV_1080I25 = 11 V4L2_DV_1080I25 = 11
V4L2_DV_1080I50 = 12 V4L2_DV_1080I50 = 12
V4L2_DV_1080I60 = 13 V4L2_DV_1080I60 = 13
V4L2_DV_1080P24 = 14 V4L2_DV_1080P24 = 14
V4L2_DV_1080P25 = 15 V4L2_DV_1080P25 = 15
V4L2_DV_1080P30 = 16 V4L2_DV_1080P30 = 16
V4L2_DV_1080P50 = 17 V4L2_DV_1080P50 = 17
V4L2_DV_1080P60 = 18 V4L2_DV_1080P60 = 18
# #
# DV BT timings # DV BT timings
# #
class v4l2_bt_timings(ctypes.Structure): class v4l2_bt_timings(ctypes.Structure):
_fields_ = [ _fields_ = [
('width', ctypes.c_uint32), ('width', ctypes.c_uint32),
@@ -882,6 +876,7 @@ class v4l2_bt_timings(ctypes.Structure):
_pack_ = True _pack_ = True
# Interlaced or progressive format # Interlaced or progressive format
V4L2_DV_PROGRESSIVE = 0 V4L2_DV_PROGRESSIVE = 0
V4L2_DV_INTERLACED = 1 V4L2_DV_INTERLACED = 1
@@ -892,6 +887,7 @@ V4L2_DV_HSYNC_POS_POL = 0x00000002
class v4l2_dv_timings(ctypes.Structure): class v4l2_dv_timings(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('bt', v4l2_bt_timings), ('bt', v4l2_bt_timings),
@@ -903,18 +899,18 @@ class v4l2_dv_timings(ctypes.Structure):
('_u', _u), ('_u', _u),
] ]
_anonymous_ = ('_u',) _anonymous_ = ('_u', )
_pack_ = True _pack_ = True
# Values for the type field # Values for the type field
V4L2_DV_BT_656_1120 = 0 V4L2_DV_BT_656_1120 = 0
# #
# Video inputs # Video inputs
# #
class v4l2_input(ctypes.Structure): class v4l2_input(ctypes.Structure):
_fields_ = [ _fields_ = [
('index', ctypes.c_uint32), ('index', ctypes.c_uint32),
@@ -957,6 +953,7 @@ V4L2_IN_CAP_STD = 0x00000004
# Video outputs # Video outputs
# #
class v4l2_output(ctypes.Structure): class v4l2_output(ctypes.Structure):
_fields_ = [ _fields_ = [
('index', ctypes.c_uint32), ('index', ctypes.c_uint32),
@@ -970,7 +967,7 @@ class v4l2_output(ctypes.Structure):
V4L2_OUTPUT_TYPE_MODULATOR = 1 V4L2_OUTPUT_TYPE_MODULATOR = 1
V4L2_OUTPUT_TYPE_ANALOG = 2 V4L2_OUTPUT_TYPE_ANALOG = 2
V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY = 3 V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY = 3
V4L2_OUT_CAP_PRESETS = 0x00000001 V4L2_OUT_CAP_PRESETS = 0x00000001
@@ -981,6 +978,7 @@ V4L2_OUT_CAP_STD = 0x00000004
# Controls # Controls
# #
class v4l2_control(ctypes.Structure): class v4l2_control(ctypes.Structure):
_fields_ = [ _fields_ = [
('id', ctypes.c_uint32), ('id', ctypes.c_uint32),
@@ -989,6 +987,7 @@ class v4l2_control(ctypes.Structure):
class v4l2_ext_control(ctypes.Structure): class v4l2_ext_control(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('value', ctypes.c_int32), ('value', ctypes.c_int32),
@@ -996,13 +995,10 @@ class v4l2_ext_control(ctypes.Structure):
('reserved', ctypes.c_void_p), ('reserved', ctypes.c_void_p),
] ]
_fields_ = [ _fields_ = [('id', ctypes.c_uint32), ('reserved2', ctypes.c_uint32 * 2),
('id', ctypes.c_uint32), ('_u', _u)]
('reserved2', ctypes.c_uint32 * 2),
('_u', _u)
]
_anonymous_ = ('_u',) _anonymous_ = ('_u', )
_pack_ = True _pack_ = True
@@ -1016,25 +1012,26 @@ class v4l2_ext_controls(ctypes.Structure):
] ]
V4L2_CTRL_CLASS_USER = 0x00980000 # Old-style 'user' controls V4L2_CTRL_CLASS_USER = 0x00980000 # Old-style 'user' controls
V4L2_CTRL_CLASS_MPEG = 0x00990000 # MPEG-compression controls V4L2_CTRL_CLASS_MPEG = 0x00990000 # MPEG-compression controls
V4L2_CTRL_CLASS_CAMERA = 0x009a0000 # Camera class controls V4L2_CTRL_CLASS_CAMERA = 0x009a0000 # Camera class controls
V4L2_CTRL_CLASS_FM_TX = 0x009b0000 # FM Modulator controls V4L2_CTRL_CLASS_FM_TX = 0x009b0000 # FM Modulator controls
V4L2_CTRL_CLASS_FLASH = 0x009c0000 # Camera flash controls V4L2_CTRL_CLASS_FLASH = 0x009c0000 # Camera flash controls
V4L2_CTRL_CLASS_JPEG = 0x009d0000 # JPEG-compression controls V4L2_CTRL_CLASS_JPEG = 0x009d0000 # JPEG-compression controls
V4L2_CTRL_CLASS_IMAGE_SOURCE = 0x009e0000 # Image source controls V4L2_CTRL_CLASS_IMAGE_SOURCE = 0x009e0000 # Image source controls
V4L2_CTRL_CLASS_IMAGE_PROC = 0x009f0000 # Image processing controls V4L2_CTRL_CLASS_IMAGE_PROC = 0x009f0000 # Image processing controls
V4L2_CTRL_CLASS_DV = 0x00a00000 # Digital Video controls V4L2_CTRL_CLASS_DV = 0x00a00000 # Digital Video controls
V4L2_CTRL_CLASS_FM_RX = 0x00a10000 # FM Receiver controls V4L2_CTRL_CLASS_FM_RX = 0x00a10000 # FM Receiver controls
V4L2_CTRL_CLASS_RF_TUNER = 0x00a20000 # RF tuner controls V4L2_CTRL_CLASS_RF_TUNER = 0x00a20000 # RF tuner controls
V4L2_CTRL_CLASS_DETECT = 0x00a30000 # Detection controls V4L2_CTRL_CLASS_DETECT = 0x00a30000 # Detection controls
def V4L2_CTRL_ID_MASK(): def V4L2_CTRL_ID_MASK():
return 0x0fffffff return 0x0fffffff
def V4L2_CTRL_ID2CLASS(id_): def V4L2_CTRL_ID2CLASS(id_):
return id_ & 0x0fff0000 # unsigned long return id_ & 0x0fff0000 # unsigned long
def V4L2_CTRL_DRIVER_PRIV(id_): 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_TREBLE = V4L2_CID_BASE + 8
V4L2_CID_AUDIO_MUTE = V4L2_CID_BASE + 9 V4L2_CID_AUDIO_MUTE = V4L2_CID_BASE + 9
V4L2_CID_AUDIO_LOUDNESS = V4L2_CID_BASE + 10 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_AUTO_WHITE_BALANCE = V4L2_CID_BASE + 12
V4L2_CID_DO_WHITE_BALANCE = V4L2_CID_BASE + 13 V4L2_CID_DO_WHITE_BALANCE = V4L2_CID_BASE + 13
V4L2_CID_RED_BALANCE = V4L2_CID_BASE + 14 V4L2_CID_RED_BALANCE = V4L2_CID_BASE + 14
V4L2_CID_BLUE_BALANCE = V4L2_CID_BASE + 15 V4L2_CID_BLUE_BALANCE = V4L2_CID_BASE + 15
V4L2_CID_GAMMA = V4L2_CID_BASE + 16 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_EXPOSURE = V4L2_CID_BASE + 17
V4L2_CID_AUTOGAIN = V4L2_CID_BASE + 18 V4L2_CID_AUTOGAIN = V4L2_CID_BASE + 18
V4L2_CID_GAIN = V4L2_CID_BASE + 19 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_MUTE = V4L2_CID_MPEG_BASE + 109
V4L2_CID_MPEG_AUDIO_AAC_BITRATE = V4L2_CID_MPEG_BASE + 110 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 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_POWER_LEVEL = V4L2_CID_FM_TX_CLASS_BASE + 113
V4L2_CID_TUNE_ANTENNA_CAPACITOR = V4L2_CID_FM_TX_CLASS_BASE + 114 V4L2_CID_TUNE_ANTENNA_CAPACITOR = V4L2_CID_FM_TX_CLASS_BASE + 114
# JPEG-class control IDs # JPEG-class control IDs
V4L2_CID_JPEG_CLASS_BASE = V4L2_CTRL_CLASS_JPEG | 0x900 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_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_444,
V4L2_JPEG_CHROMA_SUBSAMPLING_422, 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_RESTART_INTERVAL = V4L2_CID_JPEG_CLASS_BASE + 2
V4L2_CID_JPEG_COMPRESSION_QUALITY = V4L2_CID_JPEG_CLASS_BASE + 3 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_APP0 = 1 << 0
V4L2_JPEG_ACTIVE_MARKER_APP1 = 1 << 1 V4L2_JPEG_ACTIVE_MARKER_APP1 = 1 << 1
V4L2_JPEG_ACTIVE_MARKER_COM = 1 << 16 V4L2_JPEG_ACTIVE_MARKER_COM = 1 << 16
V4L2_JPEG_ACTIVE_MARKER_DQT = 1 << 17 V4L2_JPEG_ACTIVE_MARKER_DQT = 1 << 17
V4L2_JPEG_ACTIVE_MARKER_DHT = 1 << 18 V4L2_JPEG_ACTIVE_MARKER_DHT = 1 << 18
# #
# Tuning # Tuning
# #
class v4l2_tuner(ctypes.Structure): class v4l2_tuner(ctypes.Structure):
_fields_ = [ _fields_ = [
('index', ctypes.c_uint32), ('index', ctypes.c_uint32),
@@ -1579,6 +1575,7 @@ class v4l2_hw_freq_seek(ctypes.Structure):
# RDS # RDS
# #
class v4l2_rds_data(ctypes.Structure): class v4l2_rds_data(ctypes.Structure):
_fields_ = [ _fields_ = [
('lsb', ctypes.c_char), ('lsb', ctypes.c_char),
@@ -1589,7 +1586,7 @@ class v4l2_rds_data(ctypes.Structure):
_pack_ = True _pack_ = True
V4L2_RDS_BLOCK_MSK = 0x7 V4L2_RDS_BLOCK_MSK = 0x7
V4L2_RDS_BLOCK_A = 0 V4L2_RDS_BLOCK_A = 0
V4L2_RDS_BLOCK_B = 1 V4L2_RDS_BLOCK_B = 1
V4L2_RDS_BLOCK_C = 2 V4L2_RDS_BLOCK_C = 2
@@ -1600,11 +1597,11 @@ V4L2_RDS_BLOCK_INVALID = 7
V4L2_RDS_BLOCK_CORRECTED = 0x40 V4L2_RDS_BLOCK_CORRECTED = 0x40
V4L2_RDS_BLOCK_ERROR = 0x80 V4L2_RDS_BLOCK_ERROR = 0x80
# #
# Audio # Audio
# #
class v4l2_audio(ctypes.Structure): class v4l2_audio(ctypes.Structure):
_fields_ = [ _fields_ = [
('index', ctypes.c_uint32), ('index', ctypes.c_uint32),
@@ -1672,7 +1669,9 @@ V4L2_ENC_CMD_STOP_AT_GOP_END = 1 << 0
class v4l2_encoder_cmd(ctypes.Structure): class v4l2_encoder_cmd(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
class _s(ctypes.Structure): class _s(ctypes.Structure):
_fields_ = [ _fields_ = [
('data', ctypes.c_uint32 * 8), ('data', ctypes.c_uint32 * 8),
@@ -1688,13 +1687,14 @@ class v4l2_encoder_cmd(ctypes.Structure):
('_u', _u), ('_u', _u),
] ]
_anonymous_ = ('_u',) _anonymous_ = ('_u', )
# #
# Data services (VBI) # Data services (VBI)
# #
class v4l2_vbi_format(ctypes.Structure): class v4l2_vbi_format(ctypes.Structure):
_fields_ = [ _fields_ = [
('sampling_rate', ctypes.c_uint32), ('sampling_rate', ctypes.c_uint32),
@@ -1726,8 +1726,8 @@ V4L2_SLICED_VPS = 0x0400
V4L2_SLICED_CAPTION_525 = 0x1000 V4L2_SLICED_CAPTION_525 = 0x1000
V4L2_SLICED_WSS_625 = 0x4000 V4L2_SLICED_WSS_625 = 0x4000
V4L2_SLICED_VBI_525 = V4L2_SLICED_CAPTION_525 V4L2_SLICED_VBI_525 = V4L2_SLICED_CAPTION_525
V4L2_SLICED_VBI_625 = ( V4L2_SLICED_VBI_625 = (V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS
V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS | V4L2_SLICED_WSS_625) | V4L2_SLICED_WSS_625)
class v4l2_sliced_vbi_cap(ctypes.Structure): 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 # Sliced VBI data inserted into MPEG Streams
# #
V4L2_MPEG_VBI_IVTV_TELETEXT_B = 1 V4L2_MPEG_VBI_IVTV_TELETEXT_B = 1
V4L2_MPEG_VBI_IVTV_CAPTION_525 = 4 V4L2_MPEG_VBI_IVTV_CAPTION_525 = 4
V4L2_MPEG_VBI_IVTV_WSS_625 = 5 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): class v4l2_mpeg_vbi_itv0(ctypes.Structure):
_fields_ = [ _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), ('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 v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('itv0', v4l2_mpeg_vbi_itv0), ('itv0', v4l2_mpeg_vbi_itv0),
('ITV0', v4l2_mpeg_vbi_ITV0), ('ITV0', v4l2_mpeg_vbi_ITV0),
] ]
_fields_ = [ _fields_ = [('magic', ctypes.c_char * 4), ('_u', _u)]
('magic', ctypes.c_char * 4),
('_u', _u)
]
_anonymous_ = ('_u',) _anonymous_ = ('_u', )
_pack_ = True _pack_ = True
@@ -1810,7 +1807,9 @@ class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure):
# Aggregate structures # Aggregate structures
# #
class v4l2_format(ctypes.Structure): class v4l2_format(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('pix', v4l2_pix_format), ('pix', v4l2_pix_format),
@@ -1827,6 +1826,7 @@ class v4l2_format(ctypes.Structure):
class v4l2_streamparm(ctypes.Structure): class v4l2_streamparm(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('capture', v4l2_captureparm), ('capture', v4l2_captureparm),
@@ -1834,10 +1834,7 @@ class v4l2_streamparm(ctypes.Structure):
('raw_data', ctypes.c_char * 200), ('raw_data', ctypes.c_char * 200),
] ]
_fields_ = [ _fields_ = [('type', v4l2_buf_type), ('parm', _u)]
('type', v4l2_buf_type),
('parm', _u)
]
# #
@@ -1851,6 +1848,7 @@ V4L2_CHIP_MATCH_AC97 = 3
class v4l2_dbg_match(ctypes.Structure): class v4l2_dbg_match(ctypes.Structure):
class _u(ctypes.Union): class _u(ctypes.Union):
_fields_ = [ _fields_ = [
('addr', ctypes.c_uint32), ('addr', ctypes.c_uint32),
@@ -1862,7 +1860,7 @@ class v4l2_dbg_match(ctypes.Structure):
('_u', _u), ('_u', _u),
] ]
_anonymous_ = ('_u',) _anonymous_ = ('_u', )
_pack_ = True _pack_ = True
@@ -1897,7 +1895,7 @@ VIDIOC_ENUM_FMT = _IOWR('V', 2, v4l2_fmtdesc)
VIDIOC_G_FMT = _IOWR('V', 4, v4l2_format) VIDIOC_G_FMT = _IOWR('V', 4, v4l2_format)
VIDIOC_S_FMT = _IOWR('V', 5, v4l2_format) VIDIOC_S_FMT = _IOWR('V', 5, v4l2_format)
VIDIOC_REQBUFS = _IOWR('V', 8, v4l2_requestbuffers) 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_G_FBUF = _IOR('V', 10, v4l2_framebuffer)
VIDIOC_S_FBUF = _IOW('V', 11, v4l2_framebuffer) VIDIOC_S_FBUF = _IOW('V', 11, v4l2_framebuffer)
VIDIOC_OVERLAY = _IOW('V', 14, ctypes.c_int) 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_S_OUTPUT = _IOWR('V', 47, ctypes.c_int)
VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output) VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output)
VIDIOC_G_AUDOUT = _IOR('V', 49, v4l2_audioout) 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_G_MODULATOR = _IOWR('V', 54, v4l2_modulator)
VIDIOC_S_MODULATOR = _IOW('V', 55, v4l2_modulator) VIDIOC_S_MODULATOR = _IOW('V', 55, v4l2_modulator)
VIDIOC_G_FREQUENCY = _IOWR('V', 56, v4l2_frequency) VIDIOC_G_FREQUENCY = _IOWR('V', 56, v4l2_frequency)