From 838e5c2088710d0011f7933f85b17f56d456bb5a Mon Sep 17 00:00:00 2001 From: HalvedIncrease Date: Mon, 13 Oct 2025 23:13:30 -0400 Subject: [PATCH 01/34] daq: basic boilerplate setup untested --- scripts/daq/.gitignore | 1 + scripts/daq/README.md | 12 ++ scripts/daq/dbc/veh.dbc | 402 ++++++++++++++++++++++++++++++++++++++++ scripts/daq/go.mod | 25 +++ scripts/daq/go.sum | 56 ++++++ scripts/daq/handler.go | 52 ++++++ scripts/daq/main.go | 26 +++ 7 files changed, 574 insertions(+) create mode 100644 scripts/daq/.gitignore create mode 100644 scripts/daq/README.md create mode 100644 scripts/daq/dbc/veh.dbc create mode 100644 scripts/daq/go.mod create mode 100644 scripts/daq/go.sum create mode 100644 scripts/daq/handler.go create mode 100644 scripts/daq/main.go diff --git a/scripts/daq/.gitignore b/scripts/daq/.gitignore new file mode 100644 index 000000000..9ab870da8 --- /dev/null +++ b/scripts/daq/.gitignore @@ -0,0 +1 @@ +generated/ diff --git a/scripts/daq/README.md b/scripts/daq/README.md new file mode 100644 index 000000000..fa674185c --- /dev/null +++ b/scripts/daq/README.md @@ -0,0 +1,12 @@ +This is just a temporary working directory for daq... + +Generate Go DBC bindings: +``` +go get -u go.einride.tech/can +cd dbc +go run go.einride.tech/can/cmd/cantool generate ./ ../generated/ +``` + +Had to remove the "Reset" message from DashCommand to avoid name conflicts with bindings +> TODO: either find a better Go DBC binding generator or patch the upstream DBC to +> avoid having messages named "Reset" \ No newline at end of file diff --git a/scripts/daq/dbc/veh.dbc b/scripts/daq/dbc/veh.dbc new file mode 100644 index 000000000..b0a9d44c2 --- /dev/null +++ b/scripts/daq/dbc/veh.dbc @@ -0,0 +1,402 @@ +VERSION "" + + +NS_ : + NS_DESC_ + CM_ + BA_DEF_ + BA_ + VAL_ + CAT_DEF_ + CAT_ + FILTER + BA_DEF_DEF_ + EV_DATA_ + ENVVAR_DATA_ + SGTYPE_ + SGTYPE_VAL_ + BA_DEF_SGTYPE_ + BA_SGTYPE_ + SIG_TYPE_REF_ + VAL_TABLE_ + SIG_GROUP_ + SIG_VALTYPE_ + SIGTYPE_VALTYPE_ + BO_TX_BU_ + BA_DEF_REL_ + BA_REL_ + BA_DEF_DEF_REL_ + BU_SG_REL_ + BU_EV_REL_ + BU_BO_REL_ + SG_MUL_VAL_ + +BS_: + +BU_: FC LVC TMS DASH RPI IMD BMS GPS + +CM_ "ID Scheme: +Replace x with the pertinent ECU (not necessarily the sender) +x=0 Front Controller +x=1 LV Controller +x=2 TMS +x=3 Dashboard +x=4 RPI + +Increment n as needed"; + +CM_ "1xn - Irregular, high importance commands"; + +BO_ 140 InitiateCanFlash: 1 RPI + SG_ ECU : 0|8@1+ (1,0) [0|2] "" FC, LVC, TMS + +VAL_ 140 ECU 0 "FrontController" 1 "LvController" 2 "TMS"; + +CM_ "Periodic Messages +2x0 - ECUx Commands +2x1 - ECUx Statuses +2x2 - ECUx Alerts + +Each 2x1 status should contain an 8-bit counter field which increments +on each transmission to show that the ECU is alive."; + +BO_ 201 FcStatus: 8 FC + SG_ Counter : 0|8@1+ (1,0) [0|255] "" RPI + SG_ State : 8|8@1+ (1,0) [0|255] "" RPI + SG_ AccumulatorState : 16|8@1+ (1,0) [0|255] "" RPI + SG_ MotorState : 24|8@1+ (1,0) [0|255] "" RPI + SG_ Inv1State : 32|4@1+ (1,0) [0|255] "" RPI + SG_ Inv2State : 36|4@1+ (1,0) [0|255] "" RPI + SG_ DbcValid : 40|1@1+ (1,0) [0|1] "" RPI + SG_ Inv1Starter : 48|4@1+ (1,0) [0|255] "" RPI + SG_ Inv2Starter : 52|4@1+ (1,0) [0|255] "" RPI + +VAL_ 201 State 0 "START_DASHBOARD" 2 "WAIT_DRIVER_SELECT" 3 "WAIT_START_HV" 4 "STARTING_HV" 5 "WAIT_START_MOTOR" 6 "STARTING_MOTORS" 7 "STARTUP_SEND_READY_TO_DRIVE" 8 "RUNNING" 9 "SHUTDOWN" 10 "ERROR"; +VAL_ 201 MotorState 0 "IDLE" 1 "STARTING" 2 "SWITCHING_INVERTER_ON" 3 "RUNNING" 4 "ERROR"; +VAL_ 201 AccumulatorState 0 "IDLE" 1 "STARTUP_ENSURE_OPEN" 2 "STARTUP_CLOSE_NEG" 3 "STARTUP_HOLD_CLOSE_NEG" 4 "STARTUP_CLOSE_PRECHARGE" 5 "STARTUP_HOLD_CLOSE_PRECHARGE" 6 "STARTUP_CLOSE_POS" 7 "STARTUP_HOLD_CLOSE_POS" 8 "STARTUP_OPEN_PRECHARGE" 9 "RUNNING" 10 "SHUTDOWN" 11 "ERROR"; +VAL_ 201 Inv1State 0 "OFF" 1 "SYSTEM_READY" 2 "STARTUP_bDCON" 3 "STARTUP_bENABLE" 4 "STARTUP_bINVERTER" 5 "STARTUP_X140" 6 "RUNNING" 7 "ERROR" 8 "ERROR_RESET"; +VAL_ 201 Inv2State 0 "OFF" 1 "SYSTEM_READY" 2 "STARTUP_bDCON" 3 "STARTUP_bENABLE" 4 "STARTUP_bINVERTER" 5 "STARTUP_X140" 6 "RUNNING" 7 "ERROR" 8 "ERROR_RESET"; + +BO_ 202 FcAlerts: 2 FC + SG_ AppsImplausible : 0|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ AccumulatorLowSoc : 1|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ AccumulatorContactorWrongState : 2|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ MotorRetriesExceeded : 3|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ LeftMotorStartingError : 4|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ RightMotorStartingError : 5|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ LeftMotorRunningError : 6|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ RightMotorRunningError : 7|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ DashboardBootTimeout : 8|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ CanTxError : 9|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ EV47Active : 10|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ NoInv1Can : 11|1@1+ (1,0) [0|1] "" RPI, DASH + SG_ NoInv2Can : 12|1@1+ (1,0) [0|1] "" RPI, DASH + +BO_ 203 FcCounters: 8 FC + SG_ Motor : 0|8@1+ (1,0) [0|1] "" RPI, DASH + SG_ Amk1: 8|8@1+ (1,0) [0|1] "" RPI, DASH + SG_ Amk2 : 16|8@1+ (1,0) [0|1] "" RPI, DASH + SG_ Starter1 : 24|8@1+ (1,0) [0|1] "" RPI, DASH + SG_ Starter2 : 32|8@1+ (1,0) [0|1] "" RPI, DASH + +BO_ 210 LvCommand: 1 FC + SG_ BrakeLightEnable : 1|1@1+ (1,0) [0|1] "" LVC + +BO_ 213 InverterSwitchCommand: 1 FC + SG_ CloseInverterSwitch : 0|1@1+ (1,0) [0|1] "" LVC + +CM_ BO_ 213 "This should be combined with LvCommand once testing is done."; + +BO_ 211 LvStatus: 4 LVC + SG_ Counter : 0|8@1+ (1,0) [0|255] "" FC + SG_ LvState : 8|8@1+ (1,0) [0|1] "" FC + SG_ MotorControllerState : 16|8@1+ (1,0) [0|1] "" FC + SG_ MotorControllerSwitchClosed : 24|1@1+ (1,0) [0|1] "" FC + SG_ ImdFault : 25|1@1+ (1,0) [0|1] "" FC + SG_ BmsFault : 26|1@1+ (1,0) [0|1] "" FC + +VAL_ 211 LvState 0 "PWRUP_START" 1 "PWRUP_TSSI_ON" 2 "PWRUP_PERIPHERALS_ON" 3 "PWRUP_ACCUMULATOR_ON" 4 "PWRUP_MOTOR_CONTROLLER_PRECHARGING" 7 "PWRUP_SHUTDOWN_ON" 9 "DCDC_ON" 10 "POWERTRAIN_PUMP_ON" 11 "POWERTRAIN_FAN_ON" 13 "READY_TO_DRIVE" 14 "SHUTDOWN_DRIVER_WARNING" 15 "SHUTDOWN_PUMP_OFF" 16 "SHUTDOWN_FAN_OFF" 17 "SHUTDOWN_COMPLETE"; +VAL_ 211 MotorControllerState 0 "OFF" 1 "PRECHARGING" 2 "PRECHARGING_HANDOFF" 3 "ON"; + +BO_ 230 DashCommand: 5 FC + SG_ ConfigReceived : 0|1@1+ (1,0) [0|1] "" DASH + SG_ HvStarted : 1|1@1+ (1,0) [0|1] "" DASH + SG_ MotorStarted : 2|1@1+ (1,0) [0|1] "" DASH + SG_ DriveStarted : 3|1@1+ (1,0) [0|1] "" DASH + SG_ Errored : 5|1@1+ (1,0) [0|1] "" DASH + SG_ HvPrechargePercent : 8|8@1+ (1,0) [0|100] "" DASH + SG_ Speed : 16|12@1+ (0.1,0) [0|100] "mph" DASH + SG_ HvSocPercent : 32|8@1+ (1,0) [0|100] "" DASH + +BO_ 231 DashStatus: 3 DASH + SG_ Counter : 0|8@1+ (1,0) [0|255] "" FC + SG_ State : 8|8@1+ (1,0) [0|1] "" FC + SG_ Profile : 16|8@1+ (1,0) [0|15] "" FC + +VAL_ 231 State 0 "LOGO" 1 "SELECT_PROFILE" 2 "CONFIRM_SELECTION" 3 "WAIT_SELECTION_ACK" 4 "PRESS_FOR_HV" 5 "STARTING_HV" 6 "PRESS_FOR_MOTOR" 7 "STARTING_MOTORS" 8 "BRAKE_TO_START" 9 "RUNNING" 10 "SHUTDOWN" 11 "ERROR"; +VAL_ 231 Profile 0 "Default" 1 "Launch" 2 "Skidpad" 3 "Endurance" 4 "Tuning" 5 "_ENUM_TAIL_"; + +BO_ 300 Accumulator_Soc: 8 FC + SG_ PackVoltage : 0|16@1+ (1,0) [0|255] "" DASH + SG_ PrechargeVoltage : 16|16@1+ (1,0) [0|255] "" DASH + SG_ MaxPackVoltage : 32|16@1+ (1,0) [0|255] "" DASH + SG_ SocPercent : 48|8@1+ (1,0) [0|255] "" DASH + SG_ PrechargePercent : 56|8@1+ (1,0) [0|255] "" DASH + +CM_ "3xn - Additional sensor readings"; + +BO_ 310 SuspensionTravel34: 2 LVC + SG_ STP3 : 0|8@1+ (1,0) [0|255] "" FC + SG_ STP4 : 8|8@1+ (1,0) [0|255] "" FC + +BO_ 340 TuningParams: 8 RPI + SG_ aggressiveness : 0|8@1+ (1,0) [0|100] "" FC + +CM_ "4xn - General / debugging info"; + +BO_ 400 FcGitHash: 5 FC + SG_ Commit : 0|32@1+ (1,0) [0|0] "" RPI + SG_ Dirty : 32|1@1+ (1,0) [0|0] "" RPI + +BO_ 410 LvGitHash: 5 LVC + SG_ Commit : 0|32@1+ (1,0) [0|0] "" RPI + SG_ Dirty : 32|1@1+ (1,0) [0|0] "" RPI + +BO_ 420 TmsGitHash: 5 LVC + SG_ Commit : 0|32@1+ (1,0) [0|0] "" RPI + SG_ Dirty : 32|1@1+ (1,0) [0|0] "" RPI + +BO_ 430 DashGitHash: 5 DASH + SG_ Commit : 0|32@1+ (1,0) [0|0] "" RPI + SG_ Dirty : 32|1@1+ (1,0) [0|0] "" RPI + +BO_ 401 AppsDebug: 8 FC + SG_ Apps1RawVolt : 0|16@1+ (0.001,0) [0|0] "volt" RPI + SG_ Apps2RawVolt : 16|16@1+ (0.001,0) [0|0] "volt" RPI + SG_ Apps1Percent : 32|16@1+ (0.1,0) [0|0] "percent" RPI + SG_ Apps2Percent : 48|16@1+ (0.1,0) [0|0] "percent" RPI + +BO_ 402 BppsSteerDebug: 8 FC + SG_ BppsRawVolt : 0|16@1+ (0.001,0) [0|0] "volt" RPI + SG_ SteerRawVolt : 16|16@1+ (0.001,0) [0|0] "volt" RPI + SG_ BppsPercent : 32|16@1+ (0.1,0) [0|0] "percent" RPI + SG_ SteerPosition : 48|16@1+ (0.01,-1) [0|0] "[-1,+1]" RPI + +BO_ 411 LvDbcHash: 8 LVC + SG_ Hash : 0|64@1+ (1,0) [0|1] "" FC + +CM_ "Manufacturer specific IDs (do not modify) +55 IMD +769-777 GPS/IMU +1570-1574 BMS Command/Status +2553934720 BMS Temperatures 1 +2566844926 BMS Temperatures 2"; + +BO_ 55 IMD_Info_General: 8 IMD + SG_ rIsoCorrected : 0|16@1+ (1,0) [0|0] "" RPI + SG_ rIsoStatus : 16|8@1+ (1,0) [0|0] "" RPI + SG_ MeasurementCounter : 24|8@1+ (1,0) [0|0] "" RPI + SG_ WarningsAlarms : 32|16@1+ (1,0) [0|0] "" RPI + SG_ DeviceActivity : 48|8@1+ (1,0) [0|0] "" RPI + SG_ Reserved : 56|8@1+ (1,0) [0|0] "" RPI + +BO_ 56 IMD_InfoIsolationDetail: 8 IMD + SG_ rIsoNeg : 0|16@1+ (1,0) [0|0] "" RPI + SG_ rIsoPos : 16|16@1+ (1,0) [0|0] "" RPI + SG_ rIsoOriginal : 32|16@1+ (1,0) [0|0] "" RPI + SG_ isolationMeasurementCounter : 48|8@1+ (1,0) [0|0] "" RPI + SG_ isolationQuality : 56|8@1+ (1,0) [0|0] "" RPI + +BO_ 57 IMD_Info_Voltage: 8 IMD + SG_ hvSystem : 0|16@1+ (1,0) [0|0] "" RPI + SG_ hvNegToEarth : 16|16@1+ (1,0) [0|0] "" RPI + SG_ hvPosToEarth : 32|16@1+ (1,0) [0|0] "" RPI + SG_ voltageMeasurementCounter : 48|8@1+ (1,0) [0|0] "" RPI + SG_ Reserved : 56|8@1+ (1,0) [0|0] "" RPI + +BO_ 58 IMD_InfoItSystem: 8 IMD + SG_ capacityMeasuredValue : 0|16@1+ (1,0) [0|0] "" RPI + SG_ capacityMeasurementCounter : 16|8@1+ (1,0) [0|0] "" RPI + SG_ unbalanceMeasuredValue : 24|8@1+ (1,0) [0|0] "" RPI + SG_ unbalanceMeasurement : 32|8@1+ (1,0) [0|0] "" RPI + SG_ voltageMeasuredFrequency : 40|16@1+ (1,0) [0|0] "" RPI + SG_ Reserved : 56|8@1+ (1,0) [0|0] "" RPI + +BO_ 34 IMD_Request: 8 RPI + SG_ index : 0|8@1+ (1,0) [0|0] "" IMD + SG_ data0 : 8|8@1+ (1,0) [0|0] "" IMD + SG_ data1 : 16|8@1+ (1,0) [0|0] "" IMD + SG_ data2 : 24|8@1+ (1,0) [0|0] "" IMD + SG_ data3 : 32|8@1+ (1,0) [0|0] "" IMD + SG_ data4 : 40|8@1+ (1,0) [0|0] "" IMD + SG_ data5 : 48|8@1+ (1,0) [0|0] "" IMD + SG_ data6 : 56|8@1+ (1,0) [0|0] "" IMD + +BO_ 35 ID_Response: 8 IMD + SG_ index : 0|8@1+ (1,0) [0|0] "" RPI + SG_ d1 : 8|8@1+ (1,0) [0|0] "" RPI + SG_ d2 : 16|8@1+ (1,0) [0|0] "" RPI + SG_ d3 : 24|8@1+ (1,0) [0|0] "" RPI + SG_ d4 : 32|8@1+ (1,0) [0|0] "" RPI + SG_ d5 : 40|8@1+ (1,0) [0|0] "" RPI + SG_ d6 : 48|8@1+ (1,0) [0|0] "" RPI + SG_ d7 : 56|8@1+ (1,0) [0|0] "" RPI + +BO_ 1570 ContactorCommand: 3 FC + SG_ PackPositive : 0|8@1+ (1,0) [0|0] "" BMS + SG_ PackPrecharge : 8|8@1+ (1,0) [0|0] "" BMS + SG_ PackNegative : 16|8@1+ (1,0) [0|0] "" BMS + +BO_ 1572 Pack_State: 7 BMS + SG_ Pack_Current : 0|16@1+ (0.1,0) [0|0] "Amps" FC + SG_ Pack_Inst_Voltage : 16|16@1+ (0.1,0) [0|0] "Volts" FC + SG_ Avg_Cell_Voltage : 32|16@1+ (0.0001,0) [0|0] "Volts" FC + SG_ Populated_Cells : 48|8@1+ (1,0) [0|0] "Num" FC + +BO_ 1571 Pack_Current_Limits: 4 BMS + SG_ Pack_CCL : 0|16@1+ (1,0) [0|0] "Amps" FC + SG_ Pack_DCL : 16|16@1+ (1,0) [0|0] "Amps" FC + +BO_ 1573 Pack_SOC: 3 BMS + SG_ Pack_SOC : 0|8@1+ (0.5,0) [0|0] "Percent" FC + SG_ Maximum_Pack_Voltage : 8|16@1+ (0.1,0) [0|0] "Volts" FC + +BO_ 1574 Contactor_Feedback: 3 BMS + SG_ Pack_Positive_Feedback : 0|1@1+ (1,0) [0|1] "" FC, DASH, LVC + SG_ Pack_Negative_Feedback : 8|1@1+ (1,0) [0|1] "" FC, DASH, LVC + SG_ Pack_Precharge_Feedback : 16|1@1+ (1,0) [0|1] "" FC, DASH, LVC + +BO_ 2553934720 BmsBroadcast: 8 TMS + SG_ ThermModuleNum : 0|8@1+ (1,0) [0|0] "" BMS + SG_ LowThermValue : 8|8@1- (1,0) [0|0] " C" BMS + SG_ HighThermValue : 16|8@1- (1,0) [0|0] " C" BMS + SG_ AvgThermValue : 24|8@1- (1,0) [0|0] " C" BMS + SG_ NumThermEn : 32|8@1+ (1,0) [0|0] "" BMS + SG_ HighThermID : 40|8@1+ (1,0) [0|0] "" BMS + SG_ LowThermID : 48|8@1+ (1,0) [0|0] "" BMS + SG_ Checksum : 56|8@1+ (1,0) [0|0] "" BMS + +BO_ 2566844926 ThermistorBroadcast: 8 TMS + SG_ RelThermID : 0|16@1+ (1,0) [0|0] "" BMS + SG_ ThermValue : 16|8@1- (1,0) [0|0] " C" BMS + SG_ NumEnTherm : 24|8@1- (1,0) [0|0] "" BMS + SG_ LowThermValue : 32|8@1- (1,0) [0|0] " C" BMS + SG_ HighThermValue : 40|8@1- (1,0) [0|0] " C" BMS + SG_ HighThermID : 48|8@1+ (1,0) [0|0] "" BMS + SG_ LowThermID : 56|8@1+ (1,0) [0|0] "" BMS + +BO_ 769 GnssStatus: 1 GPS + SG_ FixType : 0|3@1+ (1,0) [0|5] "" RPI + SG_ Satellites : 3|5@1+ (1,0) [0|31] "" RPI + +BO_ 770 GnssTime: 6 GPS + SG_ TimeValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ TimeConfirmed : 1|1@1+ (1,0) [0|1] "" RPI + SG_ Epoch : 8|40@1+ (0.001,1577840400) [0|0] "sec" RPI + +BO_ 771 GnssPosition: 8 GPS + SG_ PositionValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ Latitude : 1|28@1+ (1E-006,-90) [-90|178.435455] "deg" RPI + SG_ Longitude : 29|29@1+ (1E-006,-180) [-180|356.870911] "deg" RPI + SG_ PositionAccuracy : 58|6@1+ (1,0) [0|63] "m" RPI + +BO_ 772 GnssAltitude: 4 GPS + SG_ AltitudeValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ Altitude : 1|18@1+ (0.1,-6000) [-6000|20000] "m" RPI + SG_ AltitudeAccuracy : 19|13@1+ (1,0) [0|8000] "m" RPI + +BO_ 773 GnssAttitude: 8 GPS + SG_ AttitudeValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ Roll : 1|12@1+ (0.1,-180) [-180|180] "deg" RPI + SG_ RollAccuracy : 13|9@1+ (0.1,0) [0|50] "deg" RPI + SG_ Pitch : 22|12@1+ (0.1,-90) [-90|90] "deg" RPI + SG_ PitchAccuracy : 34|9@1+ (0.1,0) [0|50] "deg" RPI + SG_ Heading : 43|12@1+ (0.1,0) [0|360] "deg" RPI + SG_ HeadingAccuracy : 55|9@1+ (0.1,0) [0|50] "deg" RPI + +BO_ 774 GnssOdo: 8 GPS + SG_ DistanceValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ DistanceTrip : 1|22@1+ (1,0) [0|4194303] "m" RPI + SG_ DistanceAccuracy : 23|19@1+ (1,0) [0|524287] "m" RPI + SG_ DistanceTotal : 42|22@1+ (1,0) [0|4194303] "km" RPI + +BO_ 775 GnssSpeed: 5 GPS + SG_ SpeedValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ Speed : 1|20@1+ (0.001,0) [0|1048.575] "m/s" RPI + SG_ SpeedAccuracy : 21|19@1+ (0.001,0) [0|524.287] "m/s" RPI + +BO_ 776 GnssGeofence: 2 GPS + SG_ FenceValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ FenceCombined : 1|2@1+ (1,0) [0|1] "" RPI + SG_ Fence1 : 8|2@1+ (1,0) [0|1] "" RPI + SG_ Fence2 : 10|2@1+ (1,0) [0|1] "" RPI + SG_ Fence3 : 12|2@1+ (1,0) [0|1] "" RPI + SG_ Fence4 : 14|2@1+ (1,0) [0|1] "" RPI + +BO_ 777 GnssImu: 8 GPS + SG_ ImuValid : 0|1@1+ (1,0) [0|1] "" RPI + SG_ AccelerationX : 1|10@1+ (0.125,-64) [-64|63.875] "m/s^2" RPI + SG_ AccelerationY : 11|10@1+ (0.125,-64) [-64|63.875] "m/s^2" RPI + SG_ AccelerationZ : 21|10@1+ (0.125,-64) [-64|63.875] "m/s^2" RPI + SG_ AngularRateX : 31|11@1+ (0.25,-256) [-256|255.75] "deg/s" RPI + SG_ AngularRateY : 42|11@1+ (0.25,-256) [-256|255.75] "deg/s" RPI + SG_ AngularRateZ : 53|11@1+ (0.25,-256) [-256|255.75] "deg/s" RPI + +CM_ BO_ 1572 "This ID Transmits at 8 ms."; +CM_ BO_ 1571 "This ID Transmits at 8 ms."; +CM_ BO_ 1573 "This ID Transmits at 8 ms."; +CM_ BO_ 1574 "This ID Transmits at 8 ms."; +CM_ BO_ 2553934720 "Thermistor Module - BMS Broadcast"; +CM_ SG_ 2553934720 ThermModuleNum "Thermistor Module Number"; +CM_ BO_ 2566844926 "Thermistor General Broadcast"; +CM_ SG_ 2566844926 RelThermID "Thermistor ID relative to all configured Thermistor Modules"; +CM_ BO_ 769 "GNSS information"; +CM_ SG_ 769 FixType "Fix type"; +CM_ SG_ 769 Satellites "Number of satellites used"; +CM_ BO_ 770 "GNSS time"; +CM_ SG_ 770 TimeValid "Time validity"; +CM_ SG_ 770 TimeConfirmed "Time confirmed"; +CM_ SG_ 770 Epoch "Epoch time"; +CM_ BO_ 771 "GNSS position"; +CM_ SG_ 771 PositionValid "Position validity"; +CM_ SG_ 771 Latitude "Latitude"; +CM_ SG_ 771 Longitude "Longitude"; +CM_ SG_ 771 PositionAccuracy "Accuracy of position"; +CM_ BO_ 772 "GNSS altitude"; +CM_ SG_ 772 AltitudeValid "Altitude validity"; +CM_ SG_ 772 Altitude "Altitude"; +CM_ SG_ 772 AltitudeAccuracy "Accuracy of altitude"; +CM_ BO_ 773 "GNSS attitude"; +CM_ SG_ 773 AttitudeValid "Attitude validity"; +CM_ SG_ 773 Roll "Vehicle roll"; +CM_ SG_ 773 RollAccuracy "Vehicle roll accuracy"; +CM_ SG_ 773 Pitch "Vehicle pitch"; +CM_ SG_ 773 PitchAccuracy "Vehicle pitch accuracy"; +CM_ SG_ 773 Heading "Vehicle heading"; +CM_ SG_ 773 HeadingAccuracy "Vehicle heading accuracy"; +CM_ BO_ 774 "GNSS odometer"; +CM_ SG_ 774 DistanceTrip "Distance traveled since last reset"; +CM_ SG_ 774 DistanceAccuracy "Distance accuracy (1-sigma)"; +CM_ SG_ 774 DistanceTotal "Distance traveled in total"; +CM_ BO_ 775 "GNSS speed"; +CM_ SG_ 775 SpeedValid "Speed valid"; +CM_ SG_ 775 Speed "Speed m/s"; +CM_ SG_ 775 SpeedAccuracy "Speed accuracy"; +CM_ BO_ 776 "GNSS geofence(s)"; +CM_ SG_ 776 FenceValid "Geofencing status"; +CM_ SG_ 776 FenceCombined "Combined (logical OR) state of all geofences"; +CM_ SG_ 776 Fence1 "Geofence 1 state"; +CM_ SG_ 776 Fence2 "Geofence 2 state"; +CM_ SG_ 776 Fence3 "Geofence 3 state"; +CM_ SG_ 776 Fence4 "Geofence 4 state"; +CM_ BO_ 777 "GNSS IMU"; +CM_ SG_ 777 AccelerationX "X acceleration with a resolution of 0.125 m/s^2"; +CM_ SG_ 777 AccelerationY "Y acceleration with a resolution of 0.125 m/s^2"; +CM_ SG_ 777 AccelerationZ "Z acceleration with a resolution of 0.125 m/s^2"; +CM_ SG_ 777 AngularRateX "X angular rate with a resolution of 0.25 deg/s"; +CM_ SG_ 777 AngularRateY "Y angular rate with a resolution of 0.25 deg/s"; +CM_ SG_ 777 AngularRateZ "Z angular rate with a resolution of 0.25 deg/s"; +BA_DEF_ "BusType" STRING ; +BA_DEF_ "MultiplexExtEnabled" ENUM "No","Yes"; +BA_DEF_DEF_ "BusType" "CAN"; +BA_DEF_DEF_ "MultiplexExtEnabled" "No"; \ No newline at end of file diff --git a/scripts/daq/go.mod b/scripts/daq/go.mod new file mode 100644 index 000000000..5815cb534 --- /dev/null +++ b/scripts/daq/go.mod @@ -0,0 +1,25 @@ +module mac-daq + +go 1.23.0 + +require ( + github.com/macformula/hil v0.0.0-20250916142256-e7edb0f50362 + go.einride.tech/can v0.16.1 +) + +require ( + github.com/alecthomas/kingpin/v2 v2.4.0 // indirect + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 // indirect + github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.uber.org/zap v1.26.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect +) diff --git a/scripts/daq/go.sum b/scripts/daq/go.sum new file mode 100644 index 000000000..98af0915a --- /dev/null +++ b/scripts/daq/go.sum @@ -0,0 +1,56 @@ +github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/macformula/hil v0.0.0-20250916142256-e7edb0f50362 h1:0ys7bStJcXBrn4hnf+Gr1JcnZgkwSeld2kKCpFD/Sjw= +github.com/macformula/hil v0.0.0-20250916142256-e7edb0f50362/go.mod h1:DZLE3YYqW7GatL2Hh80i04w/X1fQuKhkLoAy6o8a2Uw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 h1:SpaoQDTgpo2YZkvmr2mtgloFFfPTjtLMlZkQtNAPQik= +github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +go.einride.tech/can v0.16.1 h1:s9MqX1OR6ujGxvl+gOWAGL54MC3kaPE+cgxBCUfDrB8= +go.einride.tech/can v0.16.1/go.mod h1:9pgqXNGpPfrd/WGXGmiKW8cUvIep/o+o76JgUKpQuWI= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/scripts/daq/handler.go b/scripts/daq/handler.go new file mode 100644 index 000000000..a3b6db436 --- /dev/null +++ b/scripts/daq/handler.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "github.com/macformula/hil/canlink" + "go.einride.tech/can" + "go.einride.tech/can/pkg/generated" + "go.uber.org/zap" +) + +type DbcMessagesDescriptor interface { + UnmarshalFrame(f can.Frame) (generated.Message, error) +} + +type DataAcquisitionHandler struct { + md DbcMessagesDescriptor + l *zap.Logger +} + +func NewDaqHandler(md DbcMessagesDescriptor, l *zap.Logger) *DataAcquisitionHandler { + return &DataAcquisitionHandler{ + md: md, + l: l, + } +} + +func (d *DataAcquisitionHandler) Name() string { + return fmt.Sprintf("DAQ Handler") +} + +func (d *DataAcquisitionHandler) Handle(broadcastChan chan canlink.TimestampedFrame, stopChan chan struct{}) error { + for { + select { + case <-stopChan: + d.l.Info("stopping handle") + case receivedFrame := <-broadcastChan: + // CAN frame received, parse it and queue it for transmission or file caching + + // We wont do any additional parsing for now, but it might be useful later + /* + * msg, err := d.md.UnmarshalFrame(receivedFrame.Frame) + * if err != nil { + * return errors.Wrap(err, "daq: handle:") + * } + */ + + fmt.Printf("daq: received frame at %s\n", receivedFrame.Time.String()) + // add to queue + default: + } + } +} diff --git a/scripts/daq/main.go b/scripts/daq/main.go new file mode 100644 index 000000000..8f20e9c4a --- /dev/null +++ b/scripts/daq/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "context" + "github.com/macformula/hil/canlink" + "go.einride.tech/can/pkg/socketcan" + "go.uber.org/zap" + vehcan "mac-daq/generated" +) + +func main() { + conn, err := socketcan.DialContext(context.Background(), "can", "van0") + if err != nil { + panic(err) + } + + logger, err := zap.NewDevelopment() + daq := NewDaqHandler(vehcan.Messages(), logger) + + manager := canlink.NewBusManager(logger, &conn) + manager.Register(daq) + manager.Start(context.Background()) + + for { + } +} From f541de05815a61cbceeb251f584322259479f431 Mon Sep 17 00:00:00 2001 From: HalvedIncrease Date: Mon, 13 Oct 2025 23:38:54 -0400 Subject: [PATCH 02/34] daq: send/enqueue template --- scripts/daq/handler.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/daq/handler.go b/scripts/daq/handler.go index a3b6db436..4a1752c65 100644 --- a/scripts/daq/handler.go +++ b/scripts/daq/handler.go @@ -45,8 +45,16 @@ func (d *DataAcquisitionHandler) Handle(broadcastChan chan canlink.TimestampedFr */ fmt.Printf("daq: received frame at %s\n", receivedFrame.Time.String()) - // add to queue + d.Enqueue(receivedFrame) default: } } } + +func (d *DataAcquisitionHandler) Enqueue(frame canlink.TimestampedFrame) error { + return nil +} + +func (d *DataAcquisitionHandler) Send() error { + return nil +} From 11081ba9d4b16eb07ecc6810ac3e3846e7b4d45a Mon Sep 17 00:00:00 2001 From: TylerStAmour Date: Mon, 27 Oct 2025 23:47:39 -0400 Subject: [PATCH 03/34] basic caching mechanism for can frames Writes them to a local SQLite DB for now --- scripts/daq/.gitignore | 1 + scripts/daq/{handler.go => can.go} | 26 +++--- scripts/daq/go.mod | 25 +++--- scripts/daq/go.sum | 45 +++++----- scripts/daq/main.go | 12 ++- scripts/daq/telemetry.go | 139 +++++++++++++++++++++++++++++ 6 files changed, 199 insertions(+), 49 deletions(-) rename scripts/daq/{handler.go => can.go} (72%) create mode 100644 scripts/daq/telemetry.go diff --git a/scripts/daq/.gitignore b/scripts/daq/.gitignore index 9ab870da8..db239ed30 100644 --- a/scripts/daq/.gitignore +++ b/scripts/daq/.gitignore @@ -1 +1,2 @@ generated/ +can_cache.sqlite \ No newline at end of file diff --git a/scripts/daq/handler.go b/scripts/daq/can.go similarity index 72% rename from scripts/daq/handler.go rename to scripts/daq/can.go index 4a1752c65..b9e3c8a52 100644 --- a/scripts/daq/handler.go +++ b/scripts/daq/can.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/macformula/hil/canlink" "go.einride.tech/can" "go.einride.tech/can/pkg/generated" @@ -13,14 +14,16 @@ type DbcMessagesDescriptor interface { } type DataAcquisitionHandler struct { - md DbcMessagesDescriptor - l *zap.Logger + md DbcMessagesDescriptor + l *zap.Logger + telemetry *TelemetryHandler } -func NewDaqHandler(md DbcMessagesDescriptor, l *zap.Logger) *DataAcquisitionHandler { +func NewDaqHandler(md DbcMessagesDescriptor, telemetry *TelemetryHandler, l *zap.Logger) *DataAcquisitionHandler { return &DataAcquisitionHandler{ - md: md, - l: l, + md: md, + l: l, + telemetry: telemetry, } } @@ -45,16 +48,11 @@ func (d *DataAcquisitionHandler) Handle(broadcastChan chan canlink.TimestampedFr */ fmt.Printf("daq: received frame at %s\n", receivedFrame.Time.String()) - d.Enqueue(receivedFrame) + err := d.telemetry.Enqueue(receivedFrame) + if err != nil { + fmt.Printf("daq: failed to enqueue frame: %s\n", err.Error()) + } default: } } } - -func (d *DataAcquisitionHandler) Enqueue(frame canlink.TimestampedFrame) error { - return nil -} - -func (d *DataAcquisitionHandler) Send() error { - return nil -} diff --git a/scripts/daq/go.mod b/scripts/daq/go.mod index 5815cb534..395da0651 100644 --- a/scripts/daq/go.mod +++ b/scripts/daq/go.mod @@ -1,25 +1,28 @@ module mac-daq -go 1.23.0 +go 1.24.0 require ( github.com/macformula/hil v0.0.0-20250916142256-e7edb0f50362 + github.com/mattn/go-sqlite3 v1.14.32 go.einride.tech/can v0.16.1 + go.uber.org/zap v1.26.0 ) require ( - github.com/alecthomas/kingpin/v2 v2.4.0 // indirect - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect - github.com/fatih/color v1.17.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 // indirect - github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 // indirect - github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.26.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.36.0 // indirect + modernc.org/libc v1.66.10 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.39.1 // indirect ) diff --git a/scripts/daq/go.sum b/scripts/daq/go.sum index 98af0915a..194882953 100644 --- a/scripts/daq/go.sum +++ b/scripts/daq/go.sum @@ -1,37 +1,29 @@ -github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= -github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/macformula/hil v0.0.0-20250916142256-e7edb0f50362 h1:0ys7bStJcXBrn4hnf+Gr1JcnZgkwSeld2kKCpFD/Sjw= github.com/macformula/hil v0.0.0-20250916142256-e7edb0f50362/go.mod h1:DZLE3YYqW7GatL2Hh80i04w/X1fQuKhkLoAy6o8a2Uw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560 h1:SpaoQDTgpo2YZkvmr2mtgloFFfPTjtLMlZkQtNAPQik= -github.com/shurcooL/go v0.0.0-20190704215121-7189cc372560/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= -github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= go.einride.tech/can v0.16.1 h1:s9MqX1OR6ujGxvl+gOWAGL54MC3kaPE+cgxBCUfDrB8= go.einride.tech/can v0.16.1/go.mod h1:9pgqXNGpPfrd/WGXGmiKW8cUvIep/o+o76JgUKpQuWI= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -40,17 +32,28 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= +modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= +modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= diff --git a/scripts/daq/main.go b/scripts/daq/main.go index 8f20e9c4a..aaea7849a 100644 --- a/scripts/daq/main.go +++ b/scripts/daq/main.go @@ -2,10 +2,11 @@ package main import ( "context" + vehcan "mac-daq/generated" + "github.com/macformula/hil/canlink" "go.einride.tech/can/pkg/socketcan" "go.uber.org/zap" - vehcan "mac-daq/generated" ) func main() { @@ -14,8 +15,13 @@ func main() { panic(err) } - logger, err := zap.NewDevelopment() - daq := NewDaqHandler(vehcan.Messages(), logger) + logger, _ := zap.NewDevelopment() + telemetry, err := NewTelemetryHandler() + if err != nil { + panic(err) + } + + daq := NewDaqHandler(vehcan.Messages(), telemetry, logger) manager := canlink.NewBusManager(logger, &conn) manager.Register(daq) diff --git a/scripts/daq/telemetry.go b/scripts/daq/telemetry.go new file mode 100644 index 000000000..a2a74d37b --- /dev/null +++ b/scripts/daq/telemetry.go @@ -0,0 +1,139 @@ +package main + +import ( + "container/list" + "database/sql" + "fmt" + + "github.com/macformula/hil/canlink" + "go.einride.tech/can" + _ "modernc.org/sqlite" +) + +const BufferSize = 512 + +type TelemetryPacket struct { + Id int64 `db:"id"` + Timestamp int64 `db:"timestamp"` + FrameId uint32 `db:"frame_id"` + FrameData can.Data `db:"frame_data"` +} + +type TelemetryHandler struct { + buf *list.List + db *sql.DB +} + +func NewTelemetryHandler() (*TelemetryHandler, error) { + db, err := sql.Open("sqlite", "./can_cache.sqlite") + if err != nil { + return nil, err + } + + _, err = db.Exec(` + CREATE TABLE IF NOT EXISTS can_cache ( + id INTEGER PRIMARY KEY, + timestamp INTEGER, + frame_id INTEGER, + frame_data INTEGER + ) + `) + if err != nil { + db.Close() + return nil, err + } + + _, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_timestamp ON can_cache(timestamp)") + if err != nil { + db.Close() + return nil, err + } + + t := TelemetryHandler{ + buf: list.New(), + db: db, + } + + err = t.fillBufferFromDisk() + if err != nil { + db.Close() + return nil, err + } + + return &t, nil +} + +func (t *TelemetryHandler) Enqueue(frame canlink.TimestampedFrame) error { + query, err := t.db.Prepare(` + INSERT INTO can_cache (timestamp, frame_id, frame_data) VALUES (?, ?, ?) + `) + if err != nil { + return err + } + + res, err := query.Exec(frame.Time.UnixMilli(), frame.Frame.ID, frame.Frame.Data.PackBigEndian()) + if err != nil { + return err + } + + // Ignore the error since the schema enforces an auto-incrementing primary key + id, _ := res.LastInsertId() + + elem := list.Element{ + Value: TelemetryPacket{ + Id: id, + Timestamp: frame.Time.UnixMilli(), + FrameId: frame.Frame.ID, + FrameData: frame.Frame.Data, + }, + } + + if t.buf.Len() >= BufferSize { + t.buf.Remove(t.buf.Back()) + } + t.buf.MoveToFront(&elem) + + return nil +} + +func (t *TelemetryHandler) Upload() error { + // pop from front of buffer and write to backend + + return nil +} + +func (t *TelemetryHandler) fillBufferFromDisk() error { + t.buf.Init() + + query, err := t.db.Prepare(` + SELECT id, timestamp, frame_id, frame_data FROM can_cache ORDER BY timestamp DESC LIMIT ? + `) + if err != nil { + return err + } + + rows, err := query.Query(BufferSize) + if err != nil { + return err + } + + for rows.Next() { + var packet TelemetryPacket + var packedFrameData uint64 + + err = rows.Scan(&packet.Id, &packet.Id, &packet.FrameId, &packedFrameData) + if err != nil { + fmt.Printf("Failed to scan packet: %v\n", err) + continue + } + + var frameData can.Data + frameData.UnpackBigEndian(packedFrameData) + packet.FrameData = frameData + + t.buf.PushBack(packet) + } + + rows.Close() + return nil +} From 86a52825e170feae6eb743cfe529b58cc07cf547 Mon Sep 17 00:00:00 2001 From: TylerStAmour Date: Sat, 1 Nov 2025 23:23:43 -0400 Subject: [PATCH 04/34] daq: interval between repeated frames + handle both CAN busses Plus some other misc work --- scripts/daq/can.go | 35 +++++++++++++----- scripts/daq/main.go | 33 +++++++++++++---- scripts/daq/telemetry.go | 78 +++++++++++++++++++++++++++++++++++----- 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/scripts/daq/can.go b/scripts/daq/can.go index b9e3c8a52..1f2246651 100644 --- a/scripts/daq/can.go +++ b/scripts/daq/can.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "time" "github.com/macformula/hil/canlink" "go.einride.tech/can" @@ -9,21 +10,30 @@ import ( "go.uber.org/zap" ) +const ( + // MAX_FRAME_INTERVAL is the milliseconds between using identical CAN frames + MAX_FRAME_INTERVAL = 100 +) + type DbcMessagesDescriptor interface { UnmarshalFrame(f can.Frame) (generated.Message, error) } type DataAcquisitionHandler struct { - md DbcMessagesDescriptor - l *zap.Logger - telemetry *TelemetryHandler + md DbcMessagesDescriptor + telemetry *TelemetryHandler + busName string + l *zap.Logger + frameArrivalMap map[uint32]time.Time } -func NewDaqHandler(md DbcMessagesDescriptor, telemetry *TelemetryHandler, l *zap.Logger) *DataAcquisitionHandler { +func NewDaqHandler(md DbcMessagesDescriptor, telemetry *TelemetryHandler, busName string, l *zap.Logger) *DataAcquisitionHandler { return &DataAcquisitionHandler{ - md: md, - l: l, - telemetry: telemetry, + md: md, + l: l, + telemetry: telemetry, + busName: busName, + frameArrivalMap: map[uint32]time.Time{}, } } @@ -47,8 +57,15 @@ func (d *DataAcquisitionHandler) Handle(broadcastChan chan canlink.TimestampedFr * } */ - fmt.Printf("daq: received frame at %s\n", receivedFrame.Time.String()) - err := d.telemetry.Enqueue(receivedFrame) + fmt.Printf("daq: received frame: %s\n", receivedFrame.Frame.String()) + lastSeen, exists := d.frameArrivalMap[receivedFrame.Frame.ID] + + // Frame was already seen less than MAX_FRAME_INTERVAL ms ago, ignore it + if exists && time.Now().Sub(lastSeen).Milliseconds() < MAX_FRAME_INTERVAL { + break + } + + err := d.telemetry.Enqueue(receivedFrame, d.busName) if err != nil { fmt.Printf("daq: failed to enqueue frame: %s\n", err.Error()) } diff --git a/scripts/daq/main.go b/scripts/daq/main.go index aaea7849a..08b55e115 100644 --- a/scripts/daq/main.go +++ b/scripts/daq/main.go @@ -2,7 +2,9 @@ package main import ( "context" + "fmt" vehcan "mac-daq/generated" + "time" "github.com/macformula/hil/canlink" "go.einride.tech/can/pkg/socketcan" @@ -10,23 +12,42 @@ import ( ) func main() { - conn, err := socketcan.DialContext(context.Background(), "can", "van0") + can0, err := socketcan.DialContext(context.Background(), "can", "can0") + if err != nil { + panic(err) + } + + can1, err := socketcan.DialContext(context.Background(), "can", "can1") if err != nil { panic(err) } logger, _ := zap.NewDevelopment() - telemetry, err := NewTelemetryHandler() + + telemetry, err := NewTelemetryHandler("./can_cache.sqlite") if err != nil { panic(err) } - daq := NewDaqHandler(vehcan.Messages(), telemetry, logger) + daqCan0 := NewDaqHandler(vehcan.Messages(), telemetry, "can0", logger) + daqCan1 := NewDaqHandler(vehcan.Messages(), telemetry, "can1", logger) + + manager0 := canlink.NewBusManager(logger, &can0) + manager0.Register(daqCan0) + manager0.Start(context.Background()) - manager := canlink.NewBusManager(logger, &conn) - manager.Register(daq) - manager.Start(context.Background()) + manager1 := canlink.NewBusManager(logger, &can1) + manager1.Register(daqCan1) + manager1.Start(context.Background()) + uploadTimer := time.NewTimer(time.Second) for { + select { + case <-uploadTimer.C: + err = telemetry.Upload() + if err != nil { + fmt.Printf("failed to upload telemetry data: %v\n", err) + } + } } } diff --git a/scripts/daq/telemetry.go b/scripts/daq/telemetry.go index a2a74d37b..e7754ffc8 100644 --- a/scripts/daq/telemetry.go +++ b/scripts/daq/telemetry.go @@ -7,6 +7,7 @@ import ( "github.com/macformula/hil/canlink" "go.einride.tech/can" + "go.uber.org/zap" _ "modernc.org/sqlite" ) @@ -20,12 +21,14 @@ type TelemetryPacket struct { } type TelemetryHandler struct { - buf *list.List - db *sql.DB + buf *list.List + db *sql.DB + l *zap.Logger + dbFile string } -func NewTelemetryHandler() (*TelemetryHandler, error) { - db, err := sql.Open("sqlite", "./can_cache.sqlite") +func NewTelemetryHandler(dbFile string) (*TelemetryHandler, error) { + db, err := sql.Open("sqlite", dbFile) if err != nil { return nil, err } @@ -35,7 +38,8 @@ func NewTelemetryHandler() (*TelemetryHandler, error) { id INTEGER PRIMARY KEY, timestamp INTEGER, frame_id INTEGER, - frame_data INTEGER + frame_data INTEGER, + bus_name TEXT ) `) if err != nil { @@ -60,18 +64,20 @@ func NewTelemetryHandler() (*TelemetryHandler, error) { return nil, err } + fmt.Printf("Initial buffer size: %v\n", t.buf.Len()) + return &t, nil } -func (t *TelemetryHandler) Enqueue(frame canlink.TimestampedFrame) error { +func (t *TelemetryHandler) Enqueue(frame canlink.TimestampedFrame, busName string) error { query, err := t.db.Prepare(` - INSERT INTO can_cache (timestamp, frame_id, frame_data) VALUES (?, ?, ?) + INSERT INTO can_cache (timestamp, frame_id, frame_data, bus_name) VALUES (?, ?, ?, ?) `) if err != nil { return err } - res, err := query.Exec(frame.Time.UnixMilli(), frame.Frame.ID, frame.Frame.Data.PackBigEndian()) + res, err := query.Exec(frame.Time.UnixMilli(), frame.Frame.ID, frame.Frame.Data.PackBigEndian(), busName) if err != nil { return err } @@ -89,6 +95,7 @@ func (t *TelemetryHandler) Enqueue(frame canlink.TimestampedFrame) error { } if t.buf.Len() >= BufferSize { + // go upload ? t.buf.Remove(t.buf.Back()) } t.buf.MoveToFront(&elem) @@ -99,9 +106,64 @@ func (t *TelemetryHandler) Enqueue(frame canlink.TimestampedFrame) error { func (t *TelemetryHandler) Upload() error { // pop from front of buffer and write to backend + // Ignore for now ... + /*buf := t.emptyBuffer() + + type temp struct { + Value []byte `json:"value"` + Timestamp int64 `json:"timestamp"` + } + + for i := range len(buf) { + frame := can.Frame{ + ID: buf[i].FrameId, + Data: buf[i].FrameData, + Length: 8, + } + rawFrame, _ := json.Marshal(frame) + + toWrite := temp{ + Timestamp: buf[i].Timestamp, + Value: rawFrame, + } + + data, err := json.Marshal(toWrite) + if err != nil { + return err + } + if i == 0 { + fmt.Printf("payload: %s\n", string(data)) + } + + // If this fails then we should re-queue the entire buffer to not lose anything + _, err = http.Post("http://localhost:5000/write-graph", "application/json", bytes.NewBuffer(data)) + if err != nil { + fmt.Printf("telemetry/upload: failed to post frame data: %v\n", err) + return err + } + } + + // In case of an error here, just log and keep going, we still need to upload our data + err := t.fillBufferFromDisk() + if err != nil { + fmt.Printf("telemetry/upload: failed to fill buffer: %v\n", err) + }*/ + return nil } +func (t *TelemetryHandler) emptyBuffer() []TelemetryPacket { + packets := make([]TelemetryPacket, t.buf.Len()) + next := t.buf.Front() + + for range t.buf.Len() { + packets = append(packets, next.Value.(TelemetryPacket)) + next = next.Next() + } + + return packets +} + func (t *TelemetryHandler) fillBufferFromDisk() error { t.buf.Init() From 2a6c00e977edf15ba917343cdde6c415effde37a Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Sat, 29 Nov 2025 21:39:41 -0500 Subject: [PATCH 05/34] feat: completed heartbeat constructor --- scripts/daq/heartbeat.go | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 scripts/daq/heartbeat.go diff --git a/scripts/daq/heartbeat.go b/scripts/daq/heartbeat.go new file mode 100644 index 000000000..c98ebf1bf --- /dev/null +++ b/scripts/daq/heartbeat.go @@ -0,0 +1,56 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "os" + "time" + + "go.einride.tech/can/pkg/socketcan" + "go.uber.org/zap" +) + +type HeartbeatHandler struct { + serverURL string + vehicleID string + sessionID string + can0 *socketcan.Receiver + can1 *socketcan.Receiver + logger *zap.Logger +} + +func NewHeartbeatHandler(can0, can1 *socketcan.Receiver, logger *zap.Logger) *HeartbeatHandler { + + // Configuring Vehicle ID + vehicleID := os.Getenv("VEHICLE_ID") + if vehicleID == "" { + logger.Error("VEHICLE_ID not found, using default.") + vehicleID = "default" + } + + // Generating Session ID + sessionBytes := make([]byte, 16) + _, err := rand.Read(sessionBytes) + var sessionID string + if err != nil { + logger.Warn("Failed to generate session ID, defaulting to time based session ID.") + sessionID = fmt.Sprintf("fallback-%d", time.Now().UnixNano()) + } else { + sessionID = hex.EncodeToString(sessionBytes) + } + + serverURL := os.Getenv("SERVER_URL") + if serverURL == "" { + logger.Error("SERVER_URL for heartbeatnot found") + } + + return &HeartbeatHandler{ + serverURL: serverURL, + vehicleID: vehicleID, + sessionID: sessionID, + can0: can0, + can1: can1, + logger: logger, + } +} From ce6e10ced72e91771cbc3ca9f68bd74a6312a33c Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Sat, 29 Nov 2025 21:49:00 -0500 Subject: [PATCH 06/34] feat: completed heartbeat implementation --- scripts/daq/heartbeat.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/scripts/daq/heartbeat.go b/scripts/daq/heartbeat.go index c98ebf1bf..fcaeb3555 100644 --- a/scripts/daq/heartbeat.go +++ b/scripts/daq/heartbeat.go @@ -1,9 +1,12 @@ package main import ( + "bytes" "crypto/rand" "encoding/hex" + "encoding/json" "fmt" + "net/http" "os" "time" @@ -54,3 +57,38 @@ func NewHeartbeatHandler(can0, can1 *socketcan.Receiver, logger *zap.Logger) *He logger: logger, } } + +func (h *HeartbeatHandler) SendHeartbeat() error { + can0Active, can1Active := h.checkCAN() + + payload := map[string]interface{}{ + "timestamp": time.Now().UnixMilli(), + "vehicle_id": h.vehicleID, + "session_id": h.sessionID, + "can_status": map[string]bool{ + "can0": can0Active, + "can1": can1Active, + }, + } + + jsonPayload, err := json.Marshal(payload) + if err != nil { + h.logger.Error("Failed to convert heartbeat payload to JSON", zap.Error(err)) + return err + } + + response, err := http.Post(h.serverURL, "application/json", bytes.NewBuffer(jsonPayload)) + if err != nil { + h.logger.Error("Failed to send heartbeat", zap.Error(err)) + return err + } + defer response.Body.Close() + + return nil +} + +func (h *HeartbeatHandler) checkCAN() (bool, bool) { + can0Active := h.can0 != nil + can1Active := h.can1 != nil + return can0Active, can1Active +} From dc4b238052262478f88de8cb29110bc0c65b69d3 Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Sat, 29 Nov 2025 22:06:47 -0500 Subject: [PATCH 07/34] fix: heartbeat types --- scripts/daq/heartbeat.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/daq/heartbeat.go b/scripts/daq/heartbeat.go index fcaeb3555..3e9629d71 100644 --- a/scripts/daq/heartbeat.go +++ b/scripts/daq/heartbeat.go @@ -6,11 +6,11 @@ import ( "encoding/hex" "encoding/json" "fmt" + "net" "net/http" "os" "time" - "go.einride.tech/can/pkg/socketcan" "go.uber.org/zap" ) @@ -18,12 +18,12 @@ type HeartbeatHandler struct { serverURL string vehicleID string sessionID string - can0 *socketcan.Receiver - can1 *socketcan.Receiver + can0 net.Conn + can1 net.Conn logger *zap.Logger } -func NewHeartbeatHandler(can0, can1 *socketcan.Receiver, logger *zap.Logger) *HeartbeatHandler { +func NewHeartbeatHandler(can0, can1 net.Conn, logger *zap.Logger) *HeartbeatHandler { // Configuring Vehicle ID vehicleID := os.Getenv("VEHICLE_ID") From 96b968a098bc1d19ca259af47d259bfb8b914f2c Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Sat, 29 Nov 2025 22:07:07 -0500 Subject: [PATCH 08/34] feat: integrated heartbeat into daq main --- scripts/daq/main.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/daq/main.go b/scripts/daq/main.go index 08b55e115..49fc945d1 100644 --- a/scripts/daq/main.go +++ b/scripts/daq/main.go @@ -24,6 +24,8 @@ func main() { logger, _ := zap.NewDevelopment() + heartbeat := NewHeartbeatHandler(can0, can1, logger) + telemetry, err := NewTelemetryHandler("./can_cache.sqlite") if err != nil { panic(err) @@ -41,6 +43,10 @@ func main() { manager1.Start(context.Background()) uploadTimer := time.NewTimer(time.Second) + + heartbeatInterval := time.NewTicker(3 * time.Second) + defer heartbeatInterval.Stop() + for { select { case <-uploadTimer.C: @@ -48,6 +54,11 @@ func main() { if err != nil { fmt.Printf("failed to upload telemetry data: %v\n", err) } + case <-heartbeatInterval.C: + err = heartbeat.SendHeartbeat() + if err != nil { + logger.Error("Failed to send heartbeat", zap.Error(err)) + } } } } From eccf64fe7f660217567cff1d5da7bc503fa1f5ae Mon Sep 17 00:00:00 2001 From: Nicholas Ching Date: Tue, 17 Mar 2026 19:22:32 -0400 Subject: [PATCH 09/34] feat: rpi logger --- scripts/daq/.gitignore | 3 +- scripts/daq/logger.py | 285 +++++++++++++++++++++++++++++++++++ scripts/daq/requirements.txt | 1 + 3 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 scripts/daq/logger.py create mode 100644 scripts/daq/requirements.txt diff --git a/scripts/daq/.gitignore b/scripts/daq/.gitignore index db239ed30..e12f48a3f 100644 --- a/scripts/daq/.gitignore +++ b/scripts/daq/.gitignore @@ -1,2 +1,3 @@ generated/ -can_cache.sqlite \ No newline at end of file +can_cache.sqlite +logs/ \ No newline at end of file diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py new file mode 100644 index 000000000..21686ceb6 --- /dev/null +++ b/scripts/daq/logger.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +""" +Raspberry Pi CAN Data Logger. + +Reads CAN messages from one or two SocketCAN interfaces via candump, decodes +them using the vehicle and powertrain DBC files, and writes decoded signals +to a timestamped CSV file. + +Usage: + python logger.py [interface] [--pt-interface PT_INTERFACE] + python logger.py vcan0 + python logger.py can0 --pt-interface can1 +""" + +import argparse +import queue +import re +import signal +import subprocess +import sys +import threading +from datetime import datetime +from pathlib import Path + +import cantools + + +# --------------------------------------------------------------------------- +# Target message IDs — what we care about logging. +# +# How these IDs work: +# Standard CAN frames use 11-bit IDs (0x000–0x7FF). +# Extended CAN frames use 29-bit IDs. +# +# In the DBC file, extended messages are written with bit 31 set, e.g.: +# BO_ 2553934720 BmsBroadcast ... +# (2553934720 = 0x9839F380, bit 31 set) +# +# cantools strips bit 31 when it parses the DBC and stores the actual +# 29-bit frame ID: +# BmsBroadcast.frame_id == 0x1839F380 == 406451072 +# +# candump prints extended IDs as 8 hex digits without bit 31: +# (t) vcan0 1839F380#... +# +# So the IDs here must match cantools (29-bit, no bit-31 flag). +# --------------------------------------------------------------------------- +TARGET_IDS = { + 230, # DashCommand (veh) - Speed + 300, # Accumulator_Soc (veh) - battery state + 310, # SuspensionTravel34 (veh) - linear pots + 401, # AppsDebug (veh) - accelerator pedal pots + 402, # BppsSteerDebug (veh) - brake pedal + steering pots + 643, # Inv1_ActualValues1 (pt) - left motor velocity + 644, # Inv2_ActualValues1 (pt) - right motor velocity + 645, # Inv1_ActualValues2 (pt) - motor/inverter temps + 646, # Inv2_ActualValues2 (pt) - motor/inverter temps + 773, # GnssAttitude (veh) - IMU roll/pitch/heading + 777, # GnssImu (veh) - IMU accelerations + rates + 1572, # Pack_State (veh) - battery current/voltage + 1573, # Pack_SOC (veh) - battery state of charge + 406451072, # BmsBroadcast (veh) - battery pack temps (extended ID 0x1839F380) + 419361278, # ThermistorBroadcast (veh) - thermistor temps (extended ID 0x18FEF1FE) +} + +# candump -L log format: (timestamp) interface hex_id#hex_data +CANDUMP_LINE_RE = re.compile( + r'\((\d+\.\d+)\)\s+\S+\s+([0-9A-Fa-f]+)#([0-9A-Fa-f]*)' +) + + +def load_dbc(veh_dbc: Path, pt_dbc: Path | None) -> cantools.database.Database: + """Load veh.dbc and optionally pt.dbc into a single cantools Database.""" + db = cantools.database.load_file(str(veh_dbc)) + if pt_dbc and pt_dbc.exists(): + with open(pt_dbc, encoding='utf-8') as f: + db.add_dbc(f) + return db + + +def open_csv(log_dir: Path) -> tuple[Path, object]: + """Create logs dir, open CSV with header, return (path, file).""" + log_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now().strftime('%Y%m%d_%H%M%S') + path = log_dir / f'can_log_{ts}.csv' + f = open(path, 'w', newline='', encoding='utf-8') + f.write('timestamp_epoch_s,message_id_hex,message_name,signal_name,value,unit\n') + return path, f + + +def parse_candump_line(line: str) -> tuple[float, int, bytes] | None: + """ + Parse a candump -L line into (timestamp_s, can_id, data_bytes). + + candump output format: + (1709650000.123456) vcan0 136#8E91 + (1709650000.456789) vcan0 1839F380#C2001816060A0041 + + The hex ID is printed as-is by candump — no bit-31 flag, just the raw + value. For extended frames this is 8 hex digits; for standard frames + it is up to 3 hex digits. We parse it directly so it matches cantools. + """ + m = CANDUMP_LINE_RE.match(line.strip()) + if not m: + return None + ts_str, hex_id, hex_data = m.groups() + if len(hex_data) % 2 != 0: + return None + return float(ts_str), int(hex_id, 16), bytes.fromhex(hex_data) + + +def decode_frame( + db: cantools.database.Database, + can_id: int, + data: bytes, +) -> tuple[str, dict[str, tuple]] | None: + """ + Look up the message in the DBC by frame ID and decode the raw bytes. + + Returns (message_name, {signal_name: (value, unit)}) or None if the + message is not in the DBC or decoding fails. + """ + msg = next((m for m in db.messages if m.frame_id == can_id), None) + if msg is None: + return None + try: + decoded = msg.decode(data) + except Exception: + return None + out = { + sig.name: (decoded[sig.name], sig.unit or '') + for sig in msg.signals + if sig.name in decoded and decoded[sig.name] is not None + } + return (msg.name, out) if out else None + + +def _candump_reader( + interface: str, + out_queue: queue.Queue, + processes: list, +) -> None: + """ + Spawn candump on `interface`, forward each line to out_queue. + Puts a None sentinel when done so the consumer knows one source finished. + """ + try: + proc = subprocess.Popen( + ['candump', '-L', interface], + stdout=subprocess.PIPE, + text=True, + bufsize=1, + ) + except FileNotFoundError: + print( + "Error: 'candump' not found.\n" + "On the Raspberry Pi install can-utils: sudo apt install can-utils", + file=sys.stderr, + ) + out_queue.put(None) + return + + processes.append(proc) + for line in proc.stdout: + out_queue.put(line) + out_queue.put(None) + + +def main() -> int: + parser = argparse.ArgumentParser( + description='CAN data logger: candump -> DBC decode -> CSV' + ) + parser.add_argument( + 'interface', + nargs='?', + default='vcan0', + help='Primary SocketCAN interface (default: vcan0)', + ) + parser.add_argument( + '--pt-interface', + default=None, + metavar='INTERFACE', + help='Optional second interface for powertrain bus (e.g. can1)', + ) + parser.add_argument( + '--veh-dbc', + type=Path, + default=None, + help='Path to veh.dbc (default: /dbc/veh.dbc)', + ) + parser.add_argument( + '--pt-dbc', + type=Path, + default=None, + help='Path to pt.dbc (default: /projects/pt.dbc)', + ) + parser.add_argument( + '--log-dir', + type=Path, + default=None, + help='Output directory for CSV logs (default: /logs)', + ) + parser.add_argument( + '--all', + action='store_true', + help='Log every decodable message, not just target signals', + ) + args = parser.parse_args() + + script_dir = Path(__file__).resolve().parent + veh_dbc = args.veh_dbc or (script_dir / 'dbc' / 'veh.dbc') + pt_dbc = args.pt_dbc or (script_dir.parent.parent / 'projects' / 'pt.dbc') + log_dir = args.log_dir or (script_dir / 'logs') + + if not veh_dbc.exists(): + print(f'Error: veh.dbc not found at {veh_dbc}', file=sys.stderr) + return 1 + + db = load_dbc(veh_dbc, pt_dbc) + log_path, csv_file = open_csv(log_dir) + + interfaces = [args.interface] + if args.pt_interface: + interfaces.append(args.pt_interface) + + print(f'Logging to {log_path}', file=sys.stderr) + print(f'Interfaces: {", ".join(interfaces)}. Press Ctrl+C to stop.', file=sys.stderr) + + # Use a queue so frames from multiple candump processes merge into one loop. + line_queue: queue.Queue = queue.Queue() + processes: list = [] + + for iface in interfaces: + t = threading.Thread( + target=_candump_reader, + args=(iface, line_queue, processes), + daemon=True, + ) + t.start() + + def cleanup(_sig=None, _frame=None): + for p in processes: + p.terminate() + p.wait() + csv_file.close() + sys.exit(0) + + signal.signal(signal.SIGINT, cleanup) + signal.signal(signal.SIGTERM, cleanup) + + # Count how many None sentinels we expect (one per interface thread). + num_interfaces = len(interfaces) + done = 0 + + while done < num_interfaces: + line = line_queue.get() + if line is None: + done += 1 + continue + + parsed = parse_candump_line(line) + if not parsed: + continue + timestamp, can_id, data = parsed + + if not args.all and can_id not in TARGET_IDS: + continue + + result = decode_frame(db, can_id, data) + if not result: + continue + + msg_name, signals = result + id_hex = f'0x{can_id:X}' + for sig_name, (value, unit) in signals.items(): + csv_file.write( + f'{timestamp},{id_hex},{msg_name},{sig_name},{value},"{unit}"\n' + ) + + csv_file.close() + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/daq/requirements.txt b/scripts/daq/requirements.txt new file mode 100644 index 000000000..c76b518b5 --- /dev/null +++ b/scripts/daq/requirements.txt @@ -0,0 +1 @@ +cantools>=39.0.0 From e12d4b8287aa199616367972cbc01a13729ab97e Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Fri, 1 May 2026 23:41:15 -0400 Subject: [PATCH 10/34] influx-db --- scripts/daq/.gitignore | 3 +- scripts/daq/logger.py | 63 ++++++++++++++++++++++++++++++++++++ scripts/daq/requirements.txt | 3 ++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/scripts/daq/.gitignore b/scripts/daq/.gitignore index e12f48a3f..223b83df6 100644 --- a/scripts/daq/.gitignore +++ b/scripts/daq/.gitignore @@ -1,3 +1,4 @@ generated/ can_cache.sqlite -logs/ \ No newline at end of file +logs/ +.env \ No newline at end of file diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index 21686ceb6..94ae38f3d 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -19,11 +19,16 @@ import subprocess import sys import threading +import os from datetime import datetime from pathlib import Path import cantools +from influxdb_client import InfluxDBClient, Point +from dotenv import load_dotenv +load_dotenv() +INFLUX_TOKEN = os.getenv('INFLUX_TOKEN') # --------------------------------------------------------------------------- # Target message IDs — what we care about logging. @@ -63,11 +68,60 @@ 419361278, # ThermistorBroadcast (veh) - thermistor temps (extended ID 0x18FEF1FE) } +IMPORTANT_SIGNALS = { + "battery_voltage", + "pack_current", + "motor_rpm", + "motor_temp", + "inverter_temp", + "soc" +} + # candump -L log format: (timestamp) interface hex_id#hex_data CANDUMP_LINE_RE = re.compile( r'\((\d+\.\d+)\)\s+\S+\s+([0-9A-Fa-f]+)#([0-9A-Fa-f]*)' ) +client = InfluxDBClient( + url=os.getenv("INFLUX_URL"), + token=os.getenv("INFLUX_TOKEN"), + org=os.getenv("INFLUX_ORG") +) +write_api = client.write_api() + + +def write_to_influx(sig_name, value, timestamp): + point = Point("car_data") \ + .tag("signal", sig_name) \ + .field("value", value) \ + .time(int(timestamp * 1e9)) + + write_api.write(bucket=os.getenv("INFLUX_BUCKET"), record=point) + +def write_fault_to_influx(fault, severity, timestamp): + point = Point("faults") \ + .tag("fault", fault) \ + .tag("severity", severity) \ + .time(int(timestamp * 1e9)) + + write_api.write(bucket=os.getenv("INFLUX_BUCKET"), record=point) + +def map_fault(code): + mapping = { + 0: "No Fault", + 1: "Low Voltage", + 2: "Over Temp", + 3: "Over Current" + } + return mapping.get(code, "Unknown") + +def get_severity(code): + if code == 0: + return "NORMAL" + elif code == 1: + return "WARNING" + else: + return "CRITICAL" def load_dbc(veh_dbc: Path, pt_dbc: Path | None) -> cantools.database.Database: """Load veh.dbc and optionally pt.dbc into a single cantools Database.""" @@ -277,6 +331,15 @@ def cleanup(_sig=None, _frame=None): f'{timestamp},{id_hex},{msg_name},{sig_name},{value},"{unit}"\n' ) + if sig_name == "fault_code": + fault_text = map_fault(value) + severity = get_severity(value) + + write_fault_to_influx(fault_text, severity, timestamp) + + if sig_name in IMPORTANT_SIGNALS: + write_to_influx(sig_name, value, timestamp) + csv_file.close() return 0 diff --git a/scripts/daq/requirements.txt b/scripts/daq/requirements.txt index c76b518b5..3f227b69e 100644 --- a/scripts/daq/requirements.txt +++ b/scripts/daq/requirements.txt @@ -1 +1,4 @@ cantools>=39.0.0 +influxdb-client>=1.50.0 +python-dotenv>=1.2.2 +python-can>=4.6.1 \ No newline at end of file From a36f8fd1fe43bad17fd4ef97b67996327e167a63 Mon Sep 17 00:00:00 2001 From: ManushPatel Date: Fri, 1 May 2026 21:23:27 +0000 Subject: [PATCH 11/34] test logger & influx db --- scripts/daq/logger.py | 8 +++++++- scripts/daq/test_encode.py | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 scripts/daq/test_encode.py diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index 94ae38f3d..c9a973468 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -176,6 +176,7 @@ def decode_frame( """ msg = next((m for m in db.messages if m.frame_id == can_id), None) if msg is None: + print(f"NO MESSAGE FOUND FOR ID: {can_id}") return None try: decoded = msg.decode(data) @@ -271,6 +272,10 @@ def main() -> int: return 1 db = load_dbc(veh_dbc, pt_dbc) + + # for msg in db.messages: + #print(msg.name, hex(msg.frame_id), [s.name for s in msg.signals]) + log_path, csv_file = open_csv(log_dir) interfaces = [args.interface] @@ -316,12 +321,13 @@ def cleanup(_sig=None, _frame=None): if not parsed: continue timestamp, can_id, data = parsed - + print(f"RAW: {can_id} {data.hex()}") if not args.all and can_id not in TARGET_IDS: continue result = decode_frame(db, can_id, data) if not result: + print(f"DECODE FAILED -> ID: {hex(can_id)} DATA: {data.hex()}") continue msg_name, signals = result diff --git a/scripts/daq/test_encode.py b/scripts/daq/test_encode.py new file mode 100644 index 000000000..dbad45d7b --- /dev/null +++ b/scripts/daq/test_encode.py @@ -0,0 +1,20 @@ +import cantools + +db = cantools.database.load_file("dbc/veh.dbc") + +msg = db.get_message_by_name("DashCommand") + +data = msg.encode({ + "Speed": 30, + "HvSocPercent": 50, + "ConfigReceived": 1, + "HvStarted": 1, + "MotorStarted": 1, + "DriveStarted": 1, + "Errored": 0, + "HvPrechargePercent": 50 +}) + +print(data.hex()) + + From f2f51796e0a53c5f1da8aad6a3b630aa965958e2 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sat, 9 May 2026 18:03:33 -0400 Subject: [PATCH 12/34] grafana dashboard json file --- scripts/daq/grafana_dashboard.json | 1553 ++++++++++++++++++++++++++++ 1 file changed, 1553 insertions(+) create mode 100644 scripts/daq/grafana_dashboard.json diff --git a/scripts/daq/grafana_dashboard.json b/scripts/daq/grafana_dashboard.json new file mode 100644 index 000000000..311453eeb --- /dev/null +++ b/scripts/daq/grafana_dashboard.json @@ -0,0 +1,1553 @@ +{ + "__inputs": [ + { + "name": "DS_INFLUXDB", + "label": "InfluxDB", + "description": "InfluxDB Cloud datasource — must use Flux query language", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + } + ], + "annotations": { + "list": [] + }, + "description": "MAC Formula Electric — Real-time racecar telemetry", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 100, + "title": "Vehicle Safety", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "min": 340, + "max": 620, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 374 + }, + { + "color": "green", + "value": 400 + }, + { + "color": "yellow", + "value": 590 + }, + { + "color": "red", + "value": 601 + } + ] + }, + "unit": "volt" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Pack_Inst_Voltage\")\n |> filter(fn: (r) => r._field == \"value\")\n |> last()" + } + ], + "title": "Battery Voltage", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "min": 0, + "max": 65, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 59 + } + ] + }, + "unit": "amp" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 2, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Pack_Current\")\n |> filter(fn: (r) => r._field == \"value\")\n |> last()" + } + ], + "title": "Pack Current", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "min": 0, + "max": 100, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 10 + }, + { + "color": "green", + "value": 20 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 3, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Pack_SOC\")\n |> filter(fn: (r) => r._field == \"value\")\n |> last()" + } + ], + "title": "SOC", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "min": 0, + "max": 80, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 55 + }, + { + "color": "red", + "value": 70 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"TempMotor\")\n |> filter(fn: (r) => r._field == \"value\")\n |> last()" + } + ], + "title": "Motor Temp", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "min": 0, + "max": 70, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 5, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"TempInverter\")\n |> filter(fn: (r) => r._field == \"value\")\n |> last()" + } + ], + "title": "Inverter Temp", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 1, + "mappings": [], + "min": 16, + "max": 30, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 19 + }, + { + "color": "green", + "value": 21 + } + ] + }, + "unit": "volt" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 6, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"lv_battery_voltage\")\n |> filter(fn: (r) => r._field == \"value\")\n |> last()" + } + ], + "title": "LV Battery", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "severity" + }, + "properties": [ + { + "id": "custom.cellOptions", + "value": { + "type": "color-background", + "mode": "basic" + } + }, + { + "id": "mappings", + "value": [ + { + "options": { + "WARNING": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + }, + { + "options": { + "CRITICAL": { + "color": "red", + "index": 1 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "_time" + }, + "properties": [ + { + "id": "displayName", + "value": "Time" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "system" + }, + "properties": [ + { + "id": "displayName", + "value": "System" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "fault" + }, + "properties": [ + { + "id": "displayName", + "value": "Fault" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "severity" + }, + "properties": [ + { + "id": "displayName", + "value": "Severity" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 7, + "options": { + "cellHeight": "sm", + "frameHeight": 0, + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Time" + } + ] + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"faults\")\n |> filter(fn: (r) => r._field == \"active\")\n |> group()\n |> keep(columns: [\"_time\", \"system\", \"fault\", \"severity\"])\n |> sort(columns: [\"_time\"], desc: true)\n |> limit(n: 100)" + } + ], + "title": "Fault Log", + "transformations": [ + { + "id": "organize", + "options": { + "renameByName": {}, + "indexByName": { + "_time": 0, + "system": 1, + "fault": 2, + "severity": 3 + }, + "excludeByName": { + "table": true + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 10, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "battery_voltage" + }, + "properties": [ + { + "id": "unit", + "value": "volt" + }, + { + "id": "custom.axisPlacement", + "value": "left" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "pack_current" + }, + "properties": [ + { + "id": "unit", + "value": "amp" + }, + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 8, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Pack_Inst_Voltage\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"battery_voltage\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Pack_Current\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"pack_current\")" + } + ], + "title": "Battery Voltage & Pack Current", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 101, + "title": "Performance", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed", + "fixedColor": "green" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 10, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "velocitykmh" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Speed\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)" + } + ], + "title": "Vehicle Speed (km/h)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "fixed", + "fixedColor": "yellow" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 10, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 59 + } + ] + }, + "unit": "amp" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 34 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Pack_Current\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)" + } + ], + "title": "Pack Current (A)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 5, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "rotrpm" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"ActualVelocity\")\n |> filter(fn: (r) => r.inverter == \"inv1\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Left Motor\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"ActualVelocity\")\n |> filter(fn: (r) => r.inverter == \"inv2\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Right Motor\")" + } + ], + "title": "Dual Motor RPM", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 50 + }, + "id": 102, + "title": "Segment Temperatures", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 5, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "celsius", + "min": 0, + "max": 70 + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 51 + }, + "id": 20, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"HighThermValue\")\n |> filter(fn: (r) => r.module == \"0\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Max\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"LowThermValue\")\n |> filter(fn: (r) => r.module == \"0\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Min\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "C", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"AvgThermValue\")\n |> filter(fn: (r) => r.module == \"0\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Avg\")" + } + ], + "title": "Segment 1 Temp", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 5, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "celsius", + "min": 0, + "max": 70 + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 51 + }, + "id": 21, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"HighThermValue\")\n |> filter(fn: (r) => r.module == \"1\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Max\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"LowThermValue\")\n |> filter(fn: (r) => r.module == \"1\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Min\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "C", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"AvgThermValue\")\n |> filter(fn: (r) => r.module == \"1\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Avg\")" + } + ], + "title": "Segment 2 Temp", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 5, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "celsius", + "min": 0, + "max": 70 + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 59 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"HighThermValue\")\n |> filter(fn: (r) => r.module == \"2\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Max\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"LowThermValue\")\n |> filter(fn: (r) => r.module == \"2\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Min\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "C", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"AvgThermValue\")\n |> filter(fn: (r) => r.module == \"2\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Avg\")" + } + ], + "title": "Segment 3 Temp", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 5, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "celsius", + "min": 0, + "max": 70 + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 59 + }, + "id": 23, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"HighThermValue\")\n |> filter(fn: (r) => r.module == \"3\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Max\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"LowThermValue\")\n |> filter(fn: (r) => r.module == \"3\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Min\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "C", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"AvgThermValue\")\n |> filter(fn: (r) => r.module == \"3\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Avg\")" + } + ], + "title": "Segment 4 Temp", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 5, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "celsius", + "min": 0, + "max": 70 + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 67 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"HighThermValue\")\n |> filter(fn: (r) => r.module == \"4\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Max\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"LowThermValue\")\n |> filter(fn: (r) => r.module == \"4\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Min\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "C", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"AvgThermValue\")\n |> filter(fn: (r) => r.module == \"4\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Avg\")" + } + ], + "title": "Segment 5 Temp", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 5, + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 60 + } + ] + }, + "unit": "celsius", + "min": 0, + "max": 70 + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 67 + }, + "id": 25, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"HighThermValue\")\n |> filter(fn: (r) => r.module == \"5\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Max\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"LowThermValue\")\n |> filter(fn: (r) => r.module == \"5\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Min\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "C", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"AvgThermValue\")\n |> filter(fn: (r) => r.module == \"5\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"Avg\")" + } + ], + "title": "Segment 6 Temp", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "tags": [ + "racecar", + "daq", + "macformula" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Racecar DAQ", + "uid": "macformula-racecar-daq", + "version": 1 +} \ No newline at end of file From f71104e2b62671c7b98b2be0245d07b6535c74fa Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sat, 9 May 2026 20:13:18 -0400 Subject: [PATCH 13/34] include pt.dbc for powertrain can parsing --- scripts/daq/dbc/pt.dbc | 92 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 scripts/daq/dbc/pt.dbc diff --git a/scripts/daq/dbc/pt.dbc b/scripts/daq/dbc/pt.dbc new file mode 100644 index 000000000..9d74735bf --- /dev/null +++ b/scripts/daq/dbc/pt.dbc @@ -0,0 +1,92 @@ +VERSION "" + + +NS_ : + NS_DESC_ + CM_ + BA_DEF_ + BA_ + VAL_ + CAT_DEF_ + CAT_ + FILTER + BA_DEF_DEF_ + EV_DATA_ + ENVVAR_DATA_ + SGTYPE_ + SGTYPE_VAL_ + BA_DEF_SGTYPE_ + BA_SGTYPE_ + SIG_TYPE_REF_ + VAL_TABLE_ + SIG_GROUP_ + SIG_VALTYPE_ + SIGTYPE_VALTYPE_ + BO_TX_BU_ + BA_DEF_REL_ + BA_REL_ + BA_DEF_DEF_REL_ + BU_SG_REL_ + BU_EV_REL_ + BU_BO_REL_ + SG_MUL_VAL_ + +BS_: + +BU_: INV1 INV2 FC + +BO_ 643 Inv1_ActualValues1: 8 INV1 + SG_ bSystemReady : 8|1@1+ (1,0) [0|0] "" FC + SG_ bError : 9|1@1+ (1,0) [0|0] "" FC + SG_ bWarn : 10|1@1+ (1,0) [0|0] "" FC + SG_ bQuitDcOn : 11|1@1+ (1,0) [0|0] "" FC + SG_ bDcOn : 12|1@1+ (1,0) [0|0] "" FC + SG_ bQuitInverterOn : 13|1@1+ (1,0) [0|0] "" FC + SG_ bInverterOn : 14|1@1+ (1,0) [0|0] "" FC + SG_ bDerating : 15|1@1+ (1,0) [0|0] "" FC + SG_ ActualVelocity : 16|16@1- (1,0) [0|0] "rpm" FC + SG_ TorqueCurrent : 32|16@1- (1,0) [0|0] "" FC + SG_ MagnetizingCurrent : 48|16@1- (1,0) [0|0] "" FC + +BO_ 644 Inv2_ActualValues1: 8 INV2 + SG_ bSystemReady : 8|1@1+ (1,0) [0|0] "" FC + SG_ bError : 9|1@1+ (1,0) [0|0] "" FC + SG_ bWarn : 10|1@1+ (1,0) [0|0] "" FC + SG_ bQuitDcOn : 11|1@1+ (1,0) [0|0] "" FC + SG_ bDcOn : 12|1@1+ (1,0) [0|0] "" FC + SG_ bQuitInverterOn : 13|1@1+ (1,0) [0|0] "" FC + SG_ bInverterOn : 14|1@1+ (1,0) [0|0] "" FC + SG_ bDerating : 15|1@1+ (1,0) [0|0] "" FC + SG_ ActualVelocity : 16|16@1- (1,0) [0|0] "rpm" FC + SG_ TorqueCurrent : 32|16@1- (1,0) [0|0] "" FC + SG_ MagnetizingCurrent : 48|16@1- (1,0) [0|0] "" FC + +BO_ 645 Inv1_ActualValues2: 8 INV1 + SG_ TempMotor : 0|16@1- (0.1,0) [0|0] "degC" FC + SG_ TempInverter : 16|16@1- (0.1,0) [0|0] "degC" FC + SG_ ErrorInfo : 32|16@1+ (1,0) [0|0] "" FC + SG_ TempIGBT : 48|16@1- (0.1,0) [0|0] "degC" FC + +BO_ 646 Inv2_ActualValues2: 8 INV2 + SG_ TempMotor : 0|16@1- (0.1,0) [0|0] "degC" FC + SG_ TempInverter : 16|16@1- (0.1,0) [0|0] "degC" FC + SG_ ErrorInfo : 32|16@1+ (1,0) [0|0] "" FC + SG_ TempIGBT : 48|16@1- (0.1,0) [0|0] "degC" FC + +BO_ 388 Inv1_Setpoints1: 8 FC + SG_ bInverterOn : 8|1@1+ (1,0) [0|0] "" INV2 + SG_ bDcOn : 9|1@1+ (1,0) [0|0] "" INV2 + SG_ bEnable : 10|1@1+ (1,0) [0|0] "" INV2 + SG_ bErrorReset : 11|1@1+ (1,0) [0|0] "" INV2 + SG_ TargetVelocity : 16|16@1- (1,0) [0|0] "rpm" INV2 + SG_ TorqueLimitPositiv : 32|16@1- (1,0) [0|0] "0.1%" INV2 + SG_ TorqueLimitNegativ : 48|16@1- (1,0) [0|0] "0.1%" INV2 + +BO_ 389 Inv2_Setpoints1: 8 FC + SG_ bInverterOn : 8|1@1+ (1,0) [0|0] "" INV2 + SG_ bDcOn : 9|1@1+ (1,0) [0|0] "" INV2 + SG_ bEnable : 10|1@1+ (1,0) [0|0] "" INV2 + SG_ bErrorReset : 11|1@1+ (1,0) [0|0] "" INV2 + SG_ TargetVelocity : 16|16@1- (1,0) [0|0] "rpm" INV2 + SG_ TorqueLimitPositiv : 32|16@1- (1,0) [0|0] "0.1%" INV2 + SG_ TorqueLimitNegativ : 48|16@1- (1,0) [0|0] "0.1%" INV2 From 64caf00f59896131b05cfad73faeacb187d0baa9 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sat, 9 May 2026 23:14:55 -0400 Subject: [PATCH 14/34] testing logger in linux-vm --- scripts/daq/logger.py | 208 +++++++++++++++++++------------------ scripts/daq/setup.sh | 89 ++++++++++++++++ scripts/daq/test_influx.py | 150 ++++++++++++++++++++++++++ 3 files changed, 344 insertions(+), 103 deletions(-) create mode 100644 scripts/daq/setup.sh create mode 100644 scripts/daq/test_influx.py diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index c9a973468..c58a8f444 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -3,8 +3,8 @@ Raspberry Pi CAN Data Logger. Reads CAN messages from one or two SocketCAN interfaces via candump, decodes -them using the vehicle and powertrain DBC files, and writes decoded signals -to a timestamped CSV file. +them using the vehicle and powertrain DBC files, writes decoded signals to a +timestamped CSV file, and streams key telemetry and fault events to InfluxDB. Usage: python logger.py [interface] [--pt-interface PT_INTERFACE] @@ -27,8 +27,7 @@ from influxdb_client import InfluxDBClient, Point from dotenv import load_dotenv -load_dotenv() -INFLUX_TOKEN = os.getenv('INFLUX_TOKEN') +load_dotenv(override=True) # --------------------------------------------------------------------------- # Target message IDs — what we care about logging. @@ -51,6 +50,8 @@ # So the IDs here must match cantools (29-bit, no bit-31 flag). # --------------------------------------------------------------------------- TARGET_IDS = { + 202, # FcAlerts (veh) - 13 boolean fault flags + 211, # LvStatus (veh) - ImdFault, BmsFault 230, # DashCommand (veh) - Speed 300, # Accumulator_Soc (veh) - battery state 310, # SuspensionTravel34 (veh) - linear pots @@ -58,73 +59,100 @@ 402, # BppsSteerDebug (veh) - brake pedal + steering pots 643, # Inv1_ActualValues1 (pt) - left motor velocity 644, # Inv2_ActualValues1 (pt) - right motor velocity - 645, # Inv1_ActualValues2 (pt) - motor/inverter temps - 646, # Inv2_ActualValues2 (pt) - motor/inverter temps + 645, # Inv1_ActualValues2 (pt) - left motor/inverter temps + 646, # Inv2_ActualValues2 (pt) - right motor/inverter temps 773, # GnssAttitude (veh) - IMU roll/pitch/heading 777, # GnssImu (veh) - IMU accelerations + rates 1572, # Pack_State (veh) - battery current/voltage 1573, # Pack_SOC (veh) - battery state of charge - 406451072, # BmsBroadcast (veh) - battery pack temps (extended ID 0x1839F380) - 419361278, # ThermistorBroadcast (veh) - thermistor temps (extended ID 0x18FEF1FE) + 406451072, # BmsBroadcast (veh) - pack temps per segment (extended ID 0x1839F380) + 419361278, # ThermistorBroadcast (veh) - thermistor temps (extended ID 0x18FEF1FE) } +# Signal names written to InfluxDB — must match DBC signal names exactly. IMPORTANT_SIGNALS = { - "battery_voltage", - "pack_current", - "motor_rpm", - "motor_temp", - "inverter_temp", - "soc" + "Pack_Inst_Voltage", # HV battery voltage (V) — Pack_State (1572) + "Pack_Current", # HV pack current (A) — Pack_State (1572) + "Pack_SOC", # state of charge (%) — Pack_SOC (1573) + "ActualVelocity", # motor RPM — Inv1/2_ActualValues1 (643/644) + "TempMotor", # motor temperature (°C) — Inv1/2_ActualValues2 (645/646) + "TempInverter", # inverter temperature (°C) — Inv1/2_ActualValues2 (645/646) + "TempIGBT", # IGBT temperature (°C) — Inv1/2_ActualValues2 (645/646) + "Speed", # vehicle speed (mph) — DashCommand (230) + "HighThermValue", # segment max temp (°C) — BmsBroadcast (0x1839F380) + "LowThermValue", # segment min temp (°C) — BmsBroadcast + "AvgThermValue", # segment avg temp (°C) — BmsBroadcast } +# Boolean fault signals from FcAlerts (202) and LvStatus (211). +# Value == 1 means the fault is active. Each maps to (system, description, severity). +FAULT_SIGNALS = { + # FcAlerts (202) + "AppsImplausible": ("APPS", "Implausible Pedal Signal", "CRITICAL"), + "AccumulatorLowSoc": ("BMS", "Low State of Charge", "WARNING"), + "AccumulatorContactorWrongState": ("BMS", "Contactor Wrong State", "CRITICAL"), + "MotorRetriesExceeded": ("Motor", "Retries Exceeded", "CRITICAL"), + "LeftMotorStartingError": ("Motor", "Left Motor Start Error", "CRITICAL"), + "RightMotorStartingError": ("Motor", "Right Motor Start Error", "CRITICAL"), + "LeftMotorRunningError": ("Motor", "Left Motor Running Error", "CRITICAL"), + "RightMotorRunningError": ("Motor", "Right Motor Running Error", "CRITICAL"), + "DashboardBootTimeout": ("Dashboard", "Boot Timeout", "WARNING"), + "CanTxError": ("CAN", "TX Error", "WARNING"), + "EV47Active": ("Safety", "EV4.7 Rule Active", "CRITICAL"), + "NoInv1Can": ("Inverter", "No INV1 CAN Comm", "CRITICAL"), + "NoInv2Can": ("Inverter", "No INV2 CAN Comm", "CRITICAL"), + # LvStatus (211) + "ImdFault": ("IMD", "Isolation Fault", "CRITICAL"), + "BmsFault": ("BMS", "BMS Fault", "CRITICAL"), +} + +# CAN IDs that produce the same signal names for left and right — tagged "inverter". +INV1_IDS = {643, 645} +INV2_IDS = {644, 646} + # candump -L log format: (timestamp) interface hex_id#hex_data CANDUMP_LINE_RE = re.compile( r'\((\d+\.\d+)\)\s+\S+\s+([0-9A-Fa-f]+)#([0-9A-Fa-f]*)' ) +""" Influx DB Helper Functions """ +# ------------------------------------------------------------------------- client = InfluxDBClient( url=os.getenv("INFLUX_URL"), token=os.getenv("INFLUX_TOKEN"), - org=os.getenv("INFLUX_ORG") + org=os.getenv("INFLUX_ORG"), + verify_ssl=False, ) write_api = client.write_api() -def write_to_influx(sig_name, value, timestamp): - point = Point("car_data") \ - .tag("signal", sig_name) \ - .field("value", value) \ +def write_to_influx(sig_name: str, value: float, timestamp: float, extra_tags: dict = None) -> None: + point = ( + Point("car_data") + .tag("signal", sig_name) + .field("value", float(value)) .time(int(timestamp * 1e9)) - + ) + if extra_tags: + for k, v in extra_tags.items(): + point = point.tag(k, v) write_api.write(bucket=os.getenv("INFLUX_BUCKET"), record=point) -def write_fault_to_influx(fault, severity, timestamp): - point = Point("faults") \ - .tag("fault", fault) \ - .tag("severity", severity) \ - .time(int(timestamp * 1e9)) +def write_fault_to_influx(system: str, fault: str, severity: str, timestamp: float) -> None: + point = ( + Point("faults") + .tag("system", system) + .tag("fault", fault) + .tag("severity", severity) + .field("active", 1) + .time(int(timestamp * 1e9)) + ) write_api.write(bucket=os.getenv("INFLUX_BUCKET"), record=point) - -def map_fault(code): - mapping = { - 0: "No Fault", - 1: "Low Voltage", - 2: "Over Temp", - 3: "Over Current" - } - return mapping.get(code, "Unknown") - -def get_severity(code): - if code == 0: - return "NORMAL" - elif code == 1: - return "WARNING" - else: - return "CRITICAL" +# ------------------------------------------------------------------------- def load_dbc(veh_dbc: Path, pt_dbc: Path | None) -> cantools.database.Database: - """Load veh.dbc and optionally pt.dbc into a single cantools Database.""" + """Load veh.dbc and pt.dbc into a single cantools Database.""" db = cantools.database.load_file(str(veh_dbc)) if pt_dbc and pt_dbc.exists(): with open(pt_dbc, encoding='utf-8') as f: @@ -147,8 +175,8 @@ def parse_candump_line(line: str) -> tuple[float, int, bytes] | None: Parse a candump -L line into (timestamp_s, can_id, data_bytes). candump output format: - (1709650000.123456) vcan0 136#8E91 - (1709650000.456789) vcan0 1839F380#C2001816060A0041 + (1709650.123456) vcan0 136#8E91 + (1709650.456789) vcan0 1839F380#C2001816060A0041 The hex ID is printed as-is by candump — no bit-31 flag, just the raw value. For extended frames this is 8 hex digits; for standard frames @@ -158,7 +186,7 @@ def parse_candump_line(line: str) -> tuple[float, int, bytes] | None: if not m: return None ts_str, hex_id, hex_data = m.groups() - if len(hex_data) % 2 != 0: + if len(hex_data) % 2 != 0: # valid hex has to be even number of chars return None return float(ts_str), int(hex_id, 16), bytes.fromhex(hex_data) @@ -174,19 +202,18 @@ def decode_frame( Returns (message_name, {signal_name: (value, unit)}) or None if the message is not in the DBC or decoding fails. """ - msg = next((m for m in db.messages if m.frame_id == can_id), None) + msg = next((m for m in db.messages if m.frame_id == can_id), None) #cycle thru dbc to find matched can id if msg is None: - print(f"NO MESSAGE FOUND FOR ID: {can_id}") return None try: - decoded = msg.decode(data) + decoded = msg.decode(data) # decode into three sections except Exception: return None out = { - sig.name: (decoded[sig.name], sig.unit or '') + sig.name: (decoded[sig.name], sig.unit or '') for sig in msg.signals if sig.name in decoded and decoded[sig.name] is not None - } + } # add units to parsed output (ex. v, A, C) return (msg.name, out) if out else None @@ -217,78 +244,47 @@ def _candump_reader( processes.append(proc) for line in proc.stdout: - out_queue.put(line) + out_queue.put(line) # put can message into queue out_queue.put(None) def main() -> int: parser = argparse.ArgumentParser( - description='CAN data logger: candump -> DBC decode -> CSV' - ) - parser.add_argument( - 'interface', - nargs='?', - default='vcan0', - help='Primary SocketCAN interface (default: vcan0)', - ) - parser.add_argument( - '--pt-interface', - default=None, - metavar='INTERFACE', - help='Optional second interface for powertrain bus (e.g. can1)', - ) - parser.add_argument( - '--veh-dbc', - type=Path, - default=None, - help='Path to veh.dbc (default: /dbc/veh.dbc)', - ) - parser.add_argument( - '--pt-dbc', - type=Path, - default=None, - help='Path to pt.dbc (default: /projects/pt.dbc)', - ) - parser.add_argument( - '--log-dir', - type=Path, - default=None, - help='Output directory for CSV logs (default: /logs)', + description='CAN data logger: candump -> DBC decode -> CSV + InfluxDB' ) + parser.add_argument( '--all', action='store_true', help='Log every decodable message, not just target signals', ) + args = parser.parse_args() script_dir = Path(__file__).resolve().parent - veh_dbc = args.veh_dbc or (script_dir / 'dbc' / 'veh.dbc') - pt_dbc = args.pt_dbc or (script_dir.parent.parent / 'projects' / 'pt.dbc') - log_dir = args.log_dir or (script_dir / 'logs') + + # Fixed deployment configuration + veh_dbc = script_dir / 'dbc' / 'veh.dbc' + pt_dbc = script_dir / 'dbc' / 'pt.dbc' + log_dir = script_dir / 'logs' + + interfaces = ['can0', 'can1'] if not veh_dbc.exists(): print(f'Error: veh.dbc not found at {veh_dbc}', file=sys.stderr) return 1 db = load_dbc(veh_dbc, pt_dbc) - - # for msg in db.messages: - #print(msg.name, hex(msg.frame_id), [s.name for s in msg.signals]) log_path, csv_file = open_csv(log_dir) - interfaces = [args.interface] - if args.pt_interface: - interfaces.append(args.pt_interface) - print(f'Logging to {log_path}', file=sys.stderr) print(f'Interfaces: {", ".join(interfaces)}. Press Ctrl+C to stop.', file=sys.stderr) - # Use a queue so frames from multiple candump processes merge into one loop. line_queue: queue.Queue = queue.Queue() processes: list = [] + # Start one CAN reader thread per interface for iface in interfaces: t = threading.Thread( target=_candump_reader, @@ -307,7 +303,6 @@ def cleanup(_sig=None, _frame=None): signal.signal(signal.SIGINT, cleanup) signal.signal(signal.SIGTERM, cleanup) - # Count how many None sentinels we expect (one per interface thread). num_interfaces = len(interfaces) done = 0 @@ -321,30 +316,37 @@ def cleanup(_sig=None, _frame=None): if not parsed: continue timestamp, can_id, data = parsed - print(f"RAW: {can_id} {data.hex()}") + if not args.all and can_id not in TARGET_IDS: continue result = decode_frame(db, can_id, data) if not result: - print(f"DECODE FAILED -> ID: {hex(can_id)} DATA: {data.hex()}") continue msg_name, signals = result id_hex = f'0x{can_id:X}' + + # Build extra tags for signals that need inverter or segment identity. + extra_tags: dict = {} + if can_id in INV1_IDS: + extra_tags["inverter"] = "inv1" + elif can_id in INV2_IDS: + extra_tags["inverter"] = "inv2" + elif can_id == 406451072 and "ThermModuleNum" in signals: + extra_tags["module"] = str(int(signals["ThermModuleNum"][0])) + for sig_name, (value, unit) in signals.items(): csv_file.write( f'{timestamp},{id_hex},{msg_name},{sig_name},{value},"{unit}"\n' ) - if sig_name == "fault_code": - fault_text = map_fault(value) - severity = get_severity(value) - - write_fault_to_influx(fault_text, severity, timestamp) + if sig_name in FAULT_SIGNALS and value == 1: + system, fault_desc, severity = FAULT_SIGNALS[sig_name] + write_fault_to_influx(system, fault_desc, severity, timestamp) if sig_name in IMPORTANT_SIGNALS: - write_to_influx(sig_name, value, timestamp) + write_to_influx(sig_name, float(value), timestamp, extra_tags) csv_file.close() return 0 diff --git a/scripts/daq/setup.sh b/scripts/daq/setup.sh new file mode 100644 index 000000000..a8a78d390 --- /dev/null +++ b/scripts/daq/setup.sh @@ -0,0 +1,89 @@ +#!/bin/bash +# DAQ setup script for Raspberry Pi (ARM64). +# Installs InfluxDB 2.x and Grafana natively — no Docker. +# Run once on a fresh Pi: bash setup.sh + +set -e + +echo "=== MAC Formula DAQ Setup ===" +echo "" + +# ── System packages ────────────────────────────────────────────────────────── +echo "[1/6] Installing system packages..." +sudo apt update -q +sudo apt install -y python3 python3-venv python3-pip can-utils curl gnupg2 + +# ── InfluxDB 2.x (native) ──────────────────────────────────────────────────── +echo "[2/6] Installing InfluxDB 2.x..." +curl -fsSL https://repos.influxdata.com/influxdata-archive_compat.key \ + | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg +echo "deb [signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg] \ + https://repos.influxdata.com/debian stable main" \ + | sudo tee /etc/apt/sources.list.d/influxdata.list +sudo apt update -q +sudo apt install -y influxdb2 + +sudo systemctl enable --now influxdb +echo " InfluxDB running at http://localhost:8086" + +# ── Grafana (from bundled ARM64 .deb) ──────────────────────────────────────── +echo "[3/6] Installing Grafana from bundled .deb..." +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DEB_FILE="$SCRIPT_DIR/grafana_11.0.0_arm64.deb" + +if [ ! -f "$DEB_FILE" ]; then + echo "ERROR: $DEB_FILE not found. Ensure the .deb is in the same directory as this script." + exit 1 +fi + +sudo apt install -y "$DEB_FILE" +sudo systemctl enable --now grafana-server +echo " Grafana running at http://localhost:3000 (admin / admin)" + +# ── CAN interfaces ──────────────────────────────────────────────────────────── +echo "[4/6] Configuring CAN interfaces..." +# Load the CAN kernel module on boot +if ! grep -q "^can$" /etc/modules 2>/dev/null; then + echo "can" | sudo tee -a /etc/modules +fi +if ! grep -q "^can_raw$" /etc/modules 2>/dev/null; then + echo "can_raw" | sudo tee -a /etc/modules +fi + +# Bring up can0 at 500 kbit/s (vehicle CAN bus) +sudo ip link set can0 up type can bitrate 500000 2>/dev/null \ + && echo " can0 up at 500 kbit/s" \ + || echo " can0 not available (OK if running without hardware)" + +# Bring up can1 at 500 kbit/s (powertrain CAN bus) — optional +sudo ip link set can1 up type can bitrate 500000 2>/dev/null \ + && echo " can1 up at 500 kbit/s" \ + || echo " can1 not available (OK if single-bus setup)" + +# ── Python environment ──────────────────────────────────────────────────────── +echo "[5/6] Setting up Python virtual environment..." +cd "$SCRIPT_DIR" +python3 -m venv venv +source venv/bin/activate +pip install --quiet -r requirements.txt +deactivate +echo " venv created. Activate with: source $SCRIPT_DIR/venv/bin/activate" + +# ── Final instructions ──────────────────────────────────────────────────────── +echo "" +echo "[6/6] Setup complete!" +echo "" +echo "Next steps:" +echo " 1. Open http://localhost:8086 in a browser" +echo " → Complete InfluxDB initial setup: org=macformula, bucket=macfe" +echo " → Generate an All Access API token" +echo " 2. Edit $SCRIPT_DIR/.env and fill in:" +echo " INFLUX_URL=http://localhost:8086" +echo " INFLUX_TOKEN=" +echo " INFLUX_ORG=macformula" +echo " INFLUX_BUCKET=macfe" +echo " 3. Open http://localhost:3000 (admin/admin) → import grafana_dashboard.json" +echo " 4. Run the logger:" +echo " source $SCRIPT_DIR/venv/bin/activate" +echo " python $SCRIPT_DIR/logger.py can0 --pt-interface can1" +echo "" diff --git a/scripts/daq/test_influx.py b/scripts/daq/test_influx.py new file mode 100644 index 000000000..0d2943f22 --- /dev/null +++ b/scripts/daq/test_influx.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +""" +Simulates CAN signal data being written to InfluxDB. +Run this to verify the Python to InfluxDB connection without needing a CAN bus. +Uses the same measurement/tag/field structure as logger.py. +""" + +import os +import time +import math +import random +import urllib3 +from dotenv import load_dotenv +from influxdb_client import InfluxDBClient, Point +from influxdb_client.client.write_api import SYNCHRONOUS + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +load_dotenv(override=True) + +client = InfluxDBClient( + url=os.getenv("INFLUX_URL"), + token=os.getenv("INFLUX_TOKEN"), + org=os.getenv("INFLUX_ORG"), + verify_ssl=False, +) +write_api = client.write_api(write_options=SYNCHRONOUS) +bucket = os.getenv("INFLUX_BUCKET") + + +def write_signal(sig_name, value): + point = ( + Point("car_data") + .tag("signal", sig_name) + .field("value", float(value)) + ) + write_api.write(bucket=bucket, record=point) + + +def write_fault(system, fault, severity): + point = ( + Point("faults") + .tag("system", system) + .tag("fault", fault) + .tag("severity", severity) + .field("active", 1) + ) + write_api.write(bucket=bucket, record=point) + + +def main(): + print(f"Connecting to InfluxDB at {os.getenv('INFLUX_URL')} ...") + print(f"Org: {os.getenv('INFLUX_ORG')} Bucket: {bucket}\n") + + # Verify connection with a test write + try: + test_point = Point("car_data").tag("signal", "_test").field("value", 0.0) + write_api.write(bucket=bucket, record=test_point) + print("Connection OK — test write succeeded.\n") + except Exception as e: + print(f"ERROR: Could not write to InfluxDB — {e}") + print("Check your token, org, bucket, and URL in .env") + return + + print("Writing simulated CAN signals (Ctrl+C to stop)...\n") + + t = 0 + try: + while True: + # Simulate values using actual DBC signal names so the dashboard queries match. + inv1_rpm = 2000 + 1500 * math.sin(t * 0.3) + random.uniform(-50, 50) + inv2_rpm = 1800 + 1400 * math.sin(t * 0.3 + 0.2) + random.uniform(-50, 50) + battery_v = 400 + 10 * math.sin(t * 0.05) + random.uniform(-1, 1) + soc = max(20, 90 - t * 0.2 + random.uniform(-0.5, 0.5)) + motor_temp = 40 + 20 * (1 - math.exp(-t * 0.02)) + random.uniform(-1, 1) + inverter_temp = 35 + 15 * (1 - math.exp(-t * 0.015)) + random.uniform(-1, 1) + igbt_temp = inverter_temp + random.uniform(-2, 2) + pack_current = 50 + 30 * math.sin(t * 0.4) + random.uniform(-5, 5) + speed = 60 + 25 * math.sin(t * 0.05) + random.uniform(-2, 2) + + # Scalar signals (no extra tags needed) + for name, value in [ + ("Pack_Inst_Voltage", battery_v), + ("Pack_Current", pack_current), + ("Pack_SOC", soc), + ("Speed", speed), + ]: + write_signal(name, value) + + # Motor signals — write once per inverter with the inverter tag + for inverter, rpm, mtemp, itemp, igbt in [ + ("inv1", inv1_rpm, motor_temp, inverter_temp, igbt_temp), + ("inv2", inv2_rpm, motor_temp + 1.5, inverter_temp + 1.0, igbt_temp + 1.0), + ]: + for sig_name, val in [ + ("ActualVelocity", rpm), + ("TempMotor", mtemp), + ("TempInverter", itemp), + ("TempIGBT", igbt), + ]: + point = ( + Point("car_data") + .tag("signal", sig_name) + .tag("inverter", inverter) + .field("value", float(val)) + ) + write_api.write(bucket=bucket, record=point) + + # Battery segment temps — 6 modules, each with High/Low/Avg + base_temp = 35 + 5 * (1 - math.exp(-t * 0.01)) + for module in range(6): + spread = module * 0.8 + random.uniform(-0.5, 0.5) + for sig_name, val in [ + ("HighThermValue", base_temp + spread + 2), + ("LowThermValue", base_temp + spread - 2), + ("AvgThermValue", base_temp + spread), + ]: + point = ( + Point("car_data") + .tag("signal", sig_name) + .tag("module", str(module)) + .field("value", float(val)) + ) + write_api.write(bucket=bucket, record=point) + + # Occasionally write a simulated fault + if t > 0 and int(t) % 15 == 0: + write_fault("BMS", "Low State of Charge", "WARNING") + print(f" t={t:.0f}s Wrote fault: BMS / Low State of Charge / WARNING") + if t > 0 and int(t) % 30 == 0: + write_fault("Motor", "Left Motor Running Error", "CRITICAL") + print(f" t={t:.0f}s Wrote fault: Motor / Left Motor Running Error / CRITICAL") + + print( + f" t={t:.0f}s inv1={inv1_rpm:.0f}rpm inv2={inv2_rpm:.0f}rpm " + f"v={battery_v:.1f}V soc={soc:.1f}% " + f"mtemp={motor_temp:.1f}°C curr={pack_current:.1f}A spd={speed:.1f}mph" + ) + + t += 1 + time.sleep(1) + + except KeyboardInterrupt: + print("\nStopped.") + finally: + client.close() + + +if __name__ == "__main__": + main() From 4c58bbc7dc2853407b272b68890d2dbdeeef592f Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 00:03:03 -0400 Subject: [PATCH 15/34] logger.py issues, need to fix on vm --- scripts/daq/logger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index c58a8f444..053260fd5 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -263,7 +263,7 @@ def main() -> int: script_dir = Path(__file__).resolve().parent - # Fixed deployment configuration + # Fixed deployment config, we won't be changing any of these veh_dbc = script_dir / 'dbc' / 'veh.dbc' pt_dbc = script_dir / 'dbc' / 'pt.dbc' log_dir = script_dir / 'logs' From d7057562f5dd5c71d2f699a310212267d61b9961 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 00:04:39 -0400 Subject: [PATCH 16/34] . --- scripts/daq/logger.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index 053260fd5..414c6b113 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -7,9 +7,9 @@ timestamped CSV file, and streams key telemetry and fault events to InfluxDB. Usage: - python logger.py [interface] [--pt-interface PT_INTERFACE] - python logger.py vcan0 - python logger.py can0 --pt-interface can1 + python logger.py # Pi: reads can0 can1 + python logger.py --interfaces vcan0 vcan1 # VM: virtual CAN + python logger.py --interfaces can0 # single interface """ import argparse @@ -258,17 +258,23 @@ def main() -> int: action='store_true', help='Log every decodable message, not just target signals', ) + parser.add_argument( + '--interfaces', + nargs='+', + default=['can0', 'can1'], + metavar='IFACE', + help='CAN interfaces to read from (default: can0 can1)', + ) args = parser.parse_args() script_dir = Path(__file__).resolve().parent - # Fixed deployment config, we won't be changing any of these veh_dbc = script_dir / 'dbc' / 'veh.dbc' pt_dbc = script_dir / 'dbc' / 'pt.dbc' log_dir = script_dir / 'logs' - interfaces = ['can0', 'can1'] + interfaces = args.interfaces if not veh_dbc.exists(): print(f'Error: veh.dbc not found at {veh_dbc}', file=sys.stderr) From 666581b2e09360390b4cb7220fd9d4aa2197b152 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 00:51:12 -0400 Subject: [PATCH 17/34] README.md, sh files, .deb file is too large :( --- scripts/daq/README.md | 199 +++++++++++++++++++++++++++++++++++++++- scripts/daq/setup.sh | 116 ++++++++++++++++------- scripts/daq/setup_vm.sh | 61 ++++++++++++ 3 files changed, 336 insertions(+), 40 deletions(-) create mode 100644 scripts/daq/setup_vm.sh diff --git a/scripts/daq/README.md b/scripts/daq/README.md index fa674185c..060144cb0 100644 --- a/scripts/daq/README.md +++ b/scripts/daq/README.md @@ -1,12 +1,201 @@ -This is just a temporary working directory for daq... +# DAQ — Data Acquisition System + +Real-time CAN telemetry for the MAC Formula Electric racecar. +Data flows from two CAN buses on the Raspberry Pi → InfluxDB → Grafana dashboard. + +--- + +## Architecture -Generate Go DBC bindings: ``` +Racecar CAN buses + can0 (Vehicle bus — FC, LVC, TMS, BMS, GPS) + can1 (Powertrain bus — Inverter 1, Inverter 2) + │ + ▼ + Raspberry Pi 4 + ┌─────────────────────────────────┐ + │ candump -L can0 / can1 │ raw CAN frames + │ │ │ + │ logger.py │ decode via veh.dbc + pt.dbc + │ │ │ + │ CSV logs (logs/can_log_*.csv) │ full signal archive + │ InfluxDB (localhost:8086) │ key signals + faults + └─────────────────────────────────┘ + │ + ▼ (network / same machine) + Grafana (localhost:3000 or Grafana Cloud) + ┌─────────────────────────────────┐ + │ Vehicle Safety row │ + │ Performance row │ + │ Segment Temperatures row │ + └─────────────────────────────────┘ +``` + +--- + + +--- + +## InfluxDB Data Schema + +All telemetry goes into two measurements: + +### `car_data` — key signal values +| Tag | Values | Description | +|---|---|---| +| `signal` | see table below | DBC signal name | +| `inverter` | `inv1`, `inv2` | set only for motor/inverter signals | +| `module` | `0`–`5` | set only for BmsBroadcast segment temps | + +Field: `value` (float) + +### `faults` — active fault events +| Tag | Example values | Description | +|---|---|---| +| `system` | `BMS`, `Motor`, `Inverter`, `IMD`, `APPS`, `CAN`, `Safety`, `Dashboard` | subsystem that raised the fault | +| `fault` | `"Low State of Charge"`, `"No INV1 CAN Comm"` | human-readable description | +| `severity` | `WARNING`, `CRITICAL` | impact level | + +Field: `active` (int, always 1) + +### Signal name reference + +| DBC signal | Source message | CAN ID | Description | +|---|---|---|---| +| `Pack_Inst_Voltage` | Pack_State | 1572 | HV battery voltage (V) | +| `Pack_Current` | Pack_State | 1572 | HV pack current (A) | +| `Pack_SOC` | Pack_SOC | 1573 | State of charge (%) | +| `ActualVelocity` | Inv1/2_ActualValues1 | 643/644 | Motor RPM — tagged `inverter=inv1/inv2` | +| `TempMotor` | Inv1/2_ActualValues2 | 645/646 | Motor temperature (°C) — tagged by inverter | +| `TempInverter` | Inv1/2_ActualValues2 | 645/646 | Inverter temperature (°C) — tagged by inverter | +| `TempIGBT` | Inv1/2_ActualValues2 | 645/646 | IGBT temperature (°C) — tagged by inverter | +| `Speed` | DashCommand | 230 | Vehicle speed (mph from DBC) | +| `HighThermValue` | BmsBroadcast | 0x1839F380 | Segment max temp (°C) — tagged `module=0..5` | +| `LowThermValue` | BmsBroadcast | 0x1839F380 | Segment min temp (°C) — tagged `module=0..5` | +| `AvgThermValue` | BmsBroadcast | 0x1839F380 | Segment avg temp (°C) — tagged `module=0..5` | + +--- + +## Fault Detection + +Faults are **boolean bits** in two CAN messages, not a single fault code signal. + +**FcAlerts (ID 202)** — Front Controller alerts: +| Signal | System | Description | Severity | +|---|---|---|---| +| `AppsImplausible` | APPS | Implausible Pedal Signal | CRITICAL | +| `AccumulatorLowSoc` | BMS | Low State of Charge | WARNING | +| `AccumulatorContactorWrongState` | BMS | Contactor Wrong State | CRITICAL | +| `MotorRetriesExceeded` | Motor | Retries Exceeded | CRITICAL | +| `LeftMotorStartingError` | Motor | Left Motor Start Error | CRITICAL | +| `RightMotorStartingError` | Motor | Right Motor Start Error | CRITICAL | +| `LeftMotorRunningError` | Motor | Left Motor Running Error | CRITICAL | +| `RightMotorRunningError` | Motor | Right Motor Running Error | CRITICAL | +| `DashboardBootTimeout` | Dashboard | Boot Timeout | WARNING | +| `CanTxError` | CAN | TX Error | WARNING | +| `EV47Active` | Safety | EV4.7 Rule Active | CRITICAL | +| `NoInv1Can` | Inverter | No INV1 CAN Comm | CRITICAL | +| `NoInv2Can` | Inverter | No INV2 CAN Comm | CRITICAL | + +**LvStatus (ID 211)** — LV Controller status: +| Signal | System | Description | Severity | +|---|---|---|---| +| `ImdFault` | IMD | Isolation Fault | CRITICAL | +| `BmsFault` | BMS | BMS Fault | CRITICAL | + +When any of these bits is `1`, `logger.py` writes a point to the `faults` measurement with the system, description, and severity. The Grafana Fault Log table shows all faults in the selected time range with colour-coded severity. + +--- + +## Raspberry Pi Setup (Production) + +Run once on a fresh Pi: + +```bash +cd scripts/daq +bash setup.sh +``` + +The script: +1. Installs `python3`, `can-utils`, `influxdb2`, and Grafana from the bundled `.deb` +2. Enables InfluxDB and Grafana as systemd services (auto-start on boot) +3. Brings up `can0` and `can1` at 500 kbit/s +4. Creates a Python virtualenv and installs requirements + +After running `setup.sh`: + +1. Open `http://localhost:8086` → complete InfluxDB setup: + - Username: `admin`, Password: (choose one) + - Organization: `macformula` + - Bucket: `macfe` + - Generate an **All Access API token** + +2. Create `.env` in this directory: + ``` + INFLUX_URL=http://localhost:8086 + INFLUX_TOKEN= + INFLUX_ORG=macformula + INFLUX_BUCKET=macfe + ``` + +3. Open `http://localhost:3000` (admin/admin) → import `grafana_dashboard.json` + +4. Start logging: + ```bash + source venv/bin/activate + python logger.py + ``` + +--- + +## Dev Setup (Windows — no Pi, no CAN bus) + +Uses InfluxDB Cloud (free tier) and Grafana Cloud instead of local installs. + +1. Create accounts at `cloud2.influxdata.com` and `grafana.com` +2. In InfluxDB Cloud: create bucket `macfe`, generate All Access token +3. Create `.env`: + ``` + INFLUX_URL=https://.aws.cloud2.influxdata.com + INFLUX_TOKEN= + INFLUX_ORG= + INFLUX_BUCKET=macfe + ``` +4. Install Python dependencies: + ```bash + python -m venv venv + venv\Scripts\activate + pip install -r requirements.txt + ``` +5. Run the simulated data writer to verify the connection: + ```bash + python test_influx.py + ``` + Should print `Connection OK — test write succeeded.` and stream fake sensor values. + +6. In Grafana Cloud: add InfluxDB datasource (Flux, same credentials as `.env`), then import `grafana_dashboard.json`. + +--- + +## Adding New Signals + +1. Add the **exact DBC signal name** to `IMPORTANT_SIGNALS` in `logger.py`. +2. If the signal comes from Inv1/Inv2, no extra work needed — the inverter tag is added automatically based on CAN ID. +3. Add a corresponding Flux query panel to `grafana_dashboard.json` (or add it in the Grafana UI and export). + +To add a new fault signal, add an entry to `FAULT_SIGNALS` in `logger.py` with `(system, description, severity)`. The signal must be a boolean bit (0/1) decoded from a message already in `TARGET_IDS`. + +--- + +## Go Bindings (legacy notes) + +The Go files (`main.go`, `can.go`, `heartbeat.go`, `telemetry.go`) implement a Go-based CAN reader for the heartbeat system. The DBC bindings are generated with: + +```bash go get -u go.einride.tech/can cd dbc go run go.einride.tech/can/cmd/cantool generate ./ ../generated/ ``` -Had to remove the "Reset" message from DashCommand to avoid name conflicts with bindings -> TODO: either find a better Go DBC binding generator or patch the upstream DBC to -> avoid having messages named "Reset" \ No newline at end of file +Note: the `Reset` message was removed from DashCommand in the DBC to avoid name conflicts in the generated Go bindings. diff --git a/scripts/daq/setup.sh b/scripts/daq/setup.sh index a8a78d390..a1b9a9acd 100644 --- a/scripts/daq/setup.sh +++ b/scripts/daq/setup.sh @@ -1,71 +1,112 @@ #!/bin/bash -# DAQ setup script for Raspberry Pi (ARM64). +# DAQ setup script for Raspberry Pi (ARM64) or amd64 Linux. # Installs InfluxDB 2.x and Grafana natively — no Docker. -# Run once on a fresh Pi: bash setup.sh +# Run once on a fresh system: bash setup.sh set -e echo "=== MAC Formula DAQ Setup ===" echo "" -# ── System packages ────────────────────────────────────────────────────────── +# ── Detect architecture ─────────────────────────────────────────────────────── +ARCH=$(dpkg --print-architecture) +echo "Detected architecture: $ARCH" +echo "" + +# ── System packages ─────────────────────────────────────────────────────────── echo "[1/6] Installing system packages..." sudo apt update -q -sudo apt install -y python3 python3-venv python3-pip can-utils curl gnupg2 +sudo apt install -y \ + python3 \ + python3-venv \ + python3-pip \ + can-utils \ + curl \ + gnupg2 -# ── InfluxDB 2.x (native) ──────────────────────────────────────────────────── +# ── InfluxDB 2.x ────────────────────────────────────────────────────────────── echo "[2/6] Installing InfluxDB 2.x..." + +sudo rm -f /etc/apt/sources.list.d/influxdata.list +sudo rm -f /usr/share/keyrings/influxdata-archive-keyring.gpg + curl -fsSL https://repos.influxdata.com/influxdata-archive_compat.key \ - | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg -echo "deb [signed-by=/etc/apt/trusted.gpg.d/influxdata-archive_compat.gpg] \ - https://repos.influxdata.com/debian stable main" \ + | gpg --dearmor \ + | sudo tee /usr/share/keyrings/influxdata-archive-keyring.gpg > /dev/null + +echo "deb [signed-by=/usr/share/keyrings/influxdata-archive-keyring.gpg] \ +https://repos.influxdata.com/debian stable main" \ | sudo tee /etc/apt/sources.list.d/influxdata.list + sudo apt update -q sudo apt install -y influxdb2 sudo systemctl enable --now influxdb echo " InfluxDB running at http://localhost:8086" -# ── Grafana (from bundled ARM64 .deb) ──────────────────────────────────────── -echo "[3/6] Installing Grafana from bundled .deb..." +# ── Grafana ─────────────────────────────────────────────────────────────────── +echo "[3/6] Installing Grafana..." + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -DEB_FILE="$SCRIPT_DIR/grafana_11.0.0_arm64.deb" -if [ ! -f "$DEB_FILE" ]; then - echo "ERROR: $DEB_FILE not found. Ensure the .deb is in the same directory as this script." - exit 1 +if [ "$ARCH" = "arm64" ]; then + DEB_FILE="$SCRIPT_DIR/grafana_11.0.0_arm64.deb" + + if [ ! -f "$DEB_FILE" ]; then + echo "ERROR: ARM64 Grafana .deb not found: $DEB_FILE" + exit 1 + fi + + # dpkg installs the package; apt install -f resolves any missing dependencies + sudo dpkg -i "$DEB_FILE" || true + sudo apt install -f -y + +elif [ "$ARCH" = "amd64" ]; then + sudo apt install -y software-properties-common + sudo mkdir -p /etc/apt/keyrings/ + + curl -fsSL https://apt.grafana.com/gpg.key \ + | sudo gpg --dearmor -o /etc/apt/keyrings/grafana.gpg + + echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" \ + | sudo tee /etc/apt/sources.list.d/grafana.list + + sudo apt update -q + sudo apt install -y grafana +else + echo "Unsupported architecture: $ARCH" + exit 1 fi -sudo apt install -y "$DEB_FILE" sudo systemctl enable --now grafana-server -echo " Grafana running at http://localhost:3000 (admin / admin)" +echo " Grafana running at http://localhost:3000 (admin/admin)" # ── CAN interfaces ──────────────────────────────────────────────────────────── echo "[4/6] Configuring CAN interfaces..." -# Load the CAN kernel module on boot + if ! grep -q "^can$" /etc/modules 2>/dev/null; then - echo "can" | sudo tee -a /etc/modules + echo "can" | sudo tee -a /etc/modules fi if ! grep -q "^can_raw$" /etc/modules 2>/dev/null; then - echo "can_raw" | sudo tee -a /etc/modules + echo "can_raw" | sudo tee -a /etc/modules fi -# Bring up can0 at 500 kbit/s (vehicle CAN bus) sudo ip link set can0 up type can bitrate 500000 2>/dev/null \ && echo " can0 up at 500 kbit/s" \ || echo " can0 not available (OK if running without hardware)" -# Bring up can1 at 500 kbit/s (powertrain CAN bus) — optional sudo ip link set can1 up type can bitrate 500000 2>/dev/null \ && echo " can1 up at 500 kbit/s" \ - || echo " can1 not available (OK if single-bus setup)" + || echo " can1 not available (OK for single-bus setup)" # ── Python environment ──────────────────────────────────────────────────────── echo "[5/6] Setting up Python virtual environment..." + cd "$SCRIPT_DIR" python3 -m venv venv source venv/bin/activate -pip install --quiet -r requirements.txt +pip install --upgrade pip --quiet +pip install -r requirements.txt --quiet deactivate echo " venv created. Activate with: source $SCRIPT_DIR/venv/bin/activate" @@ -74,16 +115,21 @@ echo "" echo "[6/6] Setup complete!" echo "" echo "Next steps:" -echo " 1. Open http://localhost:8086 in a browser" -echo " → Complete InfluxDB initial setup: org=macformula, bucket=macfe" -echo " → Generate an All Access API token" -echo " 2. Edit $SCRIPT_DIR/.env and fill in:" -echo " INFLUX_URL=http://localhost:8086" -echo " INFLUX_TOKEN=" -echo " INFLUX_ORG=macformula" -echo " INFLUX_BUCKET=macfe" -echo " 3. Open http://localhost:3000 (admin/admin) → import grafana_dashboard.json" -echo " 4. Run the logger:" -echo " source $SCRIPT_DIR/venv/bin/activate" -echo " python $SCRIPT_DIR/logger.py can0 --pt-interface can1" +echo "" +echo "1. Open InfluxDB at http://localhost:8086" +echo " Create: org=macformula, bucket=macfe" +echo " Generate an All Access API token" +echo "" +echo "2. Edit $SCRIPT_DIR/.env:" +echo " INFLUX_URL=http://localhost:8086" +echo " INFLUX_TOKEN=" +echo " INFLUX_ORG=macformula" +echo " INFLUX_BUCKET=macfe" +echo "" +echo "3. Open Grafana at http://localhost:3000 (admin/admin)" +echo " Import grafana_dashboard.json" +echo "" +echo "4. Run the logger:" +echo " source $SCRIPT_DIR/venv/bin/activate" +echo " python $SCRIPT_DIR/logger.py" echo "" diff --git a/scripts/daq/setup_vm.sh b/scripts/daq/setup_vm.sh new file mode 100644 index 000000000..264a8ecbd --- /dev/null +++ b/scripts/daq/setup_vm.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# DAQ VM test setup — verify logger.py on an amd64 Linux VM. +# Does NOT install InfluxDB or Grafana; logger uses cloud credentials from .env. +# Requires: Ubuntu 22.04+ in VirtualBox or VMware (NOT WSL2 — needs vcan kernel module). +# Run with: bash setup_vm.sh + +set -e + +echo "=== MAC Formula DAQ VM Test Setup ===" +echo "" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ── System packages ─────────────────────────────────────────────────────────── +echo "[1/3] Installing system packages..." +sudo apt update -q +sudo apt install -y python3 python3-venv python3-pip can-utils + +# ── Python environment ──────────────────────────────────────────────────────── +echo "[2/3] Setting up Python virtual environment..." +cd "$SCRIPT_DIR" +python3 -m venv venv +source venv/bin/activate +pip install --upgrade pip --quiet +pip install -r requirements.txt --quiet +deactivate +echo " venv ready. Activate with: source $SCRIPT_DIR/venv/bin/activate" + +# ── Virtual CAN interfaces ──────────────────────────────────────────────────── +echo "[3/3] Setting up virtual CAN interfaces..." +sudo modprobe vcan +sudo ip link add dev vcan0 type vcan 2>/dev/null || true +sudo ip link set vcan0 up +sudo ip link add dev vcan1 type vcan 2>/dev/null || true +sudo ip link set vcan1 up +echo " vcan0 and vcan1 are up" + +# ── Done ────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Setup complete ===" +echo "" +echo "Run the logger (writes to InfluxDB Cloud via .env credentials):" +echo "" +echo " source $SCRIPT_DIR/venv/bin/activate" +echo " python $SCRIPT_DIR/logger.py --interfaces vcan0 vcan1" +echo "" +echo "In a separate terminal, inject test CAN frames:" +echo "" +echo " # Random frames (exercises CSV logging, won't trigger InfluxDB writes):" +echo " cangen vcan0 -g 50 &" +echo " cangen vcan1 -g 50 &" +echo "" +echo " # Specific frames matching DBC IDs (triggers InfluxDB writes too):" +echo " cansend vcan0 624#0000000000000000 # Pack_State (voltage, current)" +echo " cansend vcan0 625#0000000000000000 # Pack_SOC" +echo " cansend vcan0 283#0000000000000000 # Inv1_ActualValues1 (RPM)" +echo "" +echo "Verify:" +echo " - CSV log files appear in: $SCRIPT_DIR/logs/" +echo " - InfluxDB Cloud bucket 'macfe' receives data (check .env for URL)" +echo "" From 72a7df392a314c4ed8dbede5fc239b3c84d58fe1 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 01:31:51 -0400 Subject: [PATCH 18/34] clang format ignore file --- .clang-format-ignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .clang-format-ignore diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 000000000..85abe5a67 --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,2 @@ +projects/*/src/platforms/stm32/Core/ +projects/*/src/platforms/stm32/Drivers/ From 9dddc41d264b68b7fb6414f3cf454fd82029ad42 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 10:27:01 -0400 Subject: [PATCH 19/34] remove clang format file --- .clang-format-ignore | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .clang-format-ignore diff --git a/.clang-format-ignore b/.clang-format-ignore deleted file mode 100644 index 85abe5a67..000000000 --- a/.clang-format-ignore +++ /dev/null @@ -1,2 +0,0 @@ -projects/*/src/platforms/stm32/Core/ -projects/*/src/platforms/stm32/Drivers/ From 04cc5f42af11dff22d2613081fa45ec5ec4ef503 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 10:59:19 -0400 Subject: [PATCH 20/34] clang format is annoying me --- .../src/platforms/stm32/Core/Inc/main.h | 2 + .../stm32/Core/Inc/stm32f4xx_hal_conf.h | 489 ++++++++++-------- .../src/platforms/stm32/Core/Src/gpio.c | 6 - .../platforms/stm32/Core/Src/stm32f4xx_it.c | 3 +- .../src/platforms/stm32/dashboard.ioc | 11 +- 5 files changed, 284 insertions(+), 227 deletions(-) diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h index 4ac638ccb..ade63e261 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h @@ -155,6 +155,8 @@ void Error_Handler(void); #define LCD_INT_GPIO_Port GPIOJ #define D20_Pin GPIO_PIN_12 #define D20_GPIO_Port GPIOH +#define WAKEUP_Pin GPIO_PIN_0 +#define WAKEUP_GPIO_Port GPIOA #define BUTTON_SCROLL_Pin GPIO_PIN_4 #define BUTTON_SCROLL_GPIO_Port GPIOC #define A7_Pin GPIO_PIN_13 diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h index d3df4e4c5..ca112046f 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h @@ -1,23 +1,23 @@ /* USER CODE BEGIN Header */ /** - ****************************************************************************** - * @file stm32f4xx_hal_conf_template.h - * @author MCD Application Team - * @brief HAL configuration template file. - * This file should be copied to the application folder and renamed - * to stm32f4xx_hal_conf.h. - ****************************************************************************** - * @attention - * - * Copyright (c) 2017 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ + ****************************************************************************** + * @file stm32f4xx_hal_conf_template.h + * @author MCD Application Team + * @brief HAL configuration template file. + * This file should be copied to the application folder and renamed + * to stm32f4xx_hal_conf.h. + ****************************************************************************** + * @attention + * + * Copyright (c) 2017 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ /* USER CODE END Header */ /* Define to prevent recursive inclusion -------------------------------------*/ @@ -25,7 +25,7 @@ #define __STM32F4xx_HAL_CONF_H #ifdef __cplusplus - extern "C" { +extern "C" { #endif /* Exported types ------------------------------------------------------------*/ @@ -33,11 +33,11 @@ /* ########################## Module Selection ############################## */ /** - * @brief This is the list of modules to be used in the HAL driver - */ + * @brief This is the list of modules to be used in the HAL driver + */ #define HAL_MODULE_ENABLED - /* #define HAL_CRYP_MODULE_ENABLED */ +/* #define HAL_CRYP_MODULE_ENABLED */ /* #define HAL_ADC_MODULE_ENABLED */ #define HAL_CAN_MODULE_ENABLED /* #define HAL_CRC_MODULE_ENABLED */ @@ -91,114 +91,158 @@ /* ########################## HSE/HSI Values adaptation ##################### */ /** - * @brief Adjust the value of External High Speed oscillator (HSE) used in your application. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSE is used as system clock source, directly or through the PLL). - */ -#if !defined (HSE_VALUE) - #define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */ -#endif /* HSE_VALUE */ - -#if !defined (HSE_STARTUP_TIMEOUT) - #define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */ -#endif /* HSE_STARTUP_TIMEOUT */ + * @brief Adjust the value of External High Speed oscillator (HSE) used in your + * application. This value is used by the RCC HAL module to compute the system + * frequency (when HSE is used as system clock source, directly or through the + * PLL). + */ +#if !defined(HSE_VALUE) +#define HSE_VALUE 8000000U /*!< Value of the External oscillator in Hz */ +#endif /* HSE_VALUE */ + +#if !defined(HSE_STARTUP_TIMEOUT) +#define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */ +#endif /* HSE_STARTUP_TIMEOUT */ /** - * @brief Internal High Speed oscillator (HSI) value. - * This value is used by the RCC HAL module to compute the system frequency - * (when HSI is used as system clock source, directly or through the PLL). - */ -#if !defined (HSI_VALUE) - #define HSI_VALUE ((uint32_t)16000000U) /*!< Value of the Internal oscillator in Hz*/ -#endif /* HSI_VALUE */ + * @brief Internal High Speed oscillator (HSI) value. + * This value is used by the RCC HAL module to compute the system + * frequency (when HSI is used as system clock source, directly or through the + * PLL). + */ +#if !defined(HSI_VALUE) +#define HSI_VALUE \ + ((uint32_t)16000000U) /*!< Value of the Internal oscillator in Hz*/ +#endif /* HSI_VALUE */ /** - * @brief Internal Low Speed oscillator (LSI) value. - */ -#if !defined (LSI_VALUE) - #define LSI_VALUE 32000U /*!< LSI Typical Value in Hz*/ -#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz - The real value may vary depending on the variations - in voltage and temperature.*/ + * @brief Internal Low Speed oscillator (LSI) value. + */ +#if !defined(LSI_VALUE) +#define LSI_VALUE 32000U /*!< LSI Typical Value in Hz*/ +#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz \ + The real value may vary depending on the variations \ + in voltage and temperature.*/ /** - * @brief External Low Speed oscillator (LSE) value. - */ -#if !defined (LSE_VALUE) - #define LSE_VALUE 32768U /*!< Value of the External Low Speed oscillator in Hz */ -#endif /* LSE_VALUE */ + * @brief External Low Speed oscillator (LSE) value. + */ +#if !defined(LSE_VALUE) +#define LSE_VALUE \ + 32768U /*!< Value of the External Low Speed oscillator in Hz */ +#endif /* LSE_VALUE */ -#if !defined (LSE_STARTUP_TIMEOUT) - #define LSE_STARTUP_TIMEOUT 5000U /*!< Time out for LSE start up, in ms */ -#endif /* LSE_STARTUP_TIMEOUT */ +#if !defined(LSE_STARTUP_TIMEOUT) +#define LSE_STARTUP_TIMEOUT 5000U /*!< Time out for LSE start up, in ms */ +#endif /* LSE_STARTUP_TIMEOUT */ /** - * @brief External clock source for I2S peripheral - * This value is used by the I2S HAL module to compute the I2S clock source - * frequency, this source is inserted directly through I2S_CKIN pad. - */ -#if !defined (EXTERNAL_CLOCK_VALUE) - #define EXTERNAL_CLOCK_VALUE 12288000U /*!< Value of the External audio frequency in Hz*/ -#endif /* EXTERNAL_CLOCK_VALUE */ + * @brief External clock source for I2S peripheral + * This value is used by the I2S HAL module to compute the I2S clock + * source frequency, this source is inserted directly through I2S_CKIN pad. + */ +#if !defined(EXTERNAL_CLOCK_VALUE) +#define EXTERNAL_CLOCK_VALUE \ + 12288000U /*!< Value of the External audio frequency in Hz*/ +#endif /* EXTERNAL_CLOCK_VALUE */ /* Tip: To avoid modifying this file each time you need to use different HSE, === you can define the HSE value in your toolchain compiler preprocessor. */ /* ########################### System Configuration ######################### */ /** - * @brief This is the HAL system configuration section - */ -#define VDD_VALUE 3300U /*!< Value of VDD in mv */ -#define TICK_INT_PRIORITY 15U /*!< tick interrupt priority */ -#define USE_RTOS 0U -#define PREFETCH_ENABLE 1U -#define INSTRUCTION_CACHE_ENABLE 1U -#define DATA_CACHE_ENABLE 1U - -#define USE_HAL_ADC_REGISTER_CALLBACKS 0U /* ADC register callback disabled */ -#define USE_HAL_CAN_REGISTER_CALLBACKS 0U /* CAN register callback disabled */ -#define USE_HAL_CEC_REGISTER_CALLBACKS 0U /* CEC register callback disabled */ -#define USE_HAL_CRYP_REGISTER_CALLBACKS 0U /* CRYP register callback disabled */ -#define USE_HAL_DAC_REGISTER_CALLBACKS 0U /* DAC register callback disabled */ -#define USE_HAL_DCMI_REGISTER_CALLBACKS 0U /* DCMI register callback disabled */ -#define USE_HAL_DFSDM_REGISTER_CALLBACKS 0U /* DFSDM register callback disabled */ -#define USE_HAL_DMA2D_REGISTER_CALLBACKS 0U /* DMA2D register callback disabled */ -#define USE_HAL_DSI_REGISTER_CALLBACKS 0U /* DSI register callback disabled */ -#define USE_HAL_ETH_REGISTER_CALLBACKS 0U /* ETH register callback disabled */ -#define USE_HAL_HASH_REGISTER_CALLBACKS 0U /* HASH register callback disabled */ -#define USE_HAL_HCD_REGISTER_CALLBACKS 0U /* HCD register callback disabled */ -#define USE_HAL_I2C_REGISTER_CALLBACKS 0U /* I2C register callback disabled */ -#define USE_HAL_FMPI2C_REGISTER_CALLBACKS 0U /* FMPI2C register callback disabled */ -#define USE_HAL_FMPSMBUS_REGISTER_CALLBACKS 0U /* FMPSMBUS register callback disabled */ -#define USE_HAL_I2S_REGISTER_CALLBACKS 0U /* I2S register callback disabled */ -#define USE_HAL_IRDA_REGISTER_CALLBACKS 0U /* IRDA register callback disabled */ -#define USE_HAL_LPTIM_REGISTER_CALLBACKS 0U /* LPTIM register callback disabled */ -#define USE_HAL_LTDC_REGISTER_CALLBACKS 1U /* LTDC register callback enabled */ -#define USE_HAL_MMC_REGISTER_CALLBACKS 0U /* MMC register callback disabled */ -#define USE_HAL_NAND_REGISTER_CALLBACKS 0U /* NAND register callback disabled */ -#define USE_HAL_NOR_REGISTER_CALLBACKS 0U /* NOR register callback disabled */ -#define USE_HAL_PCCARD_REGISTER_CALLBACKS 0U /* PCCARD register callback disabled */ -#define USE_HAL_PCD_REGISTER_CALLBACKS 0U /* PCD register callback disabled */ -#define USE_HAL_QSPI_REGISTER_CALLBACKS 0U /* QSPI register callback disabled */ -#define USE_HAL_RNG_REGISTER_CALLBACKS 0U /* RNG register callback disabled */ -#define USE_HAL_RTC_REGISTER_CALLBACKS 0U /* RTC register callback disabled */ -#define USE_HAL_SAI_REGISTER_CALLBACKS 0U /* SAI register callback disabled */ -#define USE_HAL_SD_REGISTER_CALLBACKS 0U /* SD register callback disabled */ -#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS 0U /* SMARTCARD register callback disabled */ -#define USE_HAL_SDRAM_REGISTER_CALLBACKS 0U /* SDRAM register callback disabled */ -#define USE_HAL_SRAM_REGISTER_CALLBACKS 0U /* SRAM register callback disabled */ -#define USE_HAL_SPDIFRX_REGISTER_CALLBACKS 0U /* SPDIFRX register callback disabled */ -#define USE_HAL_SMBUS_REGISTER_CALLBACKS 0U /* SMBUS register callback disabled */ -#define USE_HAL_SPI_REGISTER_CALLBACKS 0U /* SPI register callback disabled */ -#define USE_HAL_TIM_REGISTER_CALLBACKS 0U /* TIM register callback disabled */ -#define USE_HAL_UART_REGISTER_CALLBACKS 0U /* UART register callback disabled */ -#define USE_HAL_USART_REGISTER_CALLBACKS 0U /* USART register callback disabled */ -#define USE_HAL_WWDG_REGISTER_CALLBACKS 0U /* WWDG register callback disabled */ + * @brief This is the HAL system configuration section + */ +#define VDD_VALUE 3300U /*!< Value of VDD in mv */ +#define TICK_INT_PRIORITY 15U /*!< tick interrupt priority */ +#define USE_RTOS 0U +#define PREFETCH_ENABLE 1U +#define INSTRUCTION_CACHE_ENABLE 1U +#define DATA_CACHE_ENABLE 1U + +#define USE_HAL_ADC_REGISTER_CALLBACKS \ + 0U /* ADC register callback disabled */ +#define USE_HAL_CAN_REGISTER_CALLBACKS \ + 0U /* CAN register callback disabled */ +#define USE_HAL_CEC_REGISTER_CALLBACKS \ + 0U /* CEC register callback disabled */ +#define USE_HAL_CRYP_REGISTER_CALLBACKS \ + 0U /* CRYP register callback disabled */ +#define USE_HAL_DAC_REGISTER_CALLBACKS \ + 0U /* DAC register callback disabled */ +#define USE_HAL_DCMI_REGISTER_CALLBACKS \ + 0U /* DCMI register callback disabled */ +#define USE_HAL_DFSDM_REGISTER_CALLBACKS \ + 0U /* DFSDM register callback disabled */ +#define USE_HAL_DMA2D_REGISTER_CALLBACKS \ + 0U /* DMA2D register callback disabled */ +#define USE_HAL_DSI_REGISTER_CALLBACKS \ + 0U /* DSI register callback disabled */ +#define USE_HAL_ETH_REGISTER_CALLBACKS \ + 0U /* ETH register callback disabled */ +#define USE_HAL_HASH_REGISTER_CALLBACKS \ + 0U /* HASH register callback disabled */ +#define USE_HAL_HCD_REGISTER_CALLBACKS \ + 0U /* HCD register callback disabled */ +#define USE_HAL_I2C_REGISTER_CALLBACKS \ + 0U /* I2C register callback disabled */ +#define USE_HAL_FMPI2C_REGISTER_CALLBACKS \ + 0U /* FMPI2C register callback disabled */ +#define USE_HAL_FMPSMBUS_REGISTER_CALLBACKS \ + 0U /* FMPSMBUS register callback disabled */ +#define USE_HAL_I2S_REGISTER_CALLBACKS \ + 0U /* I2S register callback disabled */ +#define USE_HAL_IRDA_REGISTER_CALLBACKS \ + 0U /* IRDA register callback disabled */ +#define USE_HAL_LPTIM_REGISTER_CALLBACKS \ + 0U /* LPTIM register callback disabled */ +#define USE_HAL_LTDC_REGISTER_CALLBACKS \ + 0U /* LTDC register callback disabled */ +#define USE_HAL_MMC_REGISTER_CALLBACKS \ + 0U /* MMC register callback disabled */ +#define USE_HAL_NAND_REGISTER_CALLBACKS \ + 0U /* NAND register callback disabled */ +#define USE_HAL_NOR_REGISTER_CALLBACKS \ + 0U /* NOR register callback disabled */ +#define USE_HAL_PCCARD_REGISTER_CALLBACKS \ + 0U /* PCCARD register callback disabled */ +#define USE_HAL_PCD_REGISTER_CALLBACKS \ + 0U /* PCD register callback disabled */ +#define USE_HAL_QSPI_REGISTER_CALLBACKS \ + 0U /* QSPI register callback disabled */ +#define USE_HAL_RNG_REGISTER_CALLBACKS \ + 0U /* RNG register callback disabled */ +#define USE_HAL_RTC_REGISTER_CALLBACKS \ + 0U /* RTC register callback disabled */ +#define USE_HAL_SAI_REGISTER_CALLBACKS \ + 0U /* SAI register callback disabled */ +#define USE_HAL_SD_REGISTER_CALLBACKS \ + 0U /* SD register callback disabled */ +#define USE_HAL_SMARTCARD_REGISTER_CALLBACKS \ + 0U /* SMARTCARD register callback disabled */ +#define USE_HAL_SDRAM_REGISTER_CALLBACKS \ + 0U /* SDRAM register callback disabled */ +#define USE_HAL_SRAM_REGISTER_CALLBACKS \ + 0U /* SRAM register callback disabled */ +#define USE_HAL_SPDIFRX_REGISTER_CALLBACKS \ + 0U /* SPDIFRX register callback disabled */ +#define USE_HAL_SMBUS_REGISTER_CALLBACKS \ + 0U /* SMBUS register callback disabled */ +#define USE_HAL_SPI_REGISTER_CALLBACKS \ + 0U /* SPI register callback disabled */ +#define USE_HAL_TIM_REGISTER_CALLBACKS \ + 0U /* TIM register callback disabled */ +#define USE_HAL_UART_REGISTER_CALLBACKS \ + 0U /* UART register callback disabled */ +#define USE_HAL_USART_REGISTER_CALLBACKS \ + 0U /* USART register callback disabled */ +#define USE_HAL_WWDG_REGISTER_CALLBACKS \ + 0U /* WWDG register callback disabled */ /* ########################## Assert Selection ############################## */ /** - * @brief Uncomment the line below to expanse the "assert_param" macro in the - * HAL drivers code - */ + * @brief Uncomment the line below to expanse the "assert_param" macro in the + * HAL drivers code + */ /* #define USE_FULL_ASSERT 1U */ /* ################## Ethernet peripheral configuration ##################### */ @@ -206,286 +250,303 @@ /* Section 1 : Ethernet peripheral configuration */ /* MAC ADDRESS: MAC_ADDR0:MAC_ADDR1:MAC_ADDR2:MAC_ADDR3:MAC_ADDR4:MAC_ADDR5 */ -#define MAC_ADDR0 2U -#define MAC_ADDR1 0U -#define MAC_ADDR2 0U -#define MAC_ADDR3 0U -#define MAC_ADDR4 0U -#define MAC_ADDR5 0U +#define MAC_ADDR0 2U +#define MAC_ADDR1 0U +#define MAC_ADDR2 0U +#define MAC_ADDR3 0U +#define MAC_ADDR4 0U +#define MAC_ADDR5 0U /* Definition of the Ethernet driver buffers size and count */ -#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for receive */ -#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE /* buffer size for transmit */ -#define ETH_RXBUFNB 4U /* 4 Rx buffers of size ETH_RX_BUF_SIZE */ -#define ETH_TXBUFNB 4U /* 4 Tx buffers of size ETH_TX_BUF_SIZE */ +#define ETH_RX_BUF_SIZE \ + ETH_MAX_PACKET_SIZE /* buffer size for receive */ +#define ETH_TX_BUF_SIZE \ + ETH_MAX_PACKET_SIZE /* buffer size for transmit */ +#define ETH_RXBUFNB 4U /* 4 Rx buffers of size ETH_RX_BUF_SIZE */ +#define ETH_TXBUFNB 4U /* 4 Tx buffers of size ETH_TX_BUF_SIZE */ /* Section 2: PHY configuration section */ /* DP83848_PHY_ADDRESS Address*/ #define DP83848_PHY_ADDRESS /* PHY Reset delay these values are based on a 1 ms Systick interrupt*/ -#define PHY_RESET_DELAY 0x000000FFU +#define PHY_RESET_DELAY 0x000000FFU /* PHY Configuration delay */ -#define PHY_CONFIG_DELAY 0x00000FFFU +#define PHY_CONFIG_DELAY 0x00000FFFU -#define PHY_READ_TO 0x0000FFFFU -#define PHY_WRITE_TO 0x0000FFFFU +#define PHY_READ_TO 0x0000FFFFU +#define PHY_WRITE_TO 0x0000FFFFU /* Section 3: Common PHY Registers */ -#define PHY_BCR ((uint16_t)0x0000U) /*!< Transceiver Basic Control Register */ -#define PHY_BSR ((uint16_t)0x0001U) /*!< Transceiver Basic Status Register */ - -#define PHY_RESET ((uint16_t)0x8000U) /*!< PHY Reset */ -#define PHY_LOOPBACK ((uint16_t)0x4000U) /*!< Select loop-back mode */ -#define PHY_FULLDUPLEX_100M ((uint16_t)0x2100U) /*!< Set the full-duplex mode at 100 Mb/s */ -#define PHY_HALFDUPLEX_100M ((uint16_t)0x2000U) /*!< Set the half-duplex mode at 100 Mb/s */ -#define PHY_FULLDUPLEX_10M ((uint16_t)0x0100U) /*!< Set the full-duplex mode at 10 Mb/s */ -#define PHY_HALFDUPLEX_10M ((uint16_t)0x0000U) /*!< Set the half-duplex mode at 10 Mb/s */ -#define PHY_AUTONEGOTIATION ((uint16_t)0x1000U) /*!< Enable auto-negotiation function */ -#define PHY_RESTART_AUTONEGOTIATION ((uint16_t)0x0200U) /*!< Restart auto-negotiation function */ -#define PHY_POWERDOWN ((uint16_t)0x0800U) /*!< Select the power down mode */ -#define PHY_ISOLATE ((uint16_t)0x0400U) /*!< Isolate PHY from MII */ - -#define PHY_AUTONEGO_COMPLETE ((uint16_t)0x0020U) /*!< Auto-Negotiation process completed */ -#define PHY_LINKED_STATUS ((uint16_t)0x0004U) /*!< Valid link established */ -#define PHY_JABBER_DETECTION ((uint16_t)0x0002U) /*!< Jabber condition detected */ +#define PHY_BCR ((uint16_t)0x0000U) /*!< Transceiver Basic Control Register */ +#define PHY_BSR ((uint16_t)0x0001U) /*!< Transceiver Basic Status Register */ + +#define PHY_RESET ((uint16_t)0x8000U) /*!< PHY Reset */ +#define PHY_LOOPBACK ((uint16_t)0x4000U) /*!< Select loop-back mode */ +#define PHY_FULLDUPLEX_100M \ + ((uint16_t)0x2100U) /*!< Set the full-duplex mode at 100 Mb/s */ +#define PHY_HALFDUPLEX_100M \ + ((uint16_t)0x2000U) /*!< Set the half-duplex mode at 100 Mb/s */ +#define PHY_FULLDUPLEX_10M \ + ((uint16_t)0x0100U) /*!< Set the full-duplex mode at 10 Mb/s */ +#define PHY_HALFDUPLEX_10M \ + ((uint16_t)0x0000U) /*!< Set the half-duplex mode at 10 Mb/s */ +#define PHY_AUTONEGOTIATION \ + ((uint16_t)0x1000U) /*!< Enable auto-negotiation function */ +#define PHY_RESTART_AUTONEGOTIATION \ + ((uint16_t)0x0200U) /*!< Restart auto-negotiation function */ +#define PHY_POWERDOWN \ + ((uint16_t)0x0800U) /*!< Select the power down mode */ +#define PHY_ISOLATE \ + ((uint16_t)0x0400U) /*!< Isolate PHY from MII */ + +#define PHY_AUTONEGO_COMPLETE \ + ((uint16_t)0x0020U) /*!< Auto-Negotiation process completed */ +#define PHY_LINKED_STATUS \ + ((uint16_t)0x0004U) /*!< Valid link established */ +#define PHY_JABBER_DETECTION \ + ((uint16_t)0x0002U) /*!< Jabber condition detected */ /* Section 4: Extended PHY Registers */ -#define PHY_SR ((uint16_t)) /*!< PHY status register Offset */ +#define PHY_SR \ + ((uint16_t)) /*!< PHY status register Offset */ -#define PHY_SPEED_STATUS ((uint16_t)) /*!< PHY Speed mask */ -#define PHY_DUPLEX_STATUS ((uint16_t)) /*!< PHY Duplex mask */ +#define PHY_SPEED_STATUS \ + ((uint16_t)) /*!< PHY Speed mask */ +#define PHY_DUPLEX_STATUS \ + ((uint16_t)) /*!< PHY Duplex mask */ /* ################## SPI peripheral configuration ########################## */ /* CRC FEATURE: Use to activate CRC feature inside HAL SPI Driver -* Activated: CRC code is present inside driver -* Deactivated: CRC code cleaned from driver -*/ + * Activated: CRC code is present inside driver + * Deactivated: CRC code cleaned from driver + */ -#define USE_SPI_CRC 0U +#define USE_SPI_CRC 0U /* Includes ------------------------------------------------------------------*/ /** - * @brief Include module's header file - */ + * @brief Include module's header file + */ #ifdef HAL_RCC_MODULE_ENABLED - #include "stm32f4xx_hal_rcc.h" +#include "stm32f4xx_hal_rcc.h" #endif /* HAL_RCC_MODULE_ENABLED */ #ifdef HAL_GPIO_MODULE_ENABLED - #include "stm32f4xx_hal_gpio.h" +#include "stm32f4xx_hal_gpio.h" #endif /* HAL_GPIO_MODULE_ENABLED */ #ifdef HAL_EXTI_MODULE_ENABLED - #include "stm32f4xx_hal_exti.h" +#include "stm32f4xx_hal_exti.h" #endif /* HAL_EXTI_MODULE_ENABLED */ #ifdef HAL_DMA_MODULE_ENABLED - #include "stm32f4xx_hal_dma.h" +#include "stm32f4xx_hal_dma.h" #endif /* HAL_DMA_MODULE_ENABLED */ #ifdef HAL_CORTEX_MODULE_ENABLED - #include "stm32f4xx_hal_cortex.h" +#include "stm32f4xx_hal_cortex.h" #endif /* HAL_CORTEX_MODULE_ENABLED */ #ifdef HAL_ADC_MODULE_ENABLED - #include "stm32f4xx_hal_adc.h" +#include "stm32f4xx_hal_adc.h" #endif /* HAL_ADC_MODULE_ENABLED */ #ifdef HAL_CAN_MODULE_ENABLED - #include "stm32f4xx_hal_can.h" +#include "stm32f4xx_hal_can.h" #endif /* HAL_CAN_MODULE_ENABLED */ #ifdef HAL_CAN_LEGACY_MODULE_ENABLED - #include "stm32f4xx_hal_can_legacy.h" +#include "stm32f4xx_hal_can_legacy.h" #endif /* HAL_CAN_LEGACY_MODULE_ENABLED */ #ifdef HAL_CRC_MODULE_ENABLED - #include "stm32f4xx_hal_crc.h" +#include "stm32f4xx_hal_crc.h" #endif /* HAL_CRC_MODULE_ENABLED */ #ifdef HAL_CRYP_MODULE_ENABLED - #include "stm32f4xx_hal_cryp.h" +#include "stm32f4xx_hal_cryp.h" #endif /* HAL_CRYP_MODULE_ENABLED */ #ifdef HAL_DMA2D_MODULE_ENABLED - #include "stm32f4xx_hal_dma2d.h" +#include "stm32f4xx_hal_dma2d.h" #endif /* HAL_DMA2D_MODULE_ENABLED */ #ifdef HAL_DAC_MODULE_ENABLED - #include "stm32f4xx_hal_dac.h" +#include "stm32f4xx_hal_dac.h" #endif /* HAL_DAC_MODULE_ENABLED */ #ifdef HAL_DCMI_MODULE_ENABLED - #include "stm32f4xx_hal_dcmi.h" +#include "stm32f4xx_hal_dcmi.h" #endif /* HAL_DCMI_MODULE_ENABLED */ #ifdef HAL_ETH_MODULE_ENABLED - #include "stm32f4xx_hal_eth.h" +#include "stm32f4xx_hal_eth.h" #endif /* HAL_ETH_MODULE_ENABLED */ #ifdef HAL_ETH_LEGACY_MODULE_ENABLED - #include "stm32f4xx_hal_eth_legacy.h" +#include "stm32f4xx_hal_eth_legacy.h" #endif /* HAL_ETH_LEGACY_MODULE_ENABLED */ #ifdef HAL_FLASH_MODULE_ENABLED - #include "stm32f4xx_hal_flash.h" +#include "stm32f4xx_hal_flash.h" #endif /* HAL_FLASH_MODULE_ENABLED */ #ifdef HAL_SRAM_MODULE_ENABLED - #include "stm32f4xx_hal_sram.h" +#include "stm32f4xx_hal_sram.h" #endif /* HAL_SRAM_MODULE_ENABLED */ #ifdef HAL_NOR_MODULE_ENABLED - #include "stm32f4xx_hal_nor.h" +#include "stm32f4xx_hal_nor.h" #endif /* HAL_NOR_MODULE_ENABLED */ #ifdef HAL_NAND_MODULE_ENABLED - #include "stm32f4xx_hal_nand.h" +#include "stm32f4xx_hal_nand.h" #endif /* HAL_NAND_MODULE_ENABLED */ #ifdef HAL_PCCARD_MODULE_ENABLED - #include "stm32f4xx_hal_pccard.h" +#include "stm32f4xx_hal_pccard.h" #endif /* HAL_PCCARD_MODULE_ENABLED */ #ifdef HAL_SDRAM_MODULE_ENABLED - #include "stm32f4xx_hal_sdram.h" +#include "stm32f4xx_hal_sdram.h" #endif /* HAL_SDRAM_MODULE_ENABLED */ #ifdef HAL_HASH_MODULE_ENABLED - #include "stm32f4xx_hal_hash.h" +#include "stm32f4xx_hal_hash.h" #endif /* HAL_HASH_MODULE_ENABLED */ #ifdef HAL_I2C_MODULE_ENABLED - #include "stm32f4xx_hal_i2c.h" +#include "stm32f4xx_hal_i2c.h" #endif /* HAL_I2C_MODULE_ENABLED */ #ifdef HAL_SMBUS_MODULE_ENABLED - #include "stm32f4xx_hal_smbus.h" +#include "stm32f4xx_hal_smbus.h" #endif /* HAL_SMBUS_MODULE_ENABLED */ #ifdef HAL_I2S_MODULE_ENABLED - #include "stm32f4xx_hal_i2s.h" +#include "stm32f4xx_hal_i2s.h" #endif /* HAL_I2S_MODULE_ENABLED */ #ifdef HAL_IWDG_MODULE_ENABLED - #include "stm32f4xx_hal_iwdg.h" +#include "stm32f4xx_hal_iwdg.h" #endif /* HAL_IWDG_MODULE_ENABLED */ #ifdef HAL_LTDC_MODULE_ENABLED - #include "stm32f4xx_hal_ltdc.h" +#include "stm32f4xx_hal_ltdc.h" #endif /* HAL_LTDC_MODULE_ENABLED */ #ifdef HAL_PWR_MODULE_ENABLED - #include "stm32f4xx_hal_pwr.h" +#include "stm32f4xx_hal_pwr.h" #endif /* HAL_PWR_MODULE_ENABLED */ #ifdef HAL_RNG_MODULE_ENABLED - #include "stm32f4xx_hal_rng.h" +#include "stm32f4xx_hal_rng.h" #endif /* HAL_RNG_MODULE_ENABLED */ #ifdef HAL_RTC_MODULE_ENABLED - #include "stm32f4xx_hal_rtc.h" +#include "stm32f4xx_hal_rtc.h" #endif /* HAL_RTC_MODULE_ENABLED */ #ifdef HAL_SAI_MODULE_ENABLED - #include "stm32f4xx_hal_sai.h" +#include "stm32f4xx_hal_sai.h" #endif /* HAL_SAI_MODULE_ENABLED */ #ifdef HAL_SD_MODULE_ENABLED - #include "stm32f4xx_hal_sd.h" +#include "stm32f4xx_hal_sd.h" #endif /* HAL_SD_MODULE_ENABLED */ #ifdef HAL_SPI_MODULE_ENABLED - #include "stm32f4xx_hal_spi.h" +#include "stm32f4xx_hal_spi.h" #endif /* HAL_SPI_MODULE_ENABLED */ #ifdef HAL_TIM_MODULE_ENABLED - #include "stm32f4xx_hal_tim.h" +#include "stm32f4xx_hal_tim.h" #endif /* HAL_TIM_MODULE_ENABLED */ #ifdef HAL_UART_MODULE_ENABLED - #include "stm32f4xx_hal_uart.h" +#include "stm32f4xx_hal_uart.h" #endif /* HAL_UART_MODULE_ENABLED */ #ifdef HAL_USART_MODULE_ENABLED - #include "stm32f4xx_hal_usart.h" +#include "stm32f4xx_hal_usart.h" #endif /* HAL_USART_MODULE_ENABLED */ #ifdef HAL_IRDA_MODULE_ENABLED - #include "stm32f4xx_hal_irda.h" +#include "stm32f4xx_hal_irda.h" #endif /* HAL_IRDA_MODULE_ENABLED */ #ifdef HAL_SMARTCARD_MODULE_ENABLED - #include "stm32f4xx_hal_smartcard.h" +#include "stm32f4xx_hal_smartcard.h" #endif /* HAL_SMARTCARD_MODULE_ENABLED */ #ifdef HAL_WWDG_MODULE_ENABLED - #include "stm32f4xx_hal_wwdg.h" +#include "stm32f4xx_hal_wwdg.h" #endif /* HAL_WWDG_MODULE_ENABLED */ #ifdef HAL_PCD_MODULE_ENABLED - #include "stm32f4xx_hal_pcd.h" +#include "stm32f4xx_hal_pcd.h" #endif /* HAL_PCD_MODULE_ENABLED */ #ifdef HAL_HCD_MODULE_ENABLED - #include "stm32f4xx_hal_hcd.h" +#include "stm32f4xx_hal_hcd.h" #endif /* HAL_HCD_MODULE_ENABLED */ #ifdef HAL_DSI_MODULE_ENABLED - #include "stm32f4xx_hal_dsi.h" +#include "stm32f4xx_hal_dsi.h" #endif /* HAL_DSI_MODULE_ENABLED */ #ifdef HAL_QSPI_MODULE_ENABLED - #include "stm32f4xx_hal_qspi.h" +#include "stm32f4xx_hal_qspi.h" #endif /* HAL_QSPI_MODULE_ENABLED */ #ifdef HAL_CEC_MODULE_ENABLED - #include "stm32f4xx_hal_cec.h" +#include "stm32f4xx_hal_cec.h" #endif /* HAL_CEC_MODULE_ENABLED */ #ifdef HAL_FMPI2C_MODULE_ENABLED - #include "stm32f4xx_hal_fmpi2c.h" +#include "stm32f4xx_hal_fmpi2c.h" #endif /* HAL_FMPI2C_MODULE_ENABLED */ #ifdef HAL_FMPSMBUS_MODULE_ENABLED - #include "stm32f4xx_hal_fmpsmbus.h" +#include "stm32f4xx_hal_fmpsmbus.h" #endif /* HAL_FMPSMBUS_MODULE_ENABLED */ #ifdef HAL_SPDIFRX_MODULE_ENABLED - #include "stm32f4xx_hal_spdifrx.h" +#include "stm32f4xx_hal_spdifrx.h" #endif /* HAL_SPDIFRX_MODULE_ENABLED */ #ifdef HAL_DFSDM_MODULE_ENABLED - #include "stm32f4xx_hal_dfsdm.h" +#include "stm32f4xx_hal_dfsdm.h" #endif /* HAL_DFSDM_MODULE_ENABLED */ #ifdef HAL_LPTIM_MODULE_ENABLED - #include "stm32f4xx_hal_lptim.h" +#include "stm32f4xx_hal_lptim.h" #endif /* HAL_LPTIM_MODULE_ENABLED */ #ifdef HAL_MMC_MODULE_ENABLED - #include "stm32f4xx_hal_mmc.h" +#include "stm32f4xx_hal_mmc.h" #endif /* HAL_MMC_MODULE_ENABLED */ /* Exported macro ------------------------------------------------------------*/ -#ifdef USE_FULL_ASSERT +#ifdef USE_FULL_ASSERT /** - * @brief The assert_param macro is used for function's parameters check. - * @param expr If expr is false, it calls assert_failed function - * which reports the name of the source file and the source - * line number of the call that failed. - * If expr is true, it returns no value. - * @retval None - */ - #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) + * @brief The assert_param macro is used for function's parameters check. + * @param expr If expr is false, it calls assert_failed function + * which reports the name of the source file and the source + * line number of the call that failed. + * If expr is true, it returns no value. + * @retval None + */ +#define assert_param(expr) \ + ((expr) ? (void)0U : assert_failed((uint8_t*)__FILE__, __LINE__)) /* Exported functions ------------------------------------------------------- */ - void assert_failed(uint8_t* file, uint32_t line); +void assert_failed(uint8_t* file, uint32_t line); #else - #define assert_param(expr) ((void)0U) +#define assert_param(expr) ((void)0U) #endif /* USE_FULL_ASSERT */ #ifdef __cplusplus diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c index 8003d0d27..0d6d9b7f2 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c @@ -133,12 +133,6 @@ void MX_GPIO_Init(void) { GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(LCD_INT_GPIO_Port, &GPIO_InitStruct); - /*Configure GPIO pin : PA0 */ - GPIO_InitStruct.Pin = GPIO_PIN_0; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - /*Configure GPIO pins : BUTTON_SCROLL_Pin BUTTON_SELECT_Pin */ GPIO_InitStruct.Pin = BUTTON_SCROLL_Pin | BUTTON_SELECT_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c b/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c index a9faa1cf8..c73d8aeaa 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c @@ -23,7 +23,6 @@ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ -#include "lvgl.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -179,7 +178,7 @@ void SysTick_Handler(void) { /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ - lv_tick_inc(1); + /* USER CODE END SysTick_IRQn 1 */ } diff --git a/projects/dashboard/src/platforms/stm32/dashboard.ioc b/projects/dashboard/src/platforms/stm32/dashboard.ioc index 694d82c91..ec384714b 100644 --- a/projects/dashboard/src/platforms/stm32/dashboard.ioc +++ b/projects/dashboard/src/platforms/stm32/dashboard.ioc @@ -241,10 +241,11 @@ NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false -PA0/WKUP.GPIOParameters=GPIO_PuPd -PA0/WKUP.GPIO_PuPd=GPIO_PULLDOWN +PA0/WKUP.GPIOParameters=GPIO_Label +PA0/WKUP.GPIO_Label=WAKEUP [B2] PA0/WKUP.Locked=true -PA0/WKUP.Signal=GPIO_Input +PA0/WKUP.Mode=SYS_WakeUp0 +PA0/WKUP.Signal=SYS_WKUP PA13.GPIOParameters=GPIO_Label PA13.GPIO_Label=SWDIO PA13.Locked=true @@ -791,7 +792,7 @@ ProjectManager.FreePins=false ProjectManager.FreePinsContext= ProjectManager.HalAssertFull=false ProjectManager.HeapSize=0x200 -ProjectManager.KeepUserCode=true +ProjectManager.KeepUserCode=false ProjectManager.LastFirmware=true ProjectManager.LibraryCopy=2 ProjectManager.MainLocation=Core/Src @@ -801,7 +802,7 @@ ProjectManager.ProjectBuild=false ProjectManager.ProjectFileName=dashboard.ioc ProjectManager.ProjectName=dashboard ProjectManager.ProjectStructure= -ProjectManager.RegisterCallBack=LTDC +ProjectManager.RegisterCallBack= ProjectManager.StackSize=0x400 ProjectManager.TargetToolchain=STM32CubeIDE ProjectManager.ToolChainLocation= From cdacf0f463e63512211451584f784ae6fa941d47 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 11:36:28 -0400 Subject: [PATCH 21/34] revert to old config --- .../dashboard/src/platforms/stm32/Core/Inc/main.h | 2 -- .../src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h | 2 +- .../dashboard/src/platforms/stm32/Core/Src/gpio.c | 6 ++++++ .../src/platforms/stm32/Core/Src/stm32f4xx_it.c | 3 ++- projects/dashboard/src/platforms/stm32/dashboard.ioc | 11 +++++------ 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h index ade63e261..4ac638ccb 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h @@ -155,8 +155,6 @@ void Error_Handler(void); #define LCD_INT_GPIO_Port GPIOJ #define D20_Pin GPIO_PIN_12 #define D20_GPIO_Port GPIOH -#define WAKEUP_Pin GPIO_PIN_0 -#define WAKEUP_GPIO_Port GPIOA #define BUTTON_SCROLL_Pin GPIO_PIN_4 #define BUTTON_SCROLL_GPIO_Port GPIOC #define A7_Pin GPIO_PIN_13 diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h index ca112046f..ac8c2f177 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h @@ -196,7 +196,7 @@ extern "C" { #define USE_HAL_LPTIM_REGISTER_CALLBACKS \ 0U /* LPTIM register callback disabled */ #define USE_HAL_LTDC_REGISTER_CALLBACKS \ - 0U /* LTDC register callback disabled */ + 1U /* LTDC register callback enabled */ #define USE_HAL_MMC_REGISTER_CALLBACKS \ 0U /* MMC register callback disabled */ #define USE_HAL_NAND_REGISTER_CALLBACKS \ diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c index 0d6d9b7f2..8003d0d27 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c @@ -133,6 +133,12 @@ void MX_GPIO_Init(void) { GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(LCD_INT_GPIO_Port, &GPIO_InitStruct); + /*Configure GPIO pin : PA0 */ + GPIO_InitStruct.Pin = GPIO_PIN_0; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /*Configure GPIO pins : BUTTON_SCROLL_Pin BUTTON_SELECT_Pin */ GPIO_InitStruct.Pin = BUTTON_SCROLL_Pin | BUTTON_SELECT_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c b/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c index c73d8aeaa..42b32fdc3 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c @@ -23,6 +23,7 @@ #include "main.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ +#include "lvgl.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -178,7 +179,7 @@ void SysTick_Handler(void) { /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ - + LVGL_Tick_Inc(); /* USER CODE END SysTick_IRQn 1 */ } diff --git a/projects/dashboard/src/platforms/stm32/dashboard.ioc b/projects/dashboard/src/platforms/stm32/dashboard.ioc index ec384714b..694d82c91 100644 --- a/projects/dashboard/src/platforms/stm32/dashboard.ioc +++ b/projects/dashboard/src/platforms/stm32/dashboard.ioc @@ -241,11 +241,10 @@ NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false -PA0/WKUP.GPIOParameters=GPIO_Label -PA0/WKUP.GPIO_Label=WAKEUP [B2] +PA0/WKUP.GPIOParameters=GPIO_PuPd +PA0/WKUP.GPIO_PuPd=GPIO_PULLDOWN PA0/WKUP.Locked=true -PA0/WKUP.Mode=SYS_WakeUp0 -PA0/WKUP.Signal=SYS_WKUP +PA0/WKUP.Signal=GPIO_Input PA13.GPIOParameters=GPIO_Label PA13.GPIO_Label=SWDIO PA13.Locked=true @@ -792,7 +791,7 @@ ProjectManager.FreePins=false ProjectManager.FreePinsContext= ProjectManager.HalAssertFull=false ProjectManager.HeapSize=0x200 -ProjectManager.KeepUserCode=false +ProjectManager.KeepUserCode=true ProjectManager.LastFirmware=true ProjectManager.LibraryCopy=2 ProjectManager.MainLocation=Core/Src @@ -802,7 +801,7 @@ ProjectManager.ProjectBuild=false ProjectManager.ProjectFileName=dashboard.ioc ProjectManager.ProjectName=dashboard ProjectManager.ProjectStructure= -ProjectManager.RegisterCallBack= +ProjectManager.RegisterCallBack=LTDC ProjectManager.StackSize=0x400 ProjectManager.TargetToolchain=STM32CubeIDE ProjectManager.ToolChainLocation= From e7a81f7b157f9b7ac653ec10ba882c1adf7e341b Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 14:06:33 -0400 Subject: [PATCH 22/34] update erm --- projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c b/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c index 42b32fdc3..a9faa1cf8 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/stm32f4xx_it.c @@ -179,7 +179,7 @@ void SysTick_Handler(void) { /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ - LVGL_Tick_Inc(); + lv_tick_inc(1); /* USER CODE END SysTick_IRQn 1 */ } From 74efa1baada9717f107626913702778002fe3c12 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 14:16:00 -0400 Subject: [PATCH 23/34] pls pls pls fix --- .../dashboard/src/platforms/stm32/Core/Inc/main.h | 2 ++ .../src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h | 2 +- .../dashboard/src/platforms/stm32/Core/Src/gpio.c | 6 ------ projects/dashboard/src/platforms/stm32/dashboard.ioc | 11 ++++++----- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h index 4ac638ccb..ade63e261 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h @@ -155,6 +155,8 @@ void Error_Handler(void); #define LCD_INT_GPIO_Port GPIOJ #define D20_Pin GPIO_PIN_12 #define D20_GPIO_Port GPIOH +#define WAKEUP_Pin GPIO_PIN_0 +#define WAKEUP_GPIO_Port GPIOA #define BUTTON_SCROLL_Pin GPIO_PIN_4 #define BUTTON_SCROLL_GPIO_Port GPIOC #define A7_Pin GPIO_PIN_13 diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h index ac8c2f177..ca112046f 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h @@ -196,7 +196,7 @@ extern "C" { #define USE_HAL_LPTIM_REGISTER_CALLBACKS \ 0U /* LPTIM register callback disabled */ #define USE_HAL_LTDC_REGISTER_CALLBACKS \ - 1U /* LTDC register callback enabled */ + 0U /* LTDC register callback disabled */ #define USE_HAL_MMC_REGISTER_CALLBACKS \ 0U /* MMC register callback disabled */ #define USE_HAL_NAND_REGISTER_CALLBACKS \ diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c index 8003d0d27..0d6d9b7f2 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c @@ -133,12 +133,6 @@ void MX_GPIO_Init(void) { GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(LCD_INT_GPIO_Port, &GPIO_InitStruct); - /*Configure GPIO pin : PA0 */ - GPIO_InitStruct.Pin = GPIO_PIN_0; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); - /*Configure GPIO pins : BUTTON_SCROLL_Pin BUTTON_SELECT_Pin */ GPIO_InitStruct.Pin = BUTTON_SCROLL_Pin | BUTTON_SELECT_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; diff --git a/projects/dashboard/src/platforms/stm32/dashboard.ioc b/projects/dashboard/src/platforms/stm32/dashboard.ioc index 694d82c91..ec384714b 100644 --- a/projects/dashboard/src/platforms/stm32/dashboard.ioc +++ b/projects/dashboard/src/platforms/stm32/dashboard.ioc @@ -241,10 +241,11 @@ NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false -PA0/WKUP.GPIOParameters=GPIO_PuPd -PA0/WKUP.GPIO_PuPd=GPIO_PULLDOWN +PA0/WKUP.GPIOParameters=GPIO_Label +PA0/WKUP.GPIO_Label=WAKEUP [B2] PA0/WKUP.Locked=true -PA0/WKUP.Signal=GPIO_Input +PA0/WKUP.Mode=SYS_WakeUp0 +PA0/WKUP.Signal=SYS_WKUP PA13.GPIOParameters=GPIO_Label PA13.GPIO_Label=SWDIO PA13.Locked=true @@ -791,7 +792,7 @@ ProjectManager.FreePins=false ProjectManager.FreePinsContext= ProjectManager.HalAssertFull=false ProjectManager.HeapSize=0x200 -ProjectManager.KeepUserCode=true +ProjectManager.KeepUserCode=false ProjectManager.LastFirmware=true ProjectManager.LibraryCopy=2 ProjectManager.MainLocation=Core/Src @@ -801,7 +802,7 @@ ProjectManager.ProjectBuild=false ProjectManager.ProjectFileName=dashboard.ioc ProjectManager.ProjectName=dashboard ProjectManager.ProjectStructure= -ProjectManager.RegisterCallBack=LTDC +ProjectManager.RegisterCallBack= ProjectManager.StackSize=0x400 ProjectManager.TargetToolchain=STM32CubeIDE ProjectManager.ToolChainLocation= From 64ad4f9822b6bbd2ab96d41dbcd27937d10998c7 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 14:28:05 -0400 Subject: [PATCH 24/34] wow i hate this --- .../dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h index ca112046f..1c9300129 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h @@ -196,7 +196,7 @@ extern "C" { #define USE_HAL_LPTIM_REGISTER_CALLBACKS \ 0U /* LPTIM register callback disabled */ #define USE_HAL_LTDC_REGISTER_CALLBACKS \ - 0U /* LTDC register callback disabled */ + 1U /* LTDC register callback disabled */ #define USE_HAL_MMC_REGISTER_CALLBACKS \ 0U /* MMC register callback disabled */ #define USE_HAL_NAND_REGISTER_CALLBACKS \ From 85df617d772e32a9069996371469edda63c0963f Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 10 May 2026 15:33:53 -0400 Subject: [PATCH 25/34] update README.md --- scripts/daq/README.md | 101 ++++++++++++++++++++++-------------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/scripts/daq/README.md b/scripts/daq/README.md index 060144cb0..0d0145057 100644 --- a/scripts/daq/README.md +++ b/scripts/daq/README.md @@ -3,12 +3,10 @@ Real-time CAN telemetry for the MAC Formula Electric racecar. Data flows from two CAN buses on the Raspberry Pi → InfluxDB → Grafana dashboard. ---- - ## Architecture ``` -Racecar CAN buses +Racecar can0 (Vehicle bus — FC, LVC, TMS, BMS, GPS) can1 (Powertrain bus — Inverter 1, Inverter 2) │ @@ -16,15 +14,15 @@ Racecar CAN buses Raspberry Pi 4 ┌─────────────────────────────────┐ │ candump -L can0 / can1 │ raw CAN frames - │ │ │ │ logger.py │ decode via veh.dbc + pt.dbc - │ │ │ - │ CSV logs (logs/can_log_*.csv) │ full signal archive - │ InfluxDB (localhost:8086) │ key signals + faults + │ CSV logs (logs/can_log_*.csv) │ full signal archive on SD card └─────────────────────────────────┘ + │ 4G LTE (SIM card) + ▼ + InfluxDB Cloud (AWS us-east-1, free tier) │ - ▼ (network / same machine) - Grafana (localhost:3000 or Grafana Cloud) + ▼ + Dock laptop — Grafana (local, free) ┌─────────────────────────────────┐ │ Vehicle Safety row │ │ Performance row │ @@ -32,8 +30,7 @@ Racecar CAN buses └─────────────────────────────────┘ ``` ---- - +The Pi runs **only** logger.py — no InfluxDB or Grafana on the Pi itself. Data is written to InfluxDB Cloud over cellular. Grafana on the dock laptop queries InfluxDB Cloud independently — the Pi and laptop only need their own internet connections, not the same network. --- @@ -117,31 +114,19 @@ cd scripts/daq bash setup.sh ``` -The script: -1. Installs `python3`, `can-utils`, `influxdb2`, and Grafana from the bundled `.deb` -2. Enables InfluxDB and Grafana as systemd services (auto-start on boot) -3. Brings up `can0` and `can1` at 500 kbit/s -4. Creates a Python virtualenv and installs requirements +The script installs `python3`, `can-utils`, configures `can0`/`can1` at 500 kbit/s, and creates a Python virtualenv. After running `setup.sh`: -1. Open `http://localhost:8086` → complete InfluxDB setup: - - Username: `admin`, Password: (choose one) - - Organization: `macformula` - - Bucket: `macfe` - - Generate an **All Access API token** - -2. Create `.env` in this directory: +1. Copy `.env` onto the Pi (it's gitignored — copy it manually or `scp` it over): ``` - INFLUX_URL=http://localhost:8086 - INFLUX_TOKEN= + INFLUX_URL=https://us-east-1-1.aws.cloud2.influxdata.com + INFLUX_TOKEN= INFLUX_ORG=macformula INFLUX_BUCKET=macfe ``` -3. Open `http://localhost:3000` (admin/admin) → import `grafana_dashboard.json` - -4. Start logging: +2. Start logging: ```bash source venv/bin/activate python logger.py @@ -149,32 +134,50 @@ After running `setup.sh`: --- -## Dev Setup (Windows — no Pi, no CAN bus) +## Linux VM Setup (testing logger.py without a Pi) -Uses InfluxDB Cloud (free tier) and Grafana Cloud instead of local installs. +Use this to verify logger.py works before deploying to the Pi. Requires Ubuntu 22.04+ in VirtualBox or VMware — **not WSL2** (WSL2 lacks the `vcan` kernel module). -1. Create accounts at `cloud2.influxdata.com` and `grafana.com` -2. In InfluxDB Cloud: create bucket `macfe`, generate All Access token -3. Create `.env`: - ``` - INFLUX_URL=https://.aws.cloud2.influxdata.com - INFLUX_TOKEN= - INFLUX_ORG= - INFLUX_BUCKET=macfe - ``` -4. Install Python dependencies: - ```bash - python -m venv venv - venv\Scripts\activate - pip install -r requirements.txt - ``` -5. Run the simulated data writer to verify the connection: +```bash +cd scripts/daq +bash setup_vm.sh +``` + +The script installs Python deps and brings up `vcan0`/`vcan1` (virtual CAN interfaces). Uses the cloud InfluxDB credentials already in `.env` — no local InfluxDB needed. + +```bash +# Terminal 1 — run the logger: +source venv/bin/activate +python logger.py --interfaces vcan0 vcan1 + +# Terminal 2 — inject test CAN frames: +cansend vcan0 624#1234567890ABCDEF # Pack_State → writes to InfluxDB +cangen vcan0 -g 50 & # random frames → exercises CSV logging +``` + +Verify: CSV appears in `logs/`, and `car_data` measurement appears in InfluxDB Cloud Data Explorer. + +--- + +## Dock Laptop Setup (Grafana) + +Run once on the laptop you'll use at the dock: + +1. Install Grafana via Docker: ```bash - python test_influx.py + docker run -d -p 3000:3000 --name grafana grafana/grafana ``` - Should print `Connection OK — test write succeeded.` and stream fake sensor values. + Open `http://localhost:3000` (admin / admin). + +2. Add InfluxDB Cloud as a data source: + - Type: **InfluxDB**, Query language: **Flux** + - URL: value of `INFLUX_URL` from `.env` + - Token: value of `INFLUX_TOKEN` from `.env` + - Organisation: `macformula`, Default bucket: `macfe` + +3. Import `grafana_dashboard.json` via **Dashboards → Import**. -6. In Grafana Cloud: add InfluxDB datasource (Flux, same credentials as `.env`), then import `grafana_dashboard.json`. +The dashboard will live-update as the Pi writes data to InfluxDB Cloud. The laptop just needs any internet connection — it doesn't need to be near the Pi. --- From f81eeac755ca34561ad071a94ef33e5787b2b23a Mon Sep 17 00:00:00 2001 From: Noah Jaye Date: Sun, 10 May 2026 18:34:44 -0400 Subject: [PATCH 26/34] exclude stm32 from format checks --- .github/workflows/pr-format-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr-format-check.yml b/.github/workflows/pr-format-check.yml index a9c458265..72ba92896 100644 --- a/.github/workflows/pr-format-check.yml +++ b/.github/workflows/pr-format-check.yml @@ -14,4 +14,5 @@ jobs: uses: jidicula/clang-format-action@v4.15.0 with: check-path: '.' + exclude-regex: '.*/platforms/stm32/Core/.*' clang-format-version: '20' \ No newline at end of file From 3b13d6bd0c1899b89e5b0603df3a0eee7c892f81 Mon Sep 17 00:00:00 2001 From: Noah Jaye Date: Sun, 10 May 2026 19:21:59 -0400 Subject: [PATCH 27/34] Examine bindings failure --- projects/dashboard/include/bindings.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/projects/dashboard/include/bindings.hpp b/projects/dashboard/include/bindings.hpp index 4d26a9d2b..5f439db1c 100644 --- a/projects/dashboard/include/bindings.hpp +++ b/projects/dashboard/include/bindings.hpp @@ -9,8 +9,6 @@ extern macfe::periph::CanBase& veh_can_base; extern macfe::periph::DigitalInput& button_scroll; extern macfe::periph::DigitalInput& button_enter; - - extern void Initialize(); extern void Shutdown(); From 124ec353bbe989e1bb664e3ef35a561d13b0a315 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Tue, 12 May 2026 18:18:31 -0400 Subject: [PATCH 28/34] .sh file for influxdb cloud, minimal setup --- scripts/daq/setup_pi.sh | 63 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 scripts/daq/setup_pi.sh diff --git a/scripts/daq/setup_pi.sh b/scripts/daq/setup_pi.sh new file mode 100644 index 000000000..0339616a1 --- /dev/null +++ b/scripts/daq/setup_pi.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# DAQ setup for Raspberry Pi — cloud deployment. +# Installs only what logger.py needs: Python env + CAN interfaces. +# InfluxDB and Grafana run in the cloud, not on the Pi. +# Run once on a fresh Pi: bash setup_pi.sh + +set -e + +echo "=== MAC Formula DAQ Pi Setup ===" +echo "" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# ── System packages ─────────────────────────────────────────────────────────── +echo "[1/3] Installing system packages..." +sudo apt update -q +sudo apt install -y python3 python3-venv python3-pip can-utils + +# ── CAN interfaces ──────────────────────────────────────────────────────────── +echo "[2/3] Configuring CAN interfaces..." + +if ! grep -q "^can$" /etc/modules 2>/dev/null; then + echo "can" | sudo tee -a /etc/modules +fi +if ! grep -q "^can_raw$" /etc/modules 2>/dev/null; then + echo "can_raw" | sudo tee -a /etc/modules +fi + +sudo ip link set can0 up type can bitrate 500000 2>/dev/null \ + && echo " can0 up at 500 kbit/s" \ + || echo " can0 not available (OK if running without hardware)" + +sudo ip link set can1 up type can bitrate 500000 2>/dev/null \ + && echo " can1 up at 500 kbit/s" \ + || echo " can1 not available (OK for single-bus setup)" + +# ── Python environment ──────────────────────────────────────────────────────── +echo "[3/3] Setting up Python virtual environment..." + +cd "$SCRIPT_DIR" +python3 -m venv venv +source venv/bin/activate +pip install --upgrade pip --quiet +pip install -r requirements.txt --quiet +deactivate +echo " venv created." + +# ── Done ────────────────────────────────────────────────────────────────────── +echo "" +echo "=== Setup complete ===" +echo "" +echo "Next steps:" +echo "" +echo "1. Copy .env to $SCRIPT_DIR/.env with your InfluxDB Cloud credentials:" +echo " INFLUX_URL=https://us-east-1-1.aws.cloud2.influxdata.com" +echo " INFLUX_TOKEN=" +echo " INFLUX_ORG=macformula" +echo " INFLUX_BUCKET=macfe" +echo "" +echo "2. Run the logger:" +echo " source $SCRIPT_DIR/venv/bin/activate" +echo " python $SCRIPT_DIR/logger.py" +echo "" From 96361d1dd21a8068cb376c0a604b45054a71b359 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Mon, 18 May 2026 12:47:57 -0400 Subject: [PATCH 29/34] add APPS and BPPS --- scripts/daq/README.md | 2 +- scripts/daq/grafana_dashboard.json | 135 ++++++++++++++++++++++++++++- scripts/daq/logger.py | 5 ++ scripts/daq/test_influx.py | 9 +- 4 files changed, 147 insertions(+), 4 deletions(-) diff --git a/scripts/daq/README.md b/scripts/daq/README.md index 0d0145057..de3f5afca 100644 --- a/scripts/daq/README.md +++ b/scripts/daq/README.md @@ -30,7 +30,7 @@ Racecar └─────────────────────────────────┘ ``` -The Pi runs **only** logger.py — no InfluxDB or Grafana on the Pi itself. Data is written to InfluxDB Cloud over cellular. Grafana on the dock laptop queries InfluxDB Cloud independently — the Pi and laptop only need their own internet connections, not the same network. +The Pi runs **only** logger.py, no InfluxDB or Grafana on the Pi itself. Data is written to InfluxDB Cloud over cellular. Grafana on the dock laptop queries InfluxDB Cloud independently, the Pi and laptop only need their own internet connections, not the same network. --- diff --git a/scripts/daq/grafana_dashboard.json b/scripts/daq/grafana_dashboard.json index 311453eeb..529feacc3 100644 --- a/scripts/daq/grafana_dashboard.json +++ b/scripts/daq/grafana_dashboard.json @@ -3,7 +3,7 @@ { "name": "DS_INFLUXDB", "label": "InfluxDB", - "description": "InfluxDB Cloud datasource — must use Flux query language", + "description": "InfluxDB Cloud datasource \u00e2\u20ac\u201d must use Flux query language", "type": "datasource", "pluginId": "influxdb", "pluginName": "InfluxDB" @@ -39,7 +39,7 @@ "annotations": { "list": [] }, - "description": "MAC Formula Electric — Real-time racecar telemetry", + "description": "MAC Formula Electric \u00e2\u20ac\u201d Real-time racecar telemetry", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, @@ -1529,6 +1529,137 @@ ], "title": "Segment 6 Temp", "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 75 + }, + "id": 200, + "title": "Pedal Inputs", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "lineWidth": 2, + "fillOpacity": 10, + "spanNulls": false + }, + "mappings": [], + "min": 0, + "max": 100, + "unit": "percent", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "apps1" + }, + "properties": [ + { + "id": "displayName", + "value": "APPS 1" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "apps2" + }, + "properties": [ + { + "id": "displayName", + "value": "APPS 2" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "bpps" + }, + "properties": [ + { + "id": "displayName", + "value": "BPPS" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 76 + }, + "id": 201, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "A", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Apps1Percent\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"apps1\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "B", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"Apps2Percent\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"apps2\")" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_INFLUXDB}" + }, + "refId": "C", + "query": "from(bucket: \"macfe\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"car_data\")\n |> filter(fn: (r) => r.signal == \"BppsPercent\")\n |> filter(fn: (r) => r._field == \"value\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"_field\", value: \"bpps\")" + } + ], + "title": "APPS & BPPS (%)", + "type": "timeseries" } ], "refresh": "5s", diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index 414c6b113..7bc0899f7 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -82,6 +82,9 @@ "HighThermValue", # segment max temp (°C) — BmsBroadcast (0x1839F380) "LowThermValue", # segment min temp (°C) — BmsBroadcast "AvgThermValue", # segment avg temp (°C) — BmsBroadcast + "Apps1Percent", # APPS sensor 1 position (%) — AppsDebug (401) + "Apps2Percent", # APPS sensor 2 position (%) — AppsDebug (401) + "BppsPercent", # brake pedal position (%) — BppsSteerDebug (402) } # Boolean fault signals from FcAlerts (202) and LvStatus (211). @@ -201,6 +204,8 @@ def decode_frame( Returns (message_name, {signal_name: (value, unit)}) or None if the message is not in the DBC or decoding fails. + + can id - data """ msg = next((m for m in db.messages if m.frame_id == can_id), None) #cycle thru dbc to find matched can id if msg is None: diff --git a/scripts/daq/test_influx.py b/scripts/daq/test_influx.py index 0d2943f22..c431cbbcc 100644 --- a/scripts/daq/test_influx.py +++ b/scripts/daq/test_influx.py @@ -77,6 +77,9 @@ def main(): igbt_temp = inverter_temp + random.uniform(-2, 2) pack_current = 50 + 30 * math.sin(t * 0.4) + random.uniform(-5, 5) speed = 60 + 25 * math.sin(t * 0.05) + random.uniform(-2, 2) + apps1 = max(0, min(100, 50 + 45 * math.sin(t * 0.2) + random.uniform(-2, 2))) + apps2 = max(0, min(100, apps1 + random.uniform(-3, 3))) + bpps = max(0, min(100, 20 * abs(math.sin(t * 0.15)) + random.uniform(-1, 1))) # Scalar signals (no extra tags needed) for name, value in [ @@ -84,6 +87,9 @@ def main(): ("Pack_Current", pack_current), ("Pack_SOC", soc), ("Speed", speed), + ("Apps1Percent", apps1), + ("Apps2Percent", apps2), + ("BppsPercent", bpps), ]: write_signal(name, value) @@ -134,7 +140,8 @@ def main(): print( f" t={t:.0f}s inv1={inv1_rpm:.0f}rpm inv2={inv2_rpm:.0f}rpm " f"v={battery_v:.1f}V soc={soc:.1f}% " - f"mtemp={motor_temp:.1f}°C curr={pack_current:.1f}A spd={speed:.1f}mph" + f"mtemp={motor_temp:.1f}°C curr={pack_current:.1f}A spd={speed:.1f}mph " + f"apps1={apps1:.1f}% apps2={apps2:.1f}% bpps={bpps:.1f}%" ) t += 1 From 513e1752549041f1f97f84d343c9f17a80f9e856 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Fri, 29 May 2026 10:39:30 -0400 Subject: [PATCH 30/34] add thresholds and new fields --- scripts/daq/logger.py | 21 ++++++++++++++++++++- scripts/daq/test_influx.py | 34 +++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index 7bc0899f7..c532662ad 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -52,6 +52,7 @@ TARGET_IDS = { 202, # FcAlerts (veh) - 13 boolean fault flags 211, # LvStatus (veh) - ImdFault, BmsFault + 214, # LvDcdc (veh) - LV battery/bus voltage + bus current 230, # DashCommand (veh) - Speed 300, # Accumulator_Soc (veh) - battery state 310, # SuspensionTravel34 (veh) - linear pots @@ -85,6 +86,8 @@ "Apps1Percent", # APPS sensor 1 position (%) — AppsDebug (401) "Apps2Percent", # APPS sensor 2 position (%) — AppsDebug (401) "BppsPercent", # brake pedal position (%) — BppsSteerDebug (402) + "LvBatteryVoltage", # LV battery voltage (V) — LvDcdc (214) + "LvBatteryTemp", # LV battery temperature (°C) — LvDcdc (214) } # Boolean fault signals from FcAlerts (202) and LvStatus (211). @@ -108,6 +111,15 @@ "ImdFault": ("IMD", "Isolation Fault", "CRITICAL"), "BmsFault": ("BMS", "BMS Fault", "CRITICAL"), } +SIGNAL_THRESHOLDS = { + "Pack_Current": (None, 59.0, "BMS", "Pack Overcurrent", "CRITICAL"), + "Pack_Inst_Voltage": (360.0, 600.0, "BMS", "Pack Voltage Out of Range", "CRITICAL"), + "TempMotor": (None, 70.0, "Motor", "Motor Overtemperature", "CRITICAL"), + "TempInverter": (None, 60.0, "Inverter", "Inverter Overtemperature", "CRITICAL"), + "TempIGBT": (None, 60.0, "Inverter", "IGBT Overtemperature", "CRITICAL"), + "HighThermValue": (None, 60.0, "BMS", "Battery Segment Overtemperature","CRITICAL"), + "LvBatteryVoltage": (18.0, 29.0, "LV", "LV Battery Voltage Out of Range","CRITICAL"), +} # CAN IDs that produce the same signal names for left and right — tagged "inverter". INV1_IDS = {643, 645} @@ -357,7 +369,14 @@ def cleanup(_sig=None, _frame=None): write_fault_to_influx(system, fault_desc, severity, timestamp) if sig_name in IMPORTANT_SIGNALS: - write_to_influx(sig_name, float(value), timestamp, extra_tags) + fval = float(value) + write_to_influx(sig_name, fval, timestamp, extra_tags) + + if sig_name in SIGNAL_THRESHOLDS: + lo, hi, sys_name, desc, sev = SIGNAL_THRESHOLDS[sig_name] + checked = abs(fval) if sig_name == "Pack_Current" else fval + if (lo is not None and checked < lo) or (hi is not None and checked > hi): + write_fault_to_influx(sys_name, desc, sev, timestamp) csv_file.close() return 0 diff --git a/scripts/daq/test_influx.py b/scripts/daq/test_influx.py index c431cbbcc..372f2688f 100644 --- a/scripts/daq/test_influx.py +++ b/scripts/daq/test_influx.py @@ -70,26 +70,34 @@ def main(): # Simulate values using actual DBC signal names so the dashboard queries match. inv1_rpm = 2000 + 1500 * math.sin(t * 0.3) + random.uniform(-50, 50) inv2_rpm = 1800 + 1400 * math.sin(t * 0.3 + 0.2) + random.uniform(-50, 50) - battery_v = 400 + 10 * math.sin(t * 0.05) + random.uniform(-1, 1) + # Pack voltage: 360V min (cell damage), 600V max; simulate normal discharge ~490V + battery_v = 490 + 15 * math.sin(t * 0.05) + random.uniform(-1, 1) soc = max(20, 90 - t * 0.2 + random.uniform(-0.5, 0.5)) - motor_temp = 40 + 20 * (1 - math.exp(-t * 0.02)) + random.uniform(-1, 1) - inverter_temp = 35 + 15 * (1 - math.exp(-t * 0.015)) + random.uniform(-1, 1) + # Temps warm up from ambient; stay well below 70°C motor / 60°C inverter limits + motor_temp = 30 + 18 * (1 - math.exp(-t * 0.02)) + random.uniform(-1, 1) + inverter_temp = 28 + 14 * (1 - math.exp(-t * 0.015)) + random.uniform(-1, 1) igbt_temp = inverter_temp + random.uniform(-2, 2) - pack_current = 50 + 30 * math.sin(t * 0.4) + random.uniform(-5, 5) + # Pack current: fuse rated 60A, absolute max 59A; simulate normal load ~10–45A + pack_current = 27 + 18 * math.sin(t * 0.4) + random.uniform(-3, 3) speed = 60 + 25 * math.sin(t * 0.05) + random.uniform(-2, 2) apps1 = max(0, min(100, 50 + 45 * math.sin(t * 0.2) + random.uniform(-2, 2))) apps2 = max(0, min(100, apps1 + random.uniform(-3, 3))) bpps = max(0, min(100, 20 * abs(math.sin(t * 0.15)) + random.uniform(-1, 1))) + # LV battery: 18V min, 29V fully charged; simulate ~24V nominal system + lv_batt_v = 24.5 + 1.5 * math.sin(t * 0.02) + random.uniform(-0.1, 0.1) + lv_batt_temp = 25 + 8 * (1 - math.exp(-t * 0.01)) + random.uniform(-0.5, 0.5) # Scalar signals (no extra tags needed) for name, value in [ - ("Pack_Inst_Voltage", battery_v), - ("Pack_Current", pack_current), - ("Pack_SOC", soc), - ("Speed", speed), - ("Apps1Percent", apps1), - ("Apps2Percent", apps2), - ("BppsPercent", bpps), + ("Pack_Inst_Voltage", battery_v), + ("Pack_Current", pack_current), + ("Pack_SOC", soc), + ("Speed", speed), + ("Apps1Percent", apps1), + ("Apps2Percent", apps2), + ("BppsPercent", bpps), + ("LvBatteryVoltage", lv_batt_v), + ("LvBatteryTemp", lv_batt_temp), ]: write_signal(name, value) @@ -131,8 +139,8 @@ def main(): # Occasionally write a simulated fault if t > 0 and int(t) % 15 == 0: - write_fault("BMS", "Low State of Charge", "WARNING") - print(f" t={t:.0f}s Wrote fault: BMS / Low State of Charge / WARNING") + write_fault("CAN", "TX Error", "WARNING") + print(f" t={t:.0f}s Wrote fault: CAN / TX Error / WARNING") if t > 0 and int(t) % 30 == 0: write_fault("Motor", "Left Motor Running Error", "CRITICAL") print(f" t={t:.0f}s Wrote fault: Motor / Left Motor Running Error / CRITICAL") From 782520efaafd7ea9749e938309c956755ab871a6 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sat, 13 Jun 2026 14:41:06 -0400 Subject: [PATCH 31/34] dont need dashboard changes, restore them --- .../src/platforms/stm32/Core/Inc/main.h | 2 - .../src/platforms/stm32/Core/Src/dma2d.c | 139 ++++++++---------- .../src/platforms/stm32/Core/Src/gpio.c | 6 + .../src/platforms/stm32/dashboard.ioc | 11 +- 4 files changed, 75 insertions(+), 83 deletions(-) diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h index ade63e261..4ac638ccb 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/main.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/main.h @@ -155,8 +155,6 @@ void Error_Handler(void); #define LCD_INT_GPIO_Port GPIOJ #define D20_Pin GPIO_PIN_12 #define D20_GPIO_Port GPIOH -#define WAKEUP_Pin GPIO_PIN_0 -#define WAKEUP_GPIO_Port GPIOA #define BUTTON_SCROLL_Pin GPIO_PIN_4 #define BUTTON_SCROLL_GPIO_Port GPIOC #define A7_Pin GPIO_PIN_13 diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/dma2d.c b/projects/dashboard/src/platforms/stm32/Core/Src/dma2d.c index f03f02719..7eed7e35c 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/dma2d.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/dma2d.c @@ -1,21 +1,21 @@ /* USER CODE BEGIN Header */ /** - ****************************************************************************** - * @file dma2d.c - * @brief This file provides code for the configuration - * of the DMA2D instances. - ****************************************************************************** - * @attention - * - * Copyright (c) 2026 STMicroelectronics. - * All rights reserved. - * - * This software is licensed under terms that can be found in the LICENSE file - * in the root directory of this software component. - * If no LICENSE file comes with this software, it is provided AS-IS. - * - ****************************************************************************** - */ + ****************************************************************************** + * @file dma2d.c + * @brief This file provides code for the configuration + * of the DMA2D instances. + ****************************************************************************** + * @attention + * + * Copyright (c) 2026 STMicroelectronics. + * All rights reserved. + * + * This software is licensed under terms that can be found in the LICENSE file + * in the root directory of this software component. + * If no LICENSE file comes with this software, it is provided AS-IS. + * + ****************************************************************************** + */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "dma2d.h" @@ -27,75 +27,64 @@ DMA2D_HandleTypeDef hdma2d; /* DMA2D init function */ -void MX_DMA2D_Init(void) -{ - - /* USER CODE BEGIN DMA2D_Init 0 */ - - /* USER CODE END DMA2D_Init 0 */ - - /* USER CODE BEGIN DMA2D_Init 1 */ - - /* USER CODE END DMA2D_Init 1 */ - hdma2d.Instance = DMA2D; - hdma2d.Init.Mode = DMA2D_M2M; - hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565; - hdma2d.Init.OutputOffset = 0; - hdma2d.LayerCfg[1].InputOffset = 0; - hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; - hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA; - hdma2d.LayerCfg[1].InputAlpha = 0; - if (HAL_DMA2D_Init(&hdma2d) != HAL_OK) - { - Error_Handler(); - } - if (HAL_DMA2D_ConfigLayer(&hdma2d, 1) != HAL_OK) - { - Error_Handler(); - } - /* USER CODE BEGIN DMA2D_Init 2 */ - - /* USER CODE END DMA2D_Init 2 */ - +void MX_DMA2D_Init(void) { + /* USER CODE BEGIN DMA2D_Init 0 */ + + /* USER CODE END DMA2D_Init 0 */ + + /* USER CODE BEGIN DMA2D_Init 1 */ + + /* USER CODE END DMA2D_Init 1 */ + hdma2d.Instance = DMA2D; + hdma2d.Init.Mode = DMA2D_M2M; + hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565; + hdma2d.Init.OutputOffset = 0; + hdma2d.LayerCfg[1].InputOffset = 0; + hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565; + hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA; + hdma2d.LayerCfg[1].InputAlpha = 0; + if (HAL_DMA2D_Init(&hdma2d) != HAL_OK) { + Error_Handler(); + } + if (HAL_DMA2D_ConfigLayer(&hdma2d, 1) != HAL_OK) { + Error_Handler(); + } + /* USER CODE BEGIN DMA2D_Init 2 */ + + /* USER CODE END DMA2D_Init 2 */ } -void HAL_DMA2D_MspInit(DMA2D_HandleTypeDef* dma2dHandle) -{ - - if(dma2dHandle->Instance==DMA2D) - { - /* USER CODE BEGIN DMA2D_MspInit 0 */ +void HAL_DMA2D_MspInit(DMA2D_HandleTypeDef* dma2dHandle) { + if (dma2dHandle->Instance == DMA2D) { + /* USER CODE BEGIN DMA2D_MspInit 0 */ - /* USER CODE END DMA2D_MspInit 0 */ - /* DMA2D clock enable */ - __HAL_RCC_DMA2D_CLK_ENABLE(); + /* USER CODE END DMA2D_MspInit 0 */ + /* DMA2D clock enable */ + __HAL_RCC_DMA2D_CLK_ENABLE(); - /* DMA2D interrupt Init */ - HAL_NVIC_SetPriority(DMA2D_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(DMA2D_IRQn); - /* USER CODE BEGIN DMA2D_MspInit 1 */ + /* DMA2D interrupt Init */ + HAL_NVIC_SetPriority(DMA2D_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(DMA2D_IRQn); + /* USER CODE BEGIN DMA2D_MspInit 1 */ - /* USER CODE END DMA2D_MspInit 1 */ - } + /* USER CODE END DMA2D_MspInit 1 */ + } } -void HAL_DMA2D_MspDeInit(DMA2D_HandleTypeDef* dma2dHandle) -{ - - if(dma2dHandle->Instance==DMA2D) - { - /* USER CODE BEGIN DMA2D_MspDeInit 0 */ +void HAL_DMA2D_MspDeInit(DMA2D_HandleTypeDef* dma2dHandle) { + if (dma2dHandle->Instance == DMA2D) { + /* USER CODE BEGIN DMA2D_MspDeInit 0 */ - /* USER CODE END DMA2D_MspDeInit 0 */ - /* Peripheral clock disable */ - __HAL_RCC_DMA2D_CLK_DISABLE(); + /* USER CODE END DMA2D_MspDeInit 0 */ + /* Peripheral clock disable */ + __HAL_RCC_DMA2D_CLK_DISABLE(); - /* DMA2D interrupt Deinit */ - HAL_NVIC_DisableIRQ(DMA2D_IRQn); - /* USER CODE BEGIN DMA2D_MspDeInit 1 */ + /* DMA2D interrupt Deinit */ + HAL_NVIC_DisableIRQ(DMA2D_IRQn); + /* USER CODE BEGIN DMA2D_MspDeInit 1 */ - /* USER CODE END DMA2D_MspDeInit 1 */ - } + /* USER CODE END DMA2D_MspDeInit 1 */ + } } /* USER CODE BEGIN 1 */ diff --git a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c index 0d6d9b7f2..8003d0d27 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c +++ b/projects/dashboard/src/platforms/stm32/Core/Src/gpio.c @@ -133,6 +133,12 @@ void MX_GPIO_Init(void) { GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(LCD_INT_GPIO_Port, &GPIO_InitStruct); + /*Configure GPIO pin : PA0 */ + GPIO_InitStruct.Pin = GPIO_PIN_0; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /*Configure GPIO pins : BUTTON_SCROLL_Pin BUTTON_SELECT_Pin */ GPIO_InitStruct.Pin = BUTTON_SCROLL_Pin | BUTTON_SELECT_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; diff --git a/projects/dashboard/src/platforms/stm32/dashboard.ioc b/projects/dashboard/src/platforms/stm32/dashboard.ioc index ec384714b..694d82c91 100644 --- a/projects/dashboard/src/platforms/stm32/dashboard.ioc +++ b/projects/dashboard/src/platforms/stm32/dashboard.ioc @@ -241,11 +241,10 @@ NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false -PA0/WKUP.GPIOParameters=GPIO_Label -PA0/WKUP.GPIO_Label=WAKEUP [B2] +PA0/WKUP.GPIOParameters=GPIO_PuPd +PA0/WKUP.GPIO_PuPd=GPIO_PULLDOWN PA0/WKUP.Locked=true -PA0/WKUP.Mode=SYS_WakeUp0 -PA0/WKUP.Signal=SYS_WKUP +PA0/WKUP.Signal=GPIO_Input PA13.GPIOParameters=GPIO_Label PA13.GPIO_Label=SWDIO PA13.Locked=true @@ -792,7 +791,7 @@ ProjectManager.FreePins=false ProjectManager.FreePinsContext= ProjectManager.HalAssertFull=false ProjectManager.HeapSize=0x200 -ProjectManager.KeepUserCode=false +ProjectManager.KeepUserCode=true ProjectManager.LastFirmware=true ProjectManager.LibraryCopy=2 ProjectManager.MainLocation=Core/Src @@ -802,7 +801,7 @@ ProjectManager.ProjectBuild=false ProjectManager.ProjectFileName=dashboard.ioc ProjectManager.ProjectName=dashboard ProjectManager.ProjectStructure= -ProjectManager.RegisterCallBack= +ProjectManager.RegisterCallBack=LTDC ProjectManager.StackSize=0x400 ProjectManager.TargetToolchain=STM32CubeIDE ProjectManager.ToolChainLocation= From 938687eeae3493ccbfcecb6b6ce0783dc42fca73 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sat, 13 Jun 2026 15:11:56 -0400 Subject: [PATCH 32/34] another unneccesary change to dash --- .../dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h index 1c9300129..ac8c2f177 100644 --- a/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h +++ b/projects/dashboard/src/platforms/stm32/Core/Inc/stm32f4xx_hal_conf.h @@ -196,7 +196,7 @@ extern "C" { #define USE_HAL_LPTIM_REGISTER_CALLBACKS \ 0U /* LPTIM register callback disabled */ #define USE_HAL_LTDC_REGISTER_CALLBACKS \ - 1U /* LTDC register callback disabled */ + 1U /* LTDC register callback enabled */ #define USE_HAL_MMC_REGISTER_CALLBACKS \ 0U /* MMC register callback disabled */ #define USE_HAL_NAND_REGISTER_CALLBACKS \ From 02329d6732f71519bd05c2c37ba4270cc1d14138 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 14 Jun 2026 10:45:32 -0400 Subject: [PATCH 33/34] make influx optional --- scripts/daq/logger.py | 88 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index c532662ad..b59160dbc 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -131,17 +131,67 @@ ) """ Influx DB Helper Functions """ -# ------------------------------------------------------------------------- -client = InfluxDBClient( - url=os.getenv("INFLUX_URL"), - token=os.getenv("INFLUX_TOKEN"), - org=os.getenv("INFLUX_ORG"), - verify_ssl=False, -) -write_api = client.write_api() +REQUIRED_INFLUX_ENV = ("INFLUX_URL", "INFLUX_TOKEN", "INFLUX_ORG", "INFLUX_BUCKET") +client: InfluxDBClient | None = None +write_api = None +influx_warning_printed = False + + +def init_influx() -> bool: + """Initialize optional InfluxDB telemetry; CSV logging does not depend on it.""" + missing = [name for name in REQUIRED_INFLUX_ENV if not os.getenv(name)] + if missing: + print( + f"Warning: InfluxDB disabled; missing environment variables: {', '.join(missing)}\n" + "CSV logging will continue locally.", + file=sys.stderr, + ) + return False + + global client, write_api + try: + client = InfluxDBClient( + url=os.getenv("INFLUX_URL"), + token=os.getenv("INFLUX_TOKEN"), + org=os.getenv("INFLUX_ORG"), + verify_ssl=False, + ) + write_api = client.write_api() + except Exception as exc: + print( + f"Warning: InfluxDB disabled; client setup failed: {exc}\n" + "CSV logging will continue locally.", + file=sys.stderr, + ) + client = None + write_api = None + return False + + print("InfluxDB telemetry enabled.", file=sys.stderr) + return True + + +def _write_influx_point(point: Point) -> None: + """Best-effort InfluxDB write; never interrupt local CSV logging.""" + global influx_warning_printed + if write_api is None: + return + + try: + write_api.write(bucket=os.getenv("INFLUX_BUCKET"), record=point) + except Exception as exc: + if not influx_warning_printed: + print( + f"Warning: InfluxDB write failed; continuing CSV logging only: {exc}", + file=sys.stderr, + ) + influx_warning_printed = True def write_to_influx(sig_name: str, value: float, timestamp: float, extra_tags: dict = None) -> None: + if write_api is None: + return + point = ( Point("car_data") .tag("signal", sig_name) @@ -151,10 +201,13 @@ def write_to_influx(sig_name: str, value: float, timestamp: float, extra_tags: d if extra_tags: for k, v in extra_tags.items(): point = point.tag(k, v) - write_api.write(bucket=os.getenv("INFLUX_BUCKET"), record=point) + _write_influx_point(point) def write_fault_to_influx(system: str, fault: str, severity: str, timestamp: float) -> None: + if write_api is None: + return + point = ( Point("faults") .tag("system", system) @@ -163,7 +216,7 @@ def write_fault_to_influx(system: str, fault: str, severity: str, timestamp: flo .field("active", 1) .time(int(timestamp * 1e9)) ) - write_api.write(bucket=os.getenv("INFLUX_BUCKET"), record=point) + _write_influx_point(point) # ------------------------------------------------------------------------- def load_dbc(veh_dbc: Path, pt_dbc: Path | None) -> cantools.database.Database: @@ -180,7 +233,7 @@ def open_csv(log_dir: Path) -> tuple[Path, object]: log_dir.mkdir(parents=True, exist_ok=True) ts = datetime.now().strftime('%Y%m%d_%H%M%S') path = log_dir / f'can_log_{ts}.csv' - f = open(path, 'w', newline='', encoding='utf-8') + f = open(path, 'w', newline='', encoding='utf-8', buffering=1) f.write('timestamp_epoch_s,message_id_hex,message_name,signal_name,value,unit\n') return path, f @@ -256,6 +309,7 @@ def _candump_reader( "On the Raspberry Pi install can-utils: sudo apt install can-utils", file=sys.stderr, ) + out_queue.put(("error", interface)) out_queue.put(None) return @@ -297,6 +351,8 @@ def main() -> int: print(f'Error: veh.dbc not found at {veh_dbc}', file=sys.stderr) return 1 + init_influx() + db = load_dbc(veh_dbc, pt_dbc) log_path, csv_file = open_csv(log_dir) @@ -321,6 +377,8 @@ def cleanup(_sig=None, _frame=None): p.terminate() p.wait() csv_file.close() + if client is not None: + client.close() sys.exit(0) signal.signal(signal.SIGINT, cleanup) @@ -328,9 +386,13 @@ def cleanup(_sig=None, _frame=None): num_interfaces = len(interfaces) done = 0 + reader_errors = 0 while done < num_interfaces: line = line_queue.get() + if isinstance(line, tuple) and line[0] == "error": + reader_errors += 1 + continue if line is None: done += 1 continue @@ -379,7 +441,9 @@ def cleanup(_sig=None, _frame=None): write_fault_to_influx(sys_name, desc, sev, timestamp) csv_file.close() - return 0 + if client is not None: + client.close() + return 1 if reader_errors else 0 if __name__ == '__main__': From 75f8271576eed7ac3bdf2f120a4c32674226ec18 Mon Sep 17 00:00:00 2001 From: ManushPatell Date: Sun, 14 Jun 2026 10:56:41 -0400 Subject: [PATCH 34/34] remove message --- scripts/daq/logger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/daq/logger.py b/scripts/daq/logger.py index b59160dbc..a1bc31ed5 100644 --- a/scripts/daq/logger.py +++ b/scripts/daq/logger.py @@ -87,7 +87,8 @@ "Apps2Percent", # APPS sensor 2 position (%) — AppsDebug (401) "BppsPercent", # brake pedal position (%) — BppsSteerDebug (402) "LvBatteryVoltage", # LV battery voltage (V) — LvDcdc (214) - "LvBatteryTemp", # LV battery temperature (°C) — LvDcdc (214) + "BusVoltage", # DCDC bus voltage (V) — LvDcdc (214) + "BusCurrent", # DCDC bus current (A) — LvDcdc (214) } # Boolean fault signals from FcAlerts (202) and LvStatus (211).