- Increased AVR serial and command buffers.
- Boost HDMI signal.
- Rewrote RPi serial driver.
+ - Automatically scale max CPU speed to reduce RPi temp.
+ - Disable USB camera if RPi temperature above 80°C, back on at 75°C.
## v0.4.7
- Fix homing switch to motor channel mapping with non-standard axis order.
# Reload udev
/etc/init.d/udev restart
-# Stop boot splash so it does not interfere with X if GPU enabled
-grep ^dtoverlay=vc4-kms-v3d /boot/config.txt >/dev/null
-if [ $? -eq 0 ]; then plymouth quit; fi
+# Stop boot splash so it doesn't interfere with X if GPU enabled and to save CPU
+plymouth quit
# Start X in /home/pi
cd /home/pi
--- /dev/null
+/.*.cmd
+/.tmp_versions
+/Module.symvers
+/*.ko
+/*.mod.c
+/*.o
+/kernel
+/modules.order
self.fps = args.fps
self.fourcc = string_to_fourcc(args.fourcc)
- self.offline_jpg = get_image_resource('http/images/offline.jpg')
- self.in_use_jpg = get_image_resource('http/images/in-use.jpg')
+ self.overtemp = False
self.dev = None
self.clients = []
self.path = None
+ self.have_camera = False
# Find connected cameras
for i in range(4):
path = '/dev/video%d' % i
if os.path.exists(path):
+ self.have_camera = True
self.open(path)
break
path = str(device.device_node)
- if action == 'add': self.open(path)
- if action == 'remove' and path == self.path: self.close()
+ if action == 'add':
+ self.have_camera = True
+ self.open(path)
+
+ if action == 'remove' and path == self.path:
+ self.have_camera = False
+ self.close()
def _send_frame(self, frame):
self.log.warning('Failed to read from camera.')
self.ioloop.remove_handler(fd)
self.close()
- return
+ def _update_client_image(self):
+ if self.have_camera and not self.overtemp: return
+ if self.overtemp and self.have_camera: img = 'overtemp'
+ else: img = 'offline'
+
+ if len(self.clients): self.clients[-1].write_img(img)
+
def open(self, path):
try:
+ self._update_client_image()
self.path = path
+ if self.overtemp: return
self.dev = VideoDevice(path)
caps = self.dev.get_info()
self.dev = None
- def close(self):
+ def close(self, overtemp = False):
+ self._update_client_image()
if self.dev is None: return
+
try:
self.ioloop.remove_handler(self.dev)
try:
self.dev.stop()
except: pass
- self._close_dev()
- for client in self.clients:
- client.write_frame_twice(self.offline_jpg)
-
- self.log.info('Closed camera %s' % self.path)
+ self._close_dev()
+ self.log.info('Closed camera')
- except: self.log.warning('Closing camera')
+ except: self.log.exception('Exception while closing camera')
finally: self.dev = None
def add_client(self, client):
self.log.info('Adding camera client: %d' % len(self.clients))
- if len(self.clients):
- self.clients[-1].write_frame_twice(self.in_use_jpg)
-
+ if len(self.clients): self.clients[-1].write_img('in-use')
self.clients.append(client)
-
- if self.dev is None:
- client.write_frame_twice(self.offline_jpg)
+ self._update_client_image()
def remove_client(self, client):
except: pass
+ def set_overtemp(self, overtemp):
+ if self.overtemp == overtemp: return
+ self.overtemp = overtemp
+
+ if overtemp: self.close(True)
+ elif self.path is not None: self.open(self.path)
+
+
class VideoHandler(web.RequestHandler):
boundary = '---boundary---'
self.set_header('Expires', 'Mon, 3 Jan 2000 12:34:56 GMT')
self.set_header('Pragma', 'no-cache')
- if self.camera is None:
- frame = get_image_resource('http/images/offline.jpg')
- self.write_frame_twice(frame)
-
+ if self.camera is None: self.write_img('offline')
else: self.camera.add_client(self)
+ def write_img(self, name):
+ self.write_frame_twice(get_image_resource('http/images/%s.jpg' % name))
+
+
def write_frame(self, frame):
# Don't allow too many frames to queue up
min_size = len(frame) * 2
self.ioloop = bbctrl.IOLoop(ioloop)
self.id = id
self.timeout = None # Used in demo mode
- self.last_temp_warn = 0
- self.temp_thresh = 80
if id and not os.path.exists(id): os.mkdir(id)
self.lcd.add_new_page(bbctrl.MainLCDPage(self))
self.lcd.add_new_page(bbctrl.IPLCDPage(self.lcd))
- if not args.demo: self.check_temp()
-
except Exception: self.log.get('Ctrl').exception()
self.timeout = self.ioloop.call_later(t, cb, *args, **kwargs)
- def check_temp(self):
- with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f:
- temp = round(int(f.read()) / 1000)
-
- # Reset temperature warning threshold after timeout
- if time.time() < self.last_temp_warn + 60: self.temp_thresh = 80
-
- if self.temp_thresh < temp:
- self.last_temp_warn = time.time()
- self.temp_thresh = temp
-
- log = self.log.get('Ctrl')
- log.info('Hot RaspberryPi at %d°C' % temp)
-
- self.ioloop.call_later(15, self.check_temp)
-
-
def get_path(self, dir = None, filename = None):
path = './' + self.id if self.id else '.'
path = path if dir is None else (path + '/' + dir)
--- /dev/null
+################################################################################
+# #
+# 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
+
+
+def read_temp():
+ with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f:
+ return round(int(f.read()) / 1000)
+
+
+def set_max_freq(freq):
+ filename = '/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq'
+ with open(filename, 'w') as f: f.write('%d\n' % freq)
+
+
+class MonitorTemp(object):
+ def __init__(self, app):
+ self.app = app
+
+ ctrl = app.get_ctrl()
+ self.log = ctrl.log.get('Mon')
+ self.ioloop = ctrl.ioloop
+
+ self.last_temp_warn = 0
+ self.temp_thresh = 80
+ self.min_temp = 60
+ self.max_temp = 80
+ self.min_freq = 600000
+ self.max_freq = 1200000
+ self.low_camera_temp = 75
+ self.high_camera_temp = 80
+
+ self.callback()
+
+
+ # Scale max CPU based on temperature
+ def scale_cpu(self, temp):
+ if temp < self.min_temp: cpu_freq = self.max_freq
+ elif self.max_temp < temp: cpu_freq = self.min_freq
+ else:
+ r = 1 - float(temp - self.min_temp) / \
+ (self.max_temp - self.min_temp)
+ cpu_freq = self.min_freq + (self.max_freq - self.min_freq) * r
+
+ set_max_freq(cpu_freq)
+
+
+ def update_camera(self, temp):
+ if self.app.camera is None: return
+
+ # Disable camera if temp too high
+ if temp < self.low_camera_temp: self.app.camera.set_overtemp(False)
+ elif self.high_camera_temp < temp:
+ self.app.camera.set_overtemp(True)
+
+
+ def log_warnings(self, temp):
+ # Reset temperature warning threshold after timeout
+ if time.time() < self.last_temp_warn + 60: self.temp_thresh = 80
+
+ if self.temp_thresh < temp:
+ self.last_temp_warn = time.time()
+ self.temp_thresh = temp
+
+ self.log.info('Hot RaspberryPi at %d°C' % temp)
+
+
+ def callback(self):
+ try:
+ temp = read_temp()
+
+ self.scale_cpu(temp)
+ self.update_camera(temp)
+ self.log_warnings(temp)
+
+ except: self.log.exception()
+
+ self.ioloop.call_later(5, self.callback)
self.ioloop = ioloop
self.ctrls = {}
- # Init controller
- if not self.args.demo: self.get_ctrl()
-
# Init camera
if not args.disable_camera:
if self.args.demo: log = bbctrl.log.Log(args, ioloop, 'camera.log')
else: log = self.get_ctrl().log
self.camera = bbctrl.Camera(ioloop, args, log)
+ else: self.camera = None
+
+ # Init controller
+ if not self.args.demo:
+ self.get_ctrl()
+ self.monitor = bbctrl.MonitorTemp(self)
handlers = [
(r'/websocket', WSConnection),
from bbctrl.AVR import AVR
from bbctrl.AVREmu import AVREmu
from bbctrl.IOLoop import IOLoop
+from bbctrl.MonitorTemp import MonitorTemp
import bbctrl.Cmd as Cmd
import bbctrl.v4l2 as v4l2
import bbctrl.Log as log