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
import bbctrl
import json
import tornado.httpclient
class APIHandler(bbctrl.RequestHandler):
def delete(self, *args, **kwargs):
self.delete_ok(*args, **kwargs)
self.write_json('ok')
def delete_ok(self): raise HTTPError(405)
def delete_ok(self):
raise HTTPError(405)
def put(self, *args, **kwargs):
self.put_ok(*args, **kwargs)
self.write_json('ok')
def put_ok(self): raise HTTPError(405)
def put_ok(self):
raise HTTPError(405)
def prepare(self):
self.json = {}
@@ -59,11 +29,9 @@ class APIHandler(bbctrl.RequestHandler):
except ValueError:
raise HTTPError(400, 'Unable to parse JSON')
def set_default_headers(self):
self.set_header('Content-Type', 'application/json')
def write_error(self, status_code, **kwargs):
e = {}
@@ -73,16 +41,17 @@ class APIHandler(bbctrl.RequestHandler):
typ, value, tb = kwargs['exc_info']
if isinstance(value, HTTPError) and value.log_message:
e['message'] = value.log_message % value.args
else: e['message'] = str(kwargs['exc_info'][1])
else:
e['message'] = str(kwargs['exc_info'][1])
else: e['message'] = 'Unknown error'
else:
e['message'] = 'Unknown error'
e['code'] = status_code
self.write_json(e)
def write_json(self, data, pretty = False):
if pretty: data = json.dumps(data, indent = 2, separators = (',', ': '))
else: data = json.dumps(data, separators = (',', ':'))
def write_json(self, data, pretty=False):
if pretty: data = json.dumps(data, indent=2, separators=(',', ': '))
else: data = json.dumps(data, separators=(',', ':'))
self.write(data)

View File

@@ -1,59 +1,29 @@
################################################################################
# #
# This file is part of the Buildbotics firmware. #
# #
# Copyright (c) 2015 - 2018, Buildbotics LLC #
# All rights reserved. #
# #
# This file ("the software") is free software: you can redistribute it #
# and/or modify it under the terms of the GNU General Public License, #
# version 2 as published by the Free Software Foundation. You should #
# have received a copy of the GNU General Public License, version 2 #
# along with the software. If not, see <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 ctypes
import serial
import time
import traceback
import ctypes
import bbctrl
import bbctrl.Cmd as Cmd
class serial_struct(ctypes.Structure):
_fields_ = [
('type', ctypes.c_int),
('line', ctypes.c_int),
('port', ctypes.c_uint),
('irq', ctypes.c_int),
('flags', ctypes.c_int),
('xmit_fifo_size', ctypes.c_int),
('custom_divisor', ctypes.c_int),
('baud_base', ctypes.c_int),
('close_delay', ctypes.c_ushort),
('io_type', ctypes.c_byte),
('reserved', ctypes.c_byte),
('hub6', ctypes.c_int),
('closing_wait', ctypes.c_ushort),
('closing_wait2', ctypes.c_ushort),
('iomem_base', ctypes.c_char_p),
('type', ctypes.c_int),
('line', ctypes.c_int),
('port', ctypes.c_uint),
('irq', ctypes.c_int),
('flags', ctypes.c_int),
('xmit_fifo_size', ctypes.c_int),
('custom_divisor', ctypes.c_int),
('baud_base', ctypes.c_int),
('close_delay', ctypes.c_ushort),
('io_type', ctypes.c_byte),
('reserved', ctypes.c_byte),
('hub6', ctypes.c_int),
('closing_wait', ctypes.c_ushort),
('closing_wait2', ctypes.c_ushort),
('iomem_base', ctypes.c_char_p),
('iomem_reg_shift', ctypes.c_ushort),
('port_high', ctypes.c_uint),
('iomap_base', ctypes.c_ulong),
('port_high', ctypes.c_uint),
('iomap_base', ctypes.c_ulong),
]
@@ -65,11 +35,12 @@ def serial_set_low_latency(sp):
ss = serial_struct()
fcntl.ioctl(sp, termios.TIOCGSERIAL, ss)
ss.flags |= 1 << ASYNCB_LOW_LATENCY # pylint: disable=no-member
ss.flags |= 1 << ASYNCB_LOW_LATENCY # pylint: disable=no-member
fcntl.ioctl(sp, termios.TIOCSSERIAL, ss)
class AVR(object):
def __init__(self, ctrl):
self.ctrl = ctrl
self.log = ctrl.log.get('AVR')
@@ -79,14 +50,16 @@ class AVR(object):
self.read_cb = None
self.write_cb = None
def close(self): pass
def close(self):
pass
def _start(self):
try:
self.sp = serial.Serial(self.ctrl.args.serial, self.ctrl.args.baud,
rtscts = 1, timeout = 0, write_timeout = 0)
self.sp = serial.Serial(self.ctrl.args.serial,
self.ctrl.args.baud,
rtscts=1,
timeout=0,
write_timeout=0)
self.sp.nonblocking()
#serial_set_low_latency(self.sp)
@@ -98,7 +71,6 @@ class AVR(object):
self.ctrl.ioloop.add_handler(self.sp, self._serial_handler,
self.ctrl.ioloop.READ)
def set_handlers(self, read_cb, write_cb):
if self.read_cb is not None or self.write_cb is not None:
raise Exception('Handler already set')
@@ -107,7 +79,6 @@ class AVR(object):
self.write_cb = write_cb
self._start()
def enable_write(self, enable):
if self.sp is None: return
@@ -115,11 +86,9 @@ class AVR(object):
if enable: flags |= self.ctrl.ioloop.WRITE
self.ctrl.ioloop.update_handler(self.sp, flags)
def _serial_write(self):
self.write_cb(lambda data: self.sp.write(data))
def _serial_read(self):
try:
data = ''
@@ -129,17 +98,16 @@ class AVR(object):
except Exception as e:
self.log.warning('%s: %s', e, data)
def _serial_handler(self, fd, events):
try:
if self.ctrl.ioloop.READ & events: self._serial_read()
if self.ctrl.ioloop.WRITE & events: self._serial_write()
except Exception as e:
self.log.warning('Serial handler error: %s', traceback.format_exc())
self.log.warning('Serial handler error: %s',
traceback.format_exc())
def i2c_command(self, cmd, byte = None, word = None, block = None):
def i2c_command(self, cmd, byte=None, word=None, block=None):
self.log.info('I2C: %s b=%s w=%s d=%s' % (cmd, byte, word, block))
retry = 10
cmd = ord(cmd[0])

