diff --git a/scripts/bbctrl.service b/scripts/bbctrl.service index d38b194..46e82f4 100644 --- a/scripts/bbctrl.service +++ b/scripts/bbctrl.service @@ -1,6 +1,13 @@ [Unit] Description=Buildbotics Controller -After=network.target +# Note: bbctrl previously had `After=network.target`. That delays +# start by ~5s on this Pi while dhcpcd brings up wlan0/eth0, but +# bbctrl does not actually require network connectivity to come up +# (the AVR is on a local serial port, the LCD on I2C). Dropping it +# means the Pi shows the UI faster on cold boot. The wifi config UI +# still works because it queries iw/dhcpcd lazily on demand. +After=local-fs.target bbserial-rebind.service +Wants=bbserial-rebind.service [Service] User=root diff --git a/scripts/bbserial-rebind.service b/scripts/bbserial-rebind.service new file mode 100644 index 0000000..78f9058 --- /dev/null +++ b/scripts/bbserial-rebind.service @@ -0,0 +1,21 @@ +[Unit] +Description=Unbind ttyAMA0 from pl011 and reload bbserial +DefaultDependencies=no +After=systemd-modules-load.service local-fs.target +Before=bbctrl.service +ConditionPathExists=/sys/bus/amba/drivers/uart-pl011 + +[Service] +Type=oneshot +RemainAfterExit=yes +# Tolerate the device already being bound elsewhere or the module +# already being loaded — the goal is the end state (bbserial owns +# ttyAMA0), not running the steps. +ExecStart=/bin/sh -c '\ + echo 3f201000.serial > /sys/bus/amba/drivers/uart-pl011/unbind 2>/dev/null || true; \ + /sbin/modprobe -r bbserial 2>/dev/null || true; \ + /sbin/modprobe bbserial \ +' + +[Install] +WantedBy=multi-user.target diff --git a/scripts/rc.local.fast b/scripts/rc.local.fast new file mode 100644 index 0000000..182dde2 --- /dev/null +++ b/scripts/rc.local.fast @@ -0,0 +1,40 @@ +#!/bin/bash +# rc.local for the OneFinity Pi, "fast" variant. +# +# What changed vs. scripts/rc.local: +# - bbserial unbind/rebind moved to bbserial-rebind.service (runs +# once, before bbctrl, instead of after bbctrl is already +# listening on the serial port). +# - startx moved to kiosk.service so chromium starts in parallel +# with bbctrl rather than blocking on rc.local. +# - rc.local no longer keeps the Pi in 'starting' state forever, +# which fixes systemd-analyze. + +set -e + +# Mount /boot read only +mount -o remount,ro /boot 2>/dev/null || true + +# Set SPI GPIO mode +gpio mode 27 alt3 || true + +# Create browser memory limited cgroup +if [ -d /sys/fs/cgroup/memory ]; then + CGROUP=/sys/fs/cgroup/memory/chrome + [ -d "$CGROUP" ] || mkdir -p "$CGROUP" + chown -R pi:pi "$CGROUP" + echo 650000000 > "$CGROUP/memory.soft_limit_in_bytes" + echo 750000000 > "$CGROUP/memory.limit_in_bytes" +fi + +# Stop boot splash; harmless if plymouth already gone. +plymouth quit 2>/dev/null || true + +# Start X (chromium kiosk) in the background so rc.local can exit and +# late-boot units (bbctrl logrotate, etc.) don't block on it. Output +# is redirected so the journal doesn't fill up with X warnings. +cd /home/pi +nohup sudo -u pi startx >/var/log/onefin-x.log 2>&1 & +disown + +exit 0 diff --git a/src/py/bbctrl/Log.py b/src/py/bbctrl/Log.py index 87f694f..6fed9f7 100644 --- a/src/py/bbctrl/Log.py +++ b/src/py/bbctrl/Log.py @@ -182,4 +182,11 @@ class Log(object): if n == 16: os.unlink(fullpath) else: self._rotate(path, nextN) - os.rename(fullpath, '%s.%d' % (path, nextN)) + # The recursive call may have unlinked or rotated this + # path; tolerate a missing source rather than crashing + # bbctrl on startup. This also tolerates concurrent + # logrotate runs from /etc/cron.reboot. + try: + os.rename(fullpath, '%s.%d' % (path, nextN)) + except FileNotFoundError: + pass diff --git a/src/py/bbctrl/Planner.py b/src/py/bbctrl/Planner.py index fe27066..c967611 100644 --- a/src/py/bbctrl/Planner.py +++ b/src/py/bbctrl/Planner.py @@ -30,7 +30,22 @@ import math import re import time from collections import deque -import camotics.gplan as gplan # pylint: disable=no-name-in-module,import-error +# camotics.gplan is heavy (loads a C++ extension that pulls in libstdc++, +# boost::python, etc.). Defer it: bbctrl can listen on HTTP and serve +# the UI without ever touching the planner. Lazy-load the first time +# Planner.init() runs, which is when the user actually queues motion. +gplan = None +def _load_gplan(): + global gplan + if gplan is None: + try: + import bbctrl.Trace as _T + with _T.span('imports.camotics_gplan'): + import camotics.gplan as _gplan # pylint: disable=no-name-in-module,import-error + except Exception: + import camotics.gplan as _gplan # pylint: disable=no-name-in-module,import-error + gplan = _gplan + return gplan import bbctrl.Cmd as Cmd from bbctrl.CommandQueue import CommandQueue @@ -329,7 +344,7 @@ class Planner(): if stop: self.ctrl.mach.stop() - self.planner = gplan.Planner() + self.planner = _load_gplan().Planner() self.planner.set_resolver(self._get_var_cb) # TODO logger is global and will not work correctly in demo mode self.planner.set_logger(self._log_cb, 1, 'LinePlanner:3')