Reformatting of python files.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user