mirror of
https://github.com/bettercap/bettercap
synced 2025-07-16 10:03:39 -07:00
new: implemented can.obd2 builtin parser
This commit is contained in:
parent
cf6fba6151
commit
c3999d6bb5
11 changed files with 520 additions and 52 deletions
|
@ -20,6 +20,7 @@ type CANModule struct {
|
||||||
filter string
|
filter string
|
||||||
filterExpr *bexpr.Evaluator
|
filterExpr *bexpr.Evaluator
|
||||||
dbc *DBC
|
dbc *DBC
|
||||||
|
obd2 *OBD2
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
recv *socketcan.Receiver
|
recv *socketcan.Receiver
|
||||||
send *socketcan.Transmitter
|
send *socketcan.Transmitter
|
||||||
|
@ -30,6 +31,7 @@ func NewCanModule(s *session.Session) *CANModule {
|
||||||
SessionModule: session.NewSessionModule("can", s),
|
SessionModule: session.NewSessionModule("can", s),
|
||||||
filter: "",
|
filter: "",
|
||||||
dbc: &DBC{},
|
dbc: &DBC{},
|
||||||
|
obd2: &OBD2{},
|
||||||
filterExpr: nil,
|
filterExpr: nil,
|
||||||
transport: "can",
|
transport: "can",
|
||||||
deviceName: "can0",
|
deviceName: "can0",
|
||||||
|
@ -61,6 +63,10 @@ func NewCanModule(s *session.Session) *CANModule {
|
||||||
"",
|
"",
|
||||||
"Optional boolean expression to select frames to report."))
|
"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", "",
|
mod.AddHandler(session.NewModuleHandler("can.recon on", "",
|
||||||
"Start CAN-bus discovery.",
|
"Start CAN-bus discovery.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/evilsocket/islazy/str"
|
"github.com/evilsocket/islazy/str"
|
||||||
"go.einride.tech/can"
|
|
||||||
"go.einride.tech/can/pkg/descriptor"
|
"go.einride.tech/can/pkg/descriptor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,7 +53,7 @@ func (dbc *DBC) LoadData(mod *CANModule, name string, input []byte) error {
|
||||||
return nil
|
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()
|
dbc.RLock()
|
||||||
defer dbc.RUnlock()
|
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 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
|
msg.Name = message.Name
|
||||||
|
|
||||||
// find source full info in DBC nodes
|
// 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
|
// add CAN source if new
|
||||||
_, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, frame.Data[:])
|
_, msg.Source = mod.Session.CAN.AddIfNew(sourceName, sourceDesc, msg.Frame.Data[:])
|
||||||
|
|
||||||
msg.Signals = make(map[string]string)
|
|
||||||
|
|
||||||
// parse signals
|
// parse signals
|
||||||
for _, signal := range message.Signals {
|
for _, signal := range message.Signals {
|
||||||
var value string
|
var value string
|
||||||
|
|
||||||
if signal.Length <= 32 && signal.IsFloat {
|
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 {
|
} 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 {
|
} else if signal.IsSigned {
|
||||||
value = fmt.Sprintf("%d", signal.UnmarshalSigned(frame.Data))
|
value = fmt.Sprintf("%d", signal.UnmarshalSigned(msg.Frame.Data))
|
||||||
} else {
|
} 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))
|
msg.Signals[signal.Name] = str.Trim(fmt.Sprintf("%s %s", value, signal.Unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,8 +55,7 @@ func (mod *CANModule) startDumpReader() error {
|
||||||
|
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := str.Trim(scanner.Text())
|
if line := str.Trim(scanner.Text()); line != "" {
|
||||||
if line != "" {
|
|
||||||
if m := dumpLineParser.FindStringSubmatch(line); len(m) != 4 {
|
if m := dumpLineParser.FindStringSubmatch(line); len(m) != 4 {
|
||||||
mod.Warning("unexpected line: '%s' -> %d matches", line, len(m))
|
mod.Warning("unexpected line: '%s' -> %d matches", line, len(m))
|
||||||
} else if timeval, err := parseTimeval(m[1]); err != nil {
|
} else if timeval, err := parseTimeval(m[1]); err != nil {
|
||||||
|
|
24
modules/can/can_message.go
Normal file
24
modules/can/can_message.go
Normal file
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
54
modules/can/can_obd2.go
Normal file
54
modules/can/can_obd2.go
Normal file
|
@ -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
|
||||||
|
}
|
72
modules/can/can_obd2_message.go
Normal file
72
modules/can/can_obd2_message.go
Normal file
|
@ -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
|
||||||
|
}
|
247
modules/can/can_obd2_pid_request.go
Normal file
247
modules/can/can_obd2_pid_request.go
Normal file
|
@ -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
|
||||||
|
}
|
25
modules/can/can_obd2_pid_response.go
Normal file
25
modules/can/can_obd2_pid_response.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package can
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/bettercap/bettercap/v2/network"
|
|
||||||
"github.com/bettercap/bettercap/v2/session"
|
"github.com/bettercap/bettercap/v2/session"
|
||||||
"github.com/evilsocket/islazy/tui"
|
"github.com/evilsocket/islazy/tui"
|
||||||
"github.com/hashicorp/go-bexpr"
|
"github.com/hashicorp/go-bexpr"
|
||||||
|
@ -11,15 +10,9 @@ import (
|
||||||
"go.einride.tech/can/pkg/socketcan"
|
"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 {
|
func (mod *CANModule) Configure() error {
|
||||||
var err error
|
var err error
|
||||||
|
var parseOBD bool
|
||||||
|
|
||||||
if mod.Running() {
|
if mod.Running() {
|
||||||
return session.ErrAlreadyStarted(mod.Name())
|
return session.ErrAlreadyStarted(mod.Name())
|
||||||
|
@ -29,6 +22,8 @@ func (mod *CANModule) Configure() error {
|
||||||
return err
|
return err
|
||||||
} else if err, mod.dumpInject = mod.BoolParam("can.dump.inject"); err != nil {
|
} else if err, mod.dumpInject = mod.BoolParam("can.dump.inject"); err != nil {
|
||||||
return err
|
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 {
|
} else if err, mod.transport = mod.StringParam("can.transport"); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if mod.transport != "can" && mod.transport != "udp" {
|
} else if mod.transport != "can" && mod.transport != "udp" {
|
||||||
|
@ -37,6 +32,8 @@ func (mod *CANModule) Configure() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod.obd2.Enable(parseOBD)
|
||||||
|
|
||||||
if mod.filter != "" {
|
if mod.filter != "" {
|
||||||
if mod.filterExpr, err = bexpr.CreateEvaluator(mod.filter); err != nil {
|
if mod.filterExpr, err = bexpr.CreateEvaluator(mod.filter); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -77,12 +74,13 @@ func (mod *CANModule) isFilteredOut(frame can.Frame, msg Message) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mod *CANModule) onFrame(frame can.Frame) {
|
func (mod *CANModule) onFrame(frame can.Frame) {
|
||||||
msg := Message{
|
msg := NewCanMessage(frame)
|
||||||
Frame: frame,
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to parse with DBC if we have any
|
// 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) {
|
if !mod.isFilteredOut(frame, msg) {
|
||||||
mod.Session.Events.Add("can.message", msg)
|
mod.Session.Events.Add("can.message", msg)
|
||||||
|
|
|
@ -13,37 +13,84 @@ import (
|
||||||
"github.com/evilsocket/islazy/tui"
|
"github.com/evilsocket/islazy/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mod *EventsStream) viewCANEvent(output io.Writer, e session.Event) {
|
func (mod *EventsStream) viewCANDeviceNew(output io.Writer, e session.Event) {
|
||||||
if e.Tag == "can.device.new" {
|
dev := e.Data.(*network.CANDevice)
|
||||||
dev := e.Data.(*network.CANDevice)
|
fmt.Fprintf(output, "[%s] [%s] new CAN device %s (%s) detected.\n",
|
||||||
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),
|
e.Time.Format(mod.timeFormat),
|
||||||
tui.Green(e.Tag),
|
tui.Green(e.Tag),
|
||||||
tui.Bold(dev.Name),
|
tui.Yellow("obd2.request"),
|
||||||
tui.Dim(dev.Description))
|
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" {
|
} else if e.Tag == "can.message" {
|
||||||
msg := e.Data.(can.Message)
|
msg := e.Data.(can.Message)
|
||||||
|
if msg.OBD2 != nil {
|
||||||
// unparsed
|
// OBD-2 PID
|
||||||
if msg.Name == "" {
|
mod.viewCANOBDMessage(output, e)
|
||||||
fmt.Fprintf(output, "[%s] [%s] <id %d> (%s): %s\n",
|
} else if msg.Name != "" {
|
||||||
e.Time.Format(mod.timeFormat),
|
// parsed from DBC
|
||||||
tui.Green(e.Tag),
|
mod.viewCANDBCMessage(output, e)
|
||||||
msg.Frame.ID,
|
|
||||||
tui.Dim(humanize.Bytes(uint64(msg.Frame.Length))),
|
|
||||||
hex.EncodeToString(msg.Frame.Data[:msg.Frame.Length]))
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(output, "[%s] [%s] <id %d> %s (%s) from %s:\n",
|
// raw unparsed frame
|
||||||
e.Time.Format(mod.timeFormat),
|
mod.viewCANRawMessage(output, e)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e)
|
fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6e126c470e97542d724927ba975011244127dbb1
|
Subproject commit c671b0be70074e918788fe9f9fd19a5d35bf79dc
|
Loading…
Add table
Add a link
Reference in a new issue