View File

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

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

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

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
import bbctrl
# 16-bit less with wrap around
def id_less(a, b): return (1 << 15) < (a - b) & ((1 << 16) - 1)
def id_less(a, b):
return (1 << 15) < (a - b) & ((1 << 16) - 1)
class CommandQueue():
def __init__(self, ctrl):
self.log = ctrl.log.get('CmdQ')
self.log.set_level(bbctrl.log.WARNING)
@@ -42,23 +17,20 @@ class CommandQueue():
self.releaseID = 0
self.q = deque()
def is_active(self): return len(self.q)
def is_active(self):
return len(self.q)
def clear(self):
self.lastEnqueueID = 0
self.releaseID = 0
self.q.clear()
def enqueue(self, id, cb, *args, **kwargs):
self.log.info('add(#%d) releaseID=%d', id, self.releaseID)
self.lastEnqueueID = id
self.q.append([id, cb, args, kwargs])
self._release()
def _release(self):
while len(self.q):
id, cb, args, kwargs = self.q[0]
@@ -72,9 +44,8 @@ class CommandQueue():
try:
if cb is not None: cb(*args, **kwargs)
except Exception:
self.log.exception('Internal error: Command queue callback error')
self.log.exception(
'Internal error: Command queue callback error')
def release(self, id):
if id and not id_less(self.releaseID, id):

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

View File

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

View File

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

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
try:
@@ -37,12 +10,12 @@ except:
class I2C(object):
def __init__(self, port, disabled):
self.port = port
self.i2c_bus = None
self.disabled = disabled or smbus is None
def connect(self):
if self.disabled: return
if self.i2c_bus is None:
@@ -54,7 +27,6 @@ class I2C(object):
if e.errno == errno.ENOENT: self.disabled = True
else: raise type(e)('I2C failed to open device: %s' % e)
def read_word(self, addr):
self.connect()
if self.disabled: return
@@ -67,8 +39,7 @@ class I2C(object):
self.i2c_bus = None
raise type(e)('I2C read word failed: %s' % e)
def write(self, addr, cmd, byte = None, word = None, block = None):
def write(self, addr, cmd, byte=None, word=None, block=None):
self.connect()
if self.disabled: return
@@ -83,7 +54,8 @@ class I2C(object):
if isinstance(block, str): block = list(map(ord, block))
self.i2c_bus.write_i2c_block_data(addr, cmd, block)
else: self.i2c_bus.write_byte(addr, cmd)
else:
self.i2c_bus.write_byte(addr, cmd)
except IOError as e:
self.i2c_bus.close()

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 bbctrl
class CB(object):
def __init__(self, ioloop, delay, cb, *args, **kwargs):
self.ioloop = ioloop
self.cb = cb
@@ -49,45 +22,41 @@ class IOLoop(object):
WRITE = tornado.ioloop.IOLoop.WRITE
ERROR = tornado.ioloop.IOLoop.ERROR
def __init__(self, ioloop):
self.ioloop = ioloop
self.fds = set()
self.handles = set()
self.callbacks = {}
def close(self):
for fd in list(self.fds): self.ioloop.remove_handler(fd)
for h in list(self.handles): self.ioloop.remove_timeout(h)
for h in list(self.callbacks): self.ioloop.remove_timeout(h)
for fd in list(self.fds):
self.ioloop.remove_handler(fd)
for h in list(self.handles):
self.ioloop.remove_timeout(h)
for h in list(self.callbacks):
self.ioloop.remove_timeout(h)
def add_handler(self, fd, handler, events):
self.ioloop.add_handler(fd, handler, events)
if hasattr(fd, 'fileno'): fd = fd.fileno()
self.fds.add(fd)
def remove_handler(self, h):
self.ioloop.remove_handler(h)
if hasattr(h, 'fileno'): h = h.fileno()
self.fds.remove(h)
def update_handler(self, fd, events): self.ioloop.update_handler(fd, events)
def update_handler(self, fd, events):
self.ioloop.update_handler(fd, events)
def call_later(self, delay, callback, *args, **kwargs):
cb = CB(self, delay, callback, *args, **kwargs)
return cb.h
def remove_timeout(self, h):
self.ioloop.remove_timeout(h)
if h in self.handles: self.handles.remove(h)
if h in self.callbacks: del self.callbacks[h]
def add_callback(self, cb, *args, **kwargs):
self.ioloop.add_callback(cb, *args, **kwargs)

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

View File

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

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
@@ -35,10 +8,12 @@ def read_temp():
def set_max_freq(freq):
filename = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq'
with open(filename, 'w') as f: f.write('%d\n' % freq)
with open(filename, 'w') as f:
f.write('%d\n' % freq)
class MonitorTemp(object):
def __init__(self, app):
self.app = app
@@ -57,7 +32,6 @@ class MonitorTemp(object):
self.callback()
# Scale max CPU based on temperature
def scale_cpu(self, temp):
if temp < self.min_temp: cpu_freq = self.max_freq
@@ -69,7 +43,6 @@ class MonitorTemp(object):
set_max_freq(cpu_freq)
def update_camera(self, temp):
if self.app.camera is None: return
@@ -78,7 +51,6 @@ class MonitorTemp(object):
elif self.high_camera_temp < temp:
self.app.camera.set_overtemp(True)
def log_warnings(self, temp):
# Reset temperature warning threshold after timeout
if time.time() < self.last_temp_warn + 60: self.temp_thresh = 80
@@ -89,7 +61,6 @@ class MonitorTemp(object):
self.log.info('Hot RaspberryPi at %d°C' % temp)
def callback(self):
try:
temp = read_temp()
@@ -99,6 +70,7 @@ class MonitorTemp(object):
self.update_camera(temp)
self.log_warnings(temp)
except: self.log.exception('Internal error: Temperature status')
except:
self.log.exception('Internal error: Temperature status')
self.ioloop.call_later(5, self.callback)

View File

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

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

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

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
import bbctrl
import tornado.web
import traceback
class RequestHandler(tornado.web.RequestHandler):
def __init__(self, app, request, **kwargs):
super().__init__(app, request, **kwargs)
self.app = app
def get_ctrl(self):
return self.app.get_ctrl(self.get_cookie('client-id'))
def get_ctrl(self): return self.app.get_ctrl(self.get_cookie('client-id'))
def get_log(self, name = 'API'): return self.get_ctrl().log.get(name)
def get_log(self, name='API'):
return self.get_ctrl().log.get(name)
def get_path(self, path = None, filename = None):
def get_path(self, path=None, filename=None):
return self.get_ctrl().get_path(path, filename)
def get_upload(self, filename = None):
def get_upload(self, filename=None):
return self.get_ctrl().get_upload(filename)
# Override exception logging
def log_exception(self, typ, value, tb):
if (isinstance(value, HTTPError) and
400 <= value.status_code and value.status_code < 500): return
if (isinstance(value, HTTPError) and 400 <= value.status_code
and value.status_code < 500):
return
log = self.get_log()
log.set_level(bbctrl.log.DEBUG)

View File

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

View File

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

View File

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

View File

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

View File

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