Files
car-radio/car-radio/code/cmd/backlight/main.go
T
2026-10-27 21:13:36 -05:00

179 lines
5.1 KiB
Go

// backlight isolates which frame turns on the FCIM backlight. Press Enter to
// step through conditions; each one sends a single candidate (or one piece of
// 0x3E8) on top of the safe base that is known not to light it:
// 0x50C keep-alive on every 0x55D, the BCD clock on 0x3F2, and the label text.
//
// The FDIM shows the active condition (e.g. "COND 3/12" / "3E8 BASELINE").
// The FCIM may hold the backlight on for a while after a frame stops; if it
// stays lit, Ctrl-C, wait for it to go dark, and restart with -start N.
//
// Usage:
//
// go run ./cmd/backlight
// go run ./cmd/backlight -start 5
// go run ./cmd/backlight -only 5 # hold condition 5 until Ctrl-C
package main
import (
"bufio"
"flag"
"fmt"
"log"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/brutella/can"
"fdim"
)
type frame struct {
id uint32
n uint8
data [8]uint8
}
var (
init3F1 = frame{0x3F1, 2, [8]uint8{0xF5, 0x90}}
base3E8 = frame{0x3E8, 8, [8]uint8{0x07, 0x00, 0x10, 0x04, 0, 0, 0, 0}}
base3EF = frame{0x3EF, 8, [8]uint8{0x32, 0x32, 0x32, 0x32, 0x03, 0x00, 0x00, 0x20}}
keepWake = frame{0x50C, 3, [8]uint8{0x0C, 0x02, 0x00}}
)
func e8(b0, b1, b2, b3 uint8) frame {
return frame{0x3E8, 8, [8]uint8{b0, b1, b2, b3, 0, 0, 0, 0}}
}
// condition: once is sent when the condition starts, repeat every -refresh.
type condition struct {
label string // FDIM row 2, max 20 chars
desc string
once []frame
repeat []frame
}
var conditions = []condition{
{label: "CONTROL", desc: "base only (keep-alive, clock, text) - expect OFF"},
{label: "3F1 ONCE", desc: "0x3F1 F5 90 sent once", once: []frame{init3F1}},
{label: "3F1 REPEAT", desc: "0x3F1 F5 90 every refresh", repeat: []frame{init3F1}},
{label: "3EF BASELINE", desc: "0x3EF 32 32 32 32 03 00 00 20 every refresh", repeat: []frame{base3EF}},
{label: "3E8 BASELINE", desc: "0x3E8 07 00 10 04 00 00 00 00 every refresh", repeat: []frame{base3E8}},
{label: "3E8 ALL ZERO", desc: "0x3E8 00 00 00 00 00 00 00 00", repeat: []frame{e8(0, 0, 0, 0)}},
{label: "3E8 B0=07 SOURCE", desc: "0x3E8 07 00 00 00 ... (source AUX only)", repeat: []frame{e8(0x07, 0, 0, 0)}},
{label: "3E8 B2=10 VOLUME", desc: "0x3E8 00 00 10 00 ... (volume only)", repeat: []frame{e8(0, 0, 0x10, 0)}},
{label: "3E8 B3=04 MODE", desc: "0x3E8 00 00 00 04 ... (byte3 bit 0x04 only)", repeat: []frame{e8(0, 0, 0, 0x04)}},
{label: "3E8 NO B3", desc: "0x3E8 07 00 10 00 ... (baseline with byte3 cleared)", repeat: []frame{e8(0x07, 0, 0x10, 0)}},
{label: "ALL THREE", desc: "3F1 once + 3E8 + 3EF baselines (what fdimprobe sends) - expect ON",
once: []frame{init3F1}, repeat: []frame{base3E8, base3EF}},
{label: "CONTROL AGAIN", desc: "base only - does the backlight go back off?"},
}
func main() {
iface := flag.String("if", "can0", "CAN interface")
start := flag.Int("start", 1, "condition number to start at")
only := flag.Int("only", 0, "run just this condition number and hold it until Ctrl-C")
refresh := flag.Duration("refresh", 500*time.Millisecond, "resend interval")
flag.Parse()
if *only != 0 {
*start = *only
}
if *start < 1 || *start > len(conditions) {
fmt.Fprintf(os.Stderr, "-start/-only must be 1..%d\n", len(conditions))
os.Exit(2)
}
bus, err := can.NewBusForInterfaceWithName(*iface)
if err != nil {
log.Fatal(err)
}
bus.SubscribeFunc(func(f can.Frame) {
if f.ID == 0x55D {
send(bus, keepWake)
}
})
go func() {
if err := bus.ConnectAndPublish(); err != nil {
log.Fatal(err)
}
}()
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
go func() {
<-sig
bus.Disconnect()
os.Exit(0)
}()
var (
mu sync.Mutex
cur = *start - 1
)
send(bus, keepWake)
go func() {
for {
f := fdim.ClockFrame(time.Now(), false)
send(bus, frame{f.ID, f.Length, f.Data})
time.Sleep(time.Second)
}
}()
go func() {
for {
mu.Lock()
i := cur
mu.Unlock()
c := conditions[i]
for _, f := range c.repeat {
send(bus, f)
}
sendText(bus, fmt.Sprintf("COND %d/%d", i+1, len(conditions)), c.label)
time.Sleep(*refresh)
}
}()
if *only != 0 {
c := conditions[*only-1]
for _, f := range c.once {
send(bus, f)
}
fmt.Printf("[%d/%d] %s\n %s\n holding, Ctrl-C to quit\n", *only, len(conditions), c.label, c.desc)
select {}
}
fmt.Println("Watch the FCIM backlight. Enter = next condition, Ctrl-C = quit.")
in := bufio.NewReader(os.Stdin)
for i := *start - 1; i < len(conditions); i++ {
c := conditions[i]
mu.Lock()
cur = i
mu.Unlock()
for _, f := range c.once {
send(bus, f)
}
fmt.Printf("[%d/%d] %s\n %s\n Enter for next ", i+1, len(conditions), c.label, c.desc)
in.ReadString('\n')
}
fmt.Println("done")
}
func sendText(bus *can.Bus, line1, line2 string) {
body, err := fdim.EncodeISOTP(0x337, fdim.BuildTwoLine(line1, line2))
if err != nil {
log.Println("encode:", err)
return
}
for _, f := range append([]fdim.Frame{fdim.PairB336, fdim.PairB324}, body...) {
send(bus, frame{f.ID, f.Length, f.Data})
}
}
func send(bus *can.Bus, f frame) {
bus.Publish(can.Frame{ID: f.id, Length: f.n, Data: f.data})
time.Sleep(5 * time.Millisecond)
}