#!/bin/bash # # lights.sh -- expose vehicle turn-signal state as marker files. # # Watches $291 on the body-control CAN bus and translates the # turn-signal nibble (high nibble of byte 3) into one of four # marker files inside $VEHICLE: # # no blinker active -> all blinker-* files removed # left blinker -> $VEHICLE/blinker-left # right blinker -> $VEHICLE/blinker-right # hazards -> $VEHICLE/blinker-hazard # # Pattern: file-existence-as-flag. Other processes can poll # `test -e $VEHICLE/blinker-left` to know whether the left turn # signal is currently on, with zero IPC machinery. The price is # you need a memory-backed filesystem (tmpfs) for $VEHICLE so # repeated touch/rm don't wear out the SD card. # # Originally created: 01.2022 by jmccorm # Last updated: 05.2026 (polish by magikh0e) # # USAGE # ./lights.sh # # REQUIRES # - can-utils (candump) apt install can-utils # - $CANC interface up at the correct bitrate (125 kbps if it's # really CAN-IHS, 500 kbps if it's CAN-C -- see BUS NOTE below) # - $VEHICLE writable tmpfs strongly recommended # # BYTE LAYOUT ($291 byte 3 high nibble, observed on the JEEP platform) # # 0x_0 no blinker # 0x_4 left blinker # 0x_8 right blinker # 0x_C hazards (both) # # Low nibble of byte 3 not yet decoded. Other bytes of $291 # carry running lights / cabin lights / dimmer / headlights -- # see in BMR for the # full byte map. # # BUS NOTE # The original jmccorm version of this script reads from `can1` # and labels it CAN-C. redracer's backlight.sh (same $291 ID, # different fields) reads from `can1` and labels it CAN-IHS. # $291 is body-control state so its natural home is CAN-IHS, # but the TIPM / CGW gateway re-broadcasts many CAN-IHS frames # onto CAN-C for powertrain modules that need them -- which # means $291 is observable on BOTH buses on most FCA platforms. # The CANC= setting below is preserved from jmccorm's original # script; flip it to your CAN-IHS interface if your bus # wiring differs. # # CLEANUP DISCIPLINE # Pattern A (explicit reset): on SIGINT / SIGTERM, all blinker # marker files are removed. Reasoning: a stale blinker-left # file after the script has died is misinformation -- consumers # polling these files should see "no blinker" once the script # stops monitoring, not the last-observed state frozen # indefinitely. See In-Vehicle # Event Handlers for the cleanup-discipline taxonomy. # # REVISION NOTES (2026-05-16) # - Added edge filter: only act when byte-3 high nibble CHANGES. # Original would re-touch the same file on every $291 frame # (~10 Hz cadence) which is wasted I/O even on tmpfs. # - Fixed multi-file bug: `touch blinker-right` without first # removing `blinker-left` left both files present when the # blinker switched directions. Each case branch now clears # all blinker-* before touching the correct one. # - Added SIGINT/SIGTERM trap (pattern A explicit reset). # - Added outer reconnect loop so vehicle sleep doesn't end # the script silently. # - Pre-flight checks $CANC interface and $VEHICLE writability. # Base directory for vehicle status. # NOTE: A memory-based filesystem (tmpfs) is HIGHLY RECOMMENDED. VEHICLE=/run/vehicle/lights mkdir -p "$VEHICLE" # Name for the CAN bus that carries $291. See BUS NOTE above; this # defaults to jmccorm's original `can1`. CANC=can1 DEBUG=false # Values: true, false, raw CAN_ID=291 RETRY_DELAY=5 LAST_BLINKER="__init__" CLEANED=0 cleanup() { [ "$CLEANED" -eq 1 ] && return 0 CLEANED=1 rm -f "$VEHICLE"/blinker-* 2>/dev/null exit 0 } trap cleanup INT TERM # Pre-flight. if ! ip link show "$CANC" >/dev/null 2>&1; then echo "ERROR: CAN interface $CANC not found" >&2 echo " Bring it up first, e.g.:" >&2 echo " ip link set $CANC up type can bitrate 125000" >&2 exit 1 fi if ! touch "$VEHICLE/.write_test" 2>/dev/null; then echo "ERROR: $VEHICLE is not writable" >&2 exit 1 fi rm -f "$VEHICLE/.write_test" # Outer reconnect loop. Vehicle sleep eventually silences the bus; # without this loop the script exits silently when candump's pipe # ends and doesn't come back when the vehicle wakes. while true; do candump -L "$CANC,0${CAN_ID}:0FFFF" 2>/dev/null \ | while read TIME BUS DATA; do [ "$DEBUG" == "raw" ] && echo "CAN data: $DATA" # candump -L line format: "(ts) bus ID#PAYLOAD" # DATA arrives as "291#0011223344556677"; strip "291#" prefix. PAYLOAD=${DATA:4} # Byte 3 high nibble carries the blinker state. # Payload char index 6 = first nibble of byte 3 (bytes are # 0:byte0, 2:byte1, 4:byte2, 6:byte3, ...). BLINKER="${PAYLOAD:6:1}" # Edge filter: skip if the blinker state hasn't changed. $291 # broadcasts at ~10 Hz; without this we'd do filesystem ops on # every frame. [ "$BLINKER" == "$LAST_BLINKER" ] && continue # Update the marker files atomically: clear all blinker-* # first, then create the correct one for the new state. This # is the fix for the original's left-to-right transition bug # where both files could be present. rm -f "$VEHICLE"/blinker-* 2>/dev/null case "$BLINKER" in "0") : ;; # no blinker "4") touch "$VEHICLE/blinker-left" ;; "8") touch "$VEHICLE/blinker-right" ;; "C") touch "$VEHICLE/blinker-hazard" ;; *) [ "$DEBUG" == "true" ] && \ echo "lights.sh: unknown blinker nibble: $BLINKER" >&2 ;; esac [ "$DEBUG" == "true" ] && echo "blinker -> $BLINKER" LAST_BLINKER="$BLINKER" done echo "[reconnect] candump pipe ended; retrying in ${RETRY_DELAY}s" >&2 sleep "$RETRY_DELAY" done