From c3999d6bb5261c631f4bfaa6e3399e79d931ef9a Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Sat, 31 Aug 2024 14:01:40 +0200 Subject: [PATCH] new: implemented can.obd2 builtin parser --- modules/can/can.go | 6 + modules/can/can_dbc.go | 18 +- modules/can/can_dump_reader.go | 3 +- modules/can/can_message.go | 24 +++ modules/can/can_obd2.go | 54 +++++ modules/can/can_obd2_message.go | 72 +++++++ modules/can/can_obd2_pid_request.go | 247 +++++++++++++++++++++++ modules/can/can_obd2_pid_response.go | 25 +++ modules/can/can_recon.go | 22 +- modules/events_stream/events_view_can.go | 99 ++++++--- modules/ui/ui | 2 +- 11 files changed, 520 insertions(+), 52 deletions(-) create mode 100644 modules/can/can_message.go create mode 100644 modules/can/can_obd2.go create mode 100644 modules/can/can_obd2_message.go create mode 100644 modules/can/can_obd2_pid_request.go create mode 100644 modules/can/can_obd2_pid_response.go diff --git a/modules/can/can.go b/modules/can/can.go index 5f447174..324d8e12 100644 --- a/modules/can/can.go +++ b/modules/can/can.go @@ -20,6 +20,7 @@ type CANModule struct { filter string filterExpr *bexpr.Evaluator dbc *DBC + obd2 *OBD2 conn net.Conn recv *socketcan.Receiver send *socketcan.Transmitter @@ -30,6 +31,7 @@ func NewCanModule(s *session.Session) *CANModule { SessionModule: session.NewSessionModule("can", s), filter: "", dbc: &DBC{}, + obd2: &OBD2{}, filterExpr: nil, transport: "can", deviceName: "can0", @@ -61,6 +63,10 @@ func NewCanModule(s *session.Session) *CANModule { "", "Optional boolean expression to select frames to report.")) + mod.AddParam(session.NewBoolParameter("can.parse.obd2", + "false", + "Enable built in OBD2 PID parsing.")) + mod.AddHandler(session.NewModuleHandler("can.recon on", "", "Start CAN-bus discovery.", func(args []string) error { diff --git a/modules/can/can_dbc.go b/modules/can/can_dbc.go index 36db7a62..2213fe0f 100644 --- a/modules/can/can_dbc.go +++ b/modules/can/can_dbc.go @@ -6,7 +6,6 @@ import ( "sync" "github.com/evilsocket/islazy/str" - "go.einride.tech/can" "go.einride.tech/can/pkg/descriptor" ) @@ -54,7 +53,7 @@ func (dbc *DBC) LoadData(mod *CANModule, name string, input []byte) error { return nil } -func (dbc *DBC) Parse(mod *CANModule, frame can.Frame, msg *Message) bool { +func (dbc *DBC) Parse(mod *CANModule, msg *Message) bool { dbc.RLock() defer dbc.RUnlock() @@ -64,7 +63,7 @@ func (dbc *DBC) Parse(mod *CANModule, frame can.Frame, msg *Message) bool { } // if the database contains this message id - if message, found := dbc.db.Message(frame.ID); found { + if message, found := dbc.db.Message(msg.Frame.ID); found { msg.Name = message.Name // find source full info in DBC nodes @@ -76,24 +75,21 @@ func (dbc *DBC) Parse(mod *CANModule, frame can.Frame, msg *Message) bool { } // add CAN source if new - _, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:]) - - msg.Signals = make(map[string]string) + _, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, msg.Frame.Data[:]) // parse signals for _, signal := range message.Signals { var value string if signal.Length <= 32 && signal.IsFloat { - value = fmt.Sprintf("%f", signal.UnmarshalFloat(frame.Data)) + value = fmt.Sprintf("%f", signal.UnmarshalFloat(msg.Frame.Data)) } else if signal.Length == 1 { - value = fmt.Sprintf("%v", signal.UnmarshalBool(frame.Data)) + value = fmt.Sprintf("%v", signal.UnmarshalBool(msg.Frame.Data)) } else if signal.IsSigned { - value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data)) + value = fmt.Sprintf("%d", signal.UnmarshalSigned(msg.Frame.Data)) } else { - value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(frame.Data)) + value = fmt.Sprintf("%d", signal.UnmarshalUnsigned(msg.Frame.Data)) } - msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit)) } diff --git a/modules/can/can_dump_reader.go b/modules/can/can_dump_reader.go index be2e3cab..5a69651e 100644 --- a/modules/can/can_dump_reader.go +++ b/modules/can/can_dump_reader.go @@ -55,8 +55,7 @@ func (mod *CANModule) startDumpReader() error { scanner := bufio.NewScanner(file) for scanner.Scan() { - line := str.Trim(scanner.Text()) - if line != "" { + if line := str.Trim(scanner.Text()); line != "" { if m := dumpLineParser.FindStringSubmatch(line); len(m) != 4 { mod.Warning("unexpected line: '%s' -> %d matches", line, len(m)) } else if timeval, err := parseTimeval(m[1]); err != nil { diff --git a/modules/can/can_message.go b/modules/can/can_message.go new file mode 100644 index 00000000..a191bff5 --- /dev/null +++ b/modules/can/can_message.go @@ -0,0 +1,24 @@ +package can + +import ( + "github.com/bettercap/bettercap/v2/network" + "go.einride.tech/can" +) + +type Message struct { + // the raw frame + Frame can.Frame + // parsed as OBD2 + OBD2 *OBD2Message + // parsed from DBC + Name string + Source *network.CANDevice + Signals map[string]string +} + +func NewCanMessage(frame can.Frame) Message { + return Message{ + Frame: frame, + Signals: make(map[string]string), + } +} diff --git a/modules/can/can_obd2.go b/modules/can/can_obd2.go new file mode 100644 index 00000000..1075e953 --- /dev/null +++ b/modules/can/can_obd2.go @@ -0,0 +1,54 @@ +package can + +import ( + "fmt" + "sync" +) + +type OBD2 struct { + sync.RWMutex + + enabled bool +} + +func (obd *OBD2) Enabled() bool { + obd.RLock() + defer obd.RUnlock() + return obd.enabled +} + +func (obd *OBD2) Enable(enable bool) { + obd.RLock() + defer obd.RUnlock() + obd.enabled = enable +} + +func (obd *OBD2) Parse(mod *CANModule, msg *Message) bool { + obd.RLock() + defer obd.RUnlock() + + // did we load any DBC database? + if !obd.enabled { + return false + } + + odbMessage := &OBD2Message{} + + if msg.Frame.ID == OBD2BroadcastRequestID { + // parse as request + if odbMessage.ParseRequest(msg.Frame) { + msg.OBD2 = odbMessage + return true + } + } else if msg.Frame.ID >= OBD2ECUResponseMinID && msg.Frame.ID <= OBD2ECUResponseMaxID { + // parse as response + if odbMessage.ParseResponse(msg.Frame) { + msg.OBD2 = odbMessage + // add CAN source if new + _, msg.Source = mod.Session.CAN.AddIfNew(fmt.Sprintf("ECU_%d", odbMessage.ECU), "", msg.Frame.Data[:]) + return true + } + } + + return false +} diff --git a/modules/can/can_obd2_message.go b/modules/can/can_obd2_message.go new file mode 100644 index 00000000..cb7536d0 --- /dev/null +++ b/modules/can/can_obd2_message.go @@ -0,0 +1,72 @@ +package can + +import ( + "fmt" +) + +// https://en.wikipedia.org/wiki/OBD-II_PIDs + +// https://www.csselectronics.com/pages/obd2-explained-simple-intro +// https://www.csselectronics.com/pages/obd2-pid-table-on-board-diagnostics-j1979 + +// https://stackoverflow.com/questions/40826932/how-can-i-get-mode-pids-from-raw-obd2-identifier-11-or-29-bit + +// https://github.com/ejvaughan/obdii/blob/master/src/OBDII.c + +// TODO: add support for 29bit identifiers +const OBD2BroadcastRequestID = 0x7DF +const OBD2ECUResponseMinID = 0x7E0 +const OBD2ECUResponseMaxID = 0x7EF + +type OBD2Service uint8 + +func (s OBD2Service) String() string { + switch s { + case 0x01: + return "Show current data" + case 0x02: + return "Show freeze frame data" + case 0x03: + return "Show stored Diagnostic Trouble Codes" + case 0x04: + return "Clear Diagnostic Trouble Codes and stored values" + case 0x05: + return "Test results, oxygen sensor monitoring (non CAN only)" + case 0x06: + return "Test results, other component/system monitoring (Test results, oxygen sensor monitoring for CAN only)" + case 0x07: + return "Show pending Diagnostic Trouble Codes (detected during current or last driving cycle)" + case 0x08: + return "Control operation of on-board component/system" + case 0x09: + return "Request vehicle information" + case 0x0A: + return "Permanent Diagnostic Trouble Codes (DTCs) (Cleared DTCs)" + } + + return fmt.Sprintf("service 0x%x", uint8(s)) +} + +type OBD2MessageType uint8 + +const ( + OBD2MessageTypeRequest OBD2MessageType = iota + OBD2MessageTypeResponse +) + +func (t OBD2MessageType) String() string { + if t == OBD2MessageTypeRequest { + return "request" + } else { + return "response" + } +} + +type OBD2Message struct { + Type OBD2MessageType + ECU uint8 + Service OBD2Service + PID OBD2PID + Size uint8 + Data []uint8 +} diff --git a/modules/can/can_obd2_pid_request.go b/modules/can/can_obd2_pid_request.go new file mode 100644 index 00000000..38317cc3 --- /dev/null +++ b/modules/can/can_obd2_pid_request.go @@ -0,0 +1,247 @@ +package can + +import ( + "encoding/binary" + "fmt" + + "go.einride.tech/can" +) + +var servicePIDS = map[uint8]map[uint16]string{ + 0x01: { + 0x0: "PIDs supported [$01 - $20]", + 0x1: "Monitor status since DTCs cleared.", + 0x2: "DTC that caused freeze frame to be stored.", + 0x3: "Fuel system status", + 0x4: "Calculated engine load", + 0x5: "Engine coolant temperature", + 0x6: "Short term fuel trim (STFT)—Bank 1", + 0x7: "Long term fuel trim (LTFT)—Bank 1", + 0x8: "Short term fuel trim (STFT)—Bank 2", + 0x9: "Long term fuel trim (LTFT)—Bank 2", + 0x0A: "Fuel pressure (gauge pressure)", + 0x0B: "Intake manifold absolute pressure", + 0x0C: "Engine speed", + 0x0D: "Vehicle speed", + 0x0E: "Timing advance", + 0x0F: "Intake air temperature", + 0x10: "Mass air flow sensor (MAF) air flow rate", + 0x11: "Throttle position", + 0x12: "Commanded secondary air status", + 0x13: "Oxygen sensors present", + 0x14: "Oxygen Sensor 1", + 0x15: "Oxygen Sensor 2", + 0x16: "Oxygen Sensor 3", + 0x17: "Oxygen Sensor 4", + 0x18: "Oxygen Sensor 5", + 0x19: "Oxygen Sensor 6", + 0x1A: "Oxygen Sensor 7", + 0x1B: "Oxygen Sensor 8", + 0x1C: "OBD standards this vehicle conforms to", + 0x1D: "Oxygen sensors present", + 0x1E: "Auxiliary input status", + 0x1F: "Run time since engine start", + 0x20: "PIDs supported [$21 - $40]", + 0x21: "Distance traveled with malfunction indicator lamp (MIL) on", + 0x22: "Fuel Rail Pressure (relative to manifold vacuum)", + 0x23: "Fuel Rail Gauge Pressure (diesel, or gasoline direct injection)", + 0x24: "Oxygen Sensor 1", + 0x25: "Oxygen Sensor 2", + 0x26: "Oxygen Sensor 3", + 0x27: "Oxygen Sensor 4", + 0x28: "Oxygen Sensor 5", + 0x29: "Oxygen Sensor 6", + 0x2A: "Oxygen Sensor 7", + 0x2B: "Oxygen Sensor 8", + 0x2C: "Commanded EGR", + 0x2D: "EGR Error", + 0x2E: "Commanded evaporative purge", + 0x2F: "Fuel Tank Level Input", + 0x30: "Warm-ups since codes cleared", + 0x31: "Distance traveled since codes cleared", + 0x32: "Evap. System Vapor Pressure", + 0x33: "Absolute Barometric Pressure", + 0x34: "Oxygen Sensor 1", + 0x35: "Oxygen Sensor 2", + 0x36: "Oxygen Sensor 3", + 0x37: "Oxygen Sensor 4", + 0x38: "Oxygen Sensor 5", + 0x39: "Oxygen Sensor 6", + 0x3A: "Oxygen Sensor 7", + 0x3B: "Oxygen Sensor 8", + 0x3C: "Catalyst Temperature: Bank 1, Sensor 1", + 0x3D: "Catalyst Temperature: Bank 2, Sensor 1", + 0x3E: "Catalyst Temperature: Bank 1, Sensor 2", + 0x3F: "Catalyst Temperature: Bank 2, Sensor 2", + 0x40: "PIDs supported [$41 - $60]", + 0x41: "Monitor status this drive cycle", + 0x42: "Control module voltage", + 0x43: "Absolute load value", + 0x44: "Commanded Air-Fuel Equivalence Ratio (lambda,λ)", + 0x45: "Relative throttle position", + 0x46: "Ambient air temperature", + 0x47: "Absolute throttle position B", + 0x48: "Absolute throttle position C", + 0x49: "Accelerator pedal position D", + 0x4A: "Accelerator pedal position E", + 0x4B: "Accelerator pedal position F", + 0x4C: "Commanded throttle actuator", + 0x4D: "Time run with MIL on", + 0x4E: "Time since trouble codes cleared", + 0x4F: "Maximum value for Fuel–Air equivalence ratio, oxygen sensor voltage, oxygen sensor current, and intake manifold absolute pressure", + 0x50: "Maximum value for air flow rate from mass air flow sensor", + 0x51: "Fuel Type", + 0x52: "Ethanol fuel %", + 0x53: "Absolute Evap system Vapor Pressure", + 0x54: "Evap system vapor pressure", + 0x55: "Short term secondary oxygen sensor trim, A: bank 1, B: bank 3", + 0x56: "Long term secondary oxygen sensor trim, A: bank 1, B: bank 3", + 0x57: "Short term secondary oxygen sensor trim, A: bank 2, B: bank 4", + 0x58: "Long term secondary oxygen sensor trim, A: bank 2, B: bank 4", + 0x59: "Fuel rail absolute pressure", + 0x5A: "Relative accelerator pedal position", + 0x5B: "Hybrid battery pack remaining life", + 0x5C: "Engine oil temperature", + 0x5D: "Fuel injection timing", + 0x5E: "Engine fuel rate", + 0x5F: "Emission requirements to which vehicle is designed", + 0x60: "PIDs supported [$61 - $80]", + 0x61: "Driver's demand engine - percent torque", + 0x62: "Actual engine - percent torque", + 0x63: "Engine reference torque", + 0x64: "Engine percent torque data", + 0x65: "Auxiliary input / output supported", + 0x66: "Mass air flow sensor", + 0x67: "Engine coolant temperature", + 0x68: "Intake air temperature sensor", + 0x69: "Actual EGR, Commanded EGR, and EGR Error", + 0x6A: "Commanded Diesel intake air flow control and relative intake air flow position", + 0x6B: "Exhaust gas recirculation temperature", + 0x6C: "Commanded throttle actuator control and relative throttle position", + 0x6D: "Fuel pressure control system", + 0x6E: "Injection pressure control system", + 0x6F: "Turbocharger compressor inlet pressure", + 0x70: "Boost pressure control", + 0x71: "Variable Geometry turbo (VGT) control", + 0x72: "Wastegate control", + 0x73: "Exhaust pressure", + 0x74: "Turbocharger RPM", + 0x75: "Turbocharger temperature", + 0x76: "Turbocharger temperature", + 0x77: "Charge air cooler temperature (CACT)", + 0x78: "Exhaust Gas temperature (EGT) Bank 1", + 0x79: "Exhaust Gas temperature (EGT) Bank 2", + 0x7A: "Diesel particulate filter (DPF)differential pressure", + 0x7B: "Diesel particulate filter (DPF)", + 0x7C: "Diesel Particulate filter (DPF) temperature", + 0x7D: "NOx NTE (Not-To-Exceed) control area status", + 0x7E: "PM NTE (Not-To-Exceed) control area status", + 0x7F: "Engine run time [b]", + 0x80: "PIDs supported [$81 - $A0]", + 0x81: "Engine run time for Auxiliary Emissions Control Device(AECD)", + 0x82: "Engine run time for Auxiliary Emissions Control Device(AECD)", + 0x83: "NOx sensor", + 0x84: "Manifold surface temperature", + 0x85: "NOx reagent system", + 0x86: "Particulate matter (PM) sensor", + 0x87: "Intake manifold absolute pressure", + 0x88: "SCR Induce System", + 0x89: "Run Time for AECD #11-#15", + 0x8A: "Run Time for AECD #16-#20", + 0x8B: "Diesel Aftertreatment", + 0x8C: "O2 Sensor (Wide Range)", + 0x8D: "Throttle Position G", + 0x8E: "Engine Friction - Percent Torque", + 0x8F: "PM Sensor Bank 1 & 2", + 0x90: "WWH-OBD Vehicle OBD System Information", + 0x91: "WWH-OBD Vehicle OBD System Information", + 0x92: "Fuel System Control", + 0x93: "WWH-OBD Vehicle OBD Counters support", + 0x94: "NOx Warning And Inducement System", + 0x98: "Exhaust Gas Temperature Sensor", + 0x99: "Exhaust Gas Temperature Sensor", + 0x9A: "Hybrid/EV Vehicle System Data, Battery, Voltage", + 0x9B: "Diesel Exhaust Fluid Sensor Data", + 0x9C: "O2 Sensor Data", + 0x9D: "Engine Fuel Rate", + 0x9E: "Engine Exhaust Flow Rate", + 0x9F: "Fuel System Percentage Use", + 0xA0: "PIDs supported [$A1 - $C0]", + 0xA1: "NOx Sensor Corrected Data", + 0xA2: "Cylinder Fuel Rate", + 0xA3: "Evap System Vapor Pressure", + 0xA4: "Transmission Actual Gear", + 0xA5: "Commanded Diesel Exhaust Fluid Dosing", + 0xA6: "Odometer [c]", + 0xA7: "NOx Sensor Concentration Sensors 3 and 4", + 0xA8: "NOx Sensor Corrected Concentration Sensors 3 and 4", + 0xA9: "ABS Disable Switch State", + 0xC0: "PIDs supported [$C1 - $E0]", + 0xC3: "Fuel Level Input A/B", + 0xC4: "Exhaust Particulate Control System Diagnostic Time/Count", + 0xC5: "Fuel Pressure A and B", + 0xC6: "Multiple system counters", + 0xC7: "Distance Since Reflash or Module Replacement", + 0xC8: "NOx Control Diagnostic (NCD) and Particulate Control Diagnostic (PCD) Warning Lamp status", + }, +} + +type OBD2PID struct { + ID uint16 + Name string +} + +func (p OBD2PID) String() string { + if p.Name != "" { + return p.Name + } + return fmt.Sprintf("pid 0x%d", p.ID) +} + +func lookupPID(svcID uint8, data []uint8) OBD2PID { + if len(data) == 1 { + data = []byte{ + 0x00, + data[0], + } + } + + pid := OBD2PID{ + ID: binary.BigEndian.Uint16(data), + } + + // resolve service + if svc, found := servicePIDS[svcID]; found { + // resolve PID name + if name, found := svc[pid.ID]; found { + pid.Name = name + } + } + + return pid +} + +func (msg *OBD2Message) ParseRequest(frame can.Frame) bool { + svcID := frame.Data[1] + // validate service / mode + if svcID > 0x0a { + return false + } + + msgSize := frame.Data[0] + // validate data size + if msgSize > 6 { + return false + } + + data := frame.Data[2 : 1+msgSize] + + msg.PID = lookupPID(svcID, data) + msg.Type = OBD2MessageTypeRequest + msg.ECU = 0xff // broadcast + msg.Size = msgSize - 1 + msg.Service = OBD2Service(svcID) + msg.Data = data + + return true +} diff --git a/modules/can/can_obd2_pid_response.go b/modules/can/can_obd2_pid_response.go new file mode 100644 index 00000000..ec28dd8d --- /dev/null +++ b/modules/can/can_obd2_pid_response.go @@ -0,0 +1,25 @@ +package can + +import ( + "go.einride.tech/can" +) + +func (msg *OBD2Message) ParseResponse(frame can.Frame) bool { + msgSize := frame.Data[0] + // validate data size + if msgSize > 7 { + // fmt.Printf("invalid response size %d\n", msgSize) + return false + } + + svcID := frame.Data[1] - 0x40 + + msg.Type = OBD2MessageTypeResponse + msg.ECU = uint8(uint16(frame.ID) - uint16(OBD2ECUResponseMinID)) + msg.Size = msgSize - 3 + msg.Service = OBD2Service(svcID) + msg.PID = lookupPID(svcID, []uint8{frame.Data[2]}) + msg.Data = frame.Data[3 : 3+msg.Size] + + return true +} diff --git a/modules/can/can_recon.go b/modules/can/can_recon.go index ec556818..21f38f97 100644 --- a/modules/can/can_recon.go +++ b/modules/can/can_recon.go @@ -3,7 +3,6 @@ package can import ( "errors" - "github.com/bettercap/bettercap/v2/network" "github.com/bettercap/bettercap/v2/session" "github.com/evilsocket/islazy/tui" "github.com/hashicorp/go-bexpr" @@ -11,15 +10,9 @@ import ( "go.einride.tech/can/pkg/socketcan" ) -type Message struct { - Frame can.Frame - Name string - Source *network.CANDevice - Signals map[string]string -} - func (mod *CANModule) Configure() error { var err error + var parseOBD bool if mod.Running() { return session.ErrAlreadyStarted(mod.Name()) @@ -29,6 +22,8 @@ func (mod *CANModule) Configure() error { return err } else if err, mod.dumpInject = mod.BoolParam("can.dump.inject"); err != nil { return err + } else if err, parseOBD = mod.BoolParam("can.parse.obd2"); err != nil { + return err } else if err, mod.transport = mod.StringParam("can.transport"); err != nil { return err } else if mod.transport != "can" && mod.transport != "udp" { @@ -37,6 +32,8 @@ func (mod *CANModule) Configure() error { return err } + mod.obd2.Enable(parseOBD) + if mod.filter != "" { if mod.filterExpr, err = bexpr.CreateEvaluator(mod.filter); err != nil { return err @@ -77,12 +74,13 @@ func (mod *CANModule) isFilteredOut(frame can.Frame, msg Message) bool { } func (mod *CANModule) onFrame(frame can.Frame) { - msg := Message{ - Frame: frame, - } + msg := NewCanMessage(frame) // try to parse with DBC if we have any - mod.dbc.Parse(mod, frame, &msg) + if !mod.dbc.Parse(mod, &msg) { + // not parsed, if enabled try ODB2 + mod.obd2.Parse(mod, &msg) + } if !mod.isFilteredOut(frame, msg) { mod.Session.Events.Add("can.message", msg) diff --git a/modules/events_stream/events_view_can.go b/modules/events_stream/events_view_can.go index d39694ff..9fbea6ec 100644 --- a/modules/events_stream/events_view_can.go +++ b/modules/events_stream/events_view_can.go @@ -13,37 +13,84 @@ import ( "github.com/evilsocket/islazy/tui" ) -func (mod *EventsStream) viewCANEvent(output io.Writer, e session.Event) { - if e.Tag == "can.device.new" { - dev := e.Data.(*network.CANDevice) - fmt.Fprintf(output, "[%s] [%s] new CAN device %s (%s) detected.\n", +func (mod *EventsStream) viewCANDeviceNew(output io.Writer, e session.Event) { + dev := e.Data.(*network.CANDevice) + fmt.Fprintf(output, "[%s] [%s] new CAN device %s (%s) detected.\n", + e.Time.Format(mod.timeFormat), + tui.Green(e.Tag), + tui.Bold(dev.Name), + tui.Dim(dev.Description)) +} + +func (mod *EventsStream) viewCANRawMessage(output io.Writer, e session.Event) { + msg := e.Data.(can.Message) + + fmt.Fprintf(output, "[%s] [%s] %s <0x%x> (%s): %s\n", + e.Time.Format(mod.timeFormat), + tui.Green(e.Tag), + tui.Dim("raw"), + msg.Frame.ID, + tui.Dim(humanize.Bytes(uint64(msg.Frame.Length))), + hex.EncodeToString(msg.Frame.Data[:msg.Frame.Length])) +} + +func (mod *EventsStream) viewCANDBCMessage(output io.Writer, e session.Event) { + msg := e.Data.(can.Message) + src := "" + if msg.Source != nil && msg.Source.Name != "" { + src = fmt.Sprintf(" from %s", msg.Source.Name) + } + + fmt.Fprintf(output, "[%s] [%s] (dbc) <0x%x> %s (%s)%s:\n", + e.Time.Format(mod.timeFormat), + tui.Green(e.Tag), + msg.Frame.ID, + msg.Name, + tui.Dim(humanize.Bytes(uint64(msg.Frame.Length))), + tui.Bold(src)) + + for name, value := range msg.Signals { + fmt.Fprintf(output, " %s : %s\n", name, value) + } +} + +func (mod *EventsStream) viewCANOBDMessage(output io.Writer, e session.Event) { + msg := e.Data.(can.Message) + obd2 := msg.OBD2 + + if obd2.Type == can.OBD2MessageTypeRequest { + fmt.Fprintf(output, "[%s] [%s] %s : %s > %s\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), - tui.Bold(dev.Name), - tui.Dim(dev.Description)) + tui.Yellow("obd2.request"), + obd2.Service, obd2.PID) + } else { + + fmt.Fprintf(output, "[%s] [%s] %s : %s > %s > %s : 0x%x\n", + e.Time.Format(mod.timeFormat), + tui.Green(e.Tag), + tui.Yellow("obd2.response"), + tui.Bold(msg.Source.Name), + obd2.Service, obd2.PID, + obd2.Data) + } + +} + +func (mod *EventsStream) viewCANEvent(output io.Writer, e session.Event) { + if e.Tag == "can.device.new" { + mod.viewCANDeviceNew(output, e) } else if e.Tag == "can.message" { msg := e.Data.(can.Message) - - // unparsed - if msg.Name == "" { - fmt.Fprintf(output, "[%s] [%s] (%s): %s\n", - e.Time.Format(mod.timeFormat), - tui.Green(e.Tag), - msg.Frame.ID, - tui.Dim(humanize.Bytes(uint64(msg.Frame.Length))), - hex.EncodeToString(msg.Frame.Data[:msg.Frame.Length])) + if msg.OBD2 != nil { + // OBD-2 PID + mod.viewCANOBDMessage(output, e) + } else if msg.Name != "" { + // parsed from DBC + mod.viewCANDBCMessage(output, e) } else { - fmt.Fprintf(output, "[%s] [%s] %s (%s) from %s:\n", - e.Time.Format(mod.timeFormat), - tui.Green(e.Tag), - msg.Frame.ID, - msg.Name, - tui.Dim(humanize.Bytes(uint64(msg.Frame.Length))), - tui.Bold(msg.Source.Name)) - - for name, value := range msg.Signals { - fmt.Fprintf(output, " %s : %s\n", name, value) - } + // raw unparsed frame + mod.viewCANRawMessage(output, e) } } else { fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) diff --git a/modules/ui/ui b/modules/ui/ui index 6e126c47..c671b0be 160000 --- a/modules/ui/ui +++ b/modules/ui/ui @@ -1 +1 @@ -Subproject commit 6e126c470e97542d724927ba975011244127dbb1 +Subproject commit c671b0be70074e918788fe9f9fd19a5d35bf79dc