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