From: Joseph Coffland Date: Thu, 23 May 2019 00:38:42 +0000 (-0700) Subject: Automatically scale max CPU speed to reduce RPi temp, Disable USB camera if RPi tempe... X-Git-Url: https://git.buildbotics.com/?a=commitdiff_plain;h=c1b7887c5572fbf49198c619aeac55968dded979;p=bbctrl-firmware Automatically scale max CPU speed to reduce RPi temp, Disable USB camera if RPi temperature above 80°C, back on at 75°C --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 06ef92b..c97aa73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Buildbotics CNC Controller Firmware Changelog - 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. diff --git a/scripts/rc.local b/scripts/rc.local index aecc57f..43e549a 100755 --- a/scripts/rc.local +++ b/scripts/rc.local @@ -23,9 +23,8 @@ fi # 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 diff --git a/src/bbserial/.gitignore b/src/bbserial/.gitignore new file mode 100644 index 0000000..a90b6ca --- /dev/null +++ b/src/bbserial/.gitignore @@ -0,0 +1,8 @@ +/.*.cmd +/.tmp_versions +/Module.symvers +/*.ko +/*.mod.c +/*.o +/kernel +/modules.order diff --git a/src/py/bbctrl/Camera.py b/src/py/bbctrl/Camera.py index a5f0fcd..385c381 100755 --- a/src/py/bbctrl/Camera.py +++ b/src/py/bbctrl/Camera.py @@ -290,16 +290,17 @@ class Camera(object): 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 @@ -317,8 +318,13 @@ class Camera(object): 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): @@ -344,13 +350,21 @@ class Camera(object): 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() @@ -389,34 +403,29 @@ class Camera(object): 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): @@ -426,6 +435,14 @@ class Camera(object): 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---' @@ -449,13 +466,14 @@ class VideoHandler(web.RequestHandler): 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 diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py index 2a0955a..854f538 100644 --- a/src/py/bbctrl/Ctrl.py +++ b/src/py/bbctrl/Ctrl.py @@ -36,8 +36,6 @@ class Ctrl(object): 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) @@ -67,8 +65,6 @@ class Ctrl(object): 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() @@ -86,23 +82,6 @@ class Ctrl(object): 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) diff --git a/src/py/bbctrl/MonitorTemp.py b/src/py/bbctrl/MonitorTemp.py new file mode 100644 index 0000000..839b2e6 --- /dev/null +++ b/src/py/bbctrl/MonitorTemp.py @@ -0,0 +1,103 @@ +################################################################################ +# # +# 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 . # +# # +# 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 # +# . # +# # +# For information regarding this software email: # +# "Joseph Coffland" # +# # +################################################################################ + +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) diff --git a/src/py/bbctrl/Web.py b/src/py/bbctrl/Web.py index 7a0d499..51e7c16 100644 --- a/src/py/bbctrl/Web.py +++ b/src/py/bbctrl/Web.py @@ -479,14 +479,17 @@ class Web(tornado.web.Application): 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), diff --git a/src/py/bbctrl/__init__.py b/src/py/bbctrl/__init__.py index b8eed91..3f54b19 100644 --- a/src/py/bbctrl/__init__.py +++ b/src/py/bbctrl/__init__.py @@ -58,6 +58,7 @@ from bbctrl.Camera import Camera, VideoHandler 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 diff --git a/src/resources/images/overtemp.jpg b/src/resources/images/overtemp.jpg new file mode 100644 index 0000000..d39b31a Binary files /dev/null and b/src/resources/images/overtemp.jpg differ