Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ project(sc2-driver-io)
# Set C++ standard to C++20
set(CMAKE_CXX_STANDARD 20)

# Find libcurl (required for InfluxDB HTTP writes)
find_package(CURL REQUIRED)

# Add shared sources to build
set(SOURCES
${SOURCES}
Expand All @@ -17,6 +20,8 @@ set(SOURCES
gps/gps.cpp
3rdparty/serial/serialib.cpp
Config.cpp
influx/src/InfluxWriter.cpp
influx/src/CsvParser.cpp
)

# Add the header files
Expand All @@ -30,6 +35,9 @@ set(HEADERS
gps/gps.h
3rdparty/serial/serialib.h
Config.h
influx/include/TelemetryRecord.h
influx/include/InfluxWriter.h
influx/include/CsvParser.h
)

# Add the executable target
Expand All @@ -41,4 +49,6 @@ if(UNIX)
target_link_libraries(${PROJECT_NAME} PRIVATE pthread)
endif()

target_include_directories(${PROJECT_NAME} PRIVATE ./)
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)

target_include_directories(${PROJECT_NAME} PRIVATE ./ influx/include)
Comment on lines +52 to +54
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMakeLists.txt sets cmake_minimum_required(VERSION 3.5) but links with the imported target CURL::libcurl, which is not available in older CMake FindCURL implementations. Either raise the minimum CMake version to one that guarantees CURL::libcurl, or link using the legacy CURL_LIBRARIES/CURL_INCLUDE_DIRS variables.

Suggested change
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
target_include_directories(${PROJECT_NAME} PRIVATE ./ influx/include)
target_link_libraries(${PROJECT_NAME} PRIVATE ${CURL_LIBRARIES})
target_include_directories(${PROJECT_NAME} PRIVATE ./ influx/include ${CURL_INCLUDE_DIRS})

Copilot uses AI. Check for mistakes.
126 changes: 126 additions & 0 deletions data_processor/dataUnpacker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,132 @@

#include "dataUnpacker.h"

// ─────────────────────────────────────────────────────────────────────────────
// buildTelemetryRecord
// Maps all live DataUnpacker fields into a TelemetryRecord for InfluxDB.
// ─────────────────────────────────────────────────────────────────────────────

TelemetryRecord DataUnpacker::buildTelemetryRecord() const {
TelemetryRecord r;

// ── MCC / Motor Control ──────────────────────────────────────────────────
r.accelerator_pedal = getAcceleratorPedal();
Comment on lines +12 to +16
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildTelemetryRecord() reads many DataUnpacker fields (including the cell_group_voltages vector) without taking the class mutex, while unpack() writes under mutex. This can cause data races and inconsistent snapshots. Consider locking the same mutex while building the record (you may need to make the mutex mutable or make buildTelemetryRecord non-const).

Copilot uses AI. Check for mistakes.
r.speed = getSpeed();
r.mc_status = static_cast<uint8_t>(getMcStatus());
r.crz_pwr_mode = getCrzPwrMode();
r.crz_spd_mode = getCrzSpdMode();
r.crz_pwr_setpt = getCrzPwrSetpt();
r.crz_spd_setpt = getCrzSpdSetpt();
r.eco = getEco();
r.main_telem = getMainTelem();
r.motor_power = getMotorPower();

// ── High Voltage / Shutdown ──────────────────────────────────────────────
r.driver_eStop = getDriverEStop();
r.external_eStop = getExternalEStop();
r.crash = getCrash();
r.discharge_enable = getDischargeEnable();
r.charge_enable = getChargeEnable();
r.isolation = getIsolation();
r.mcu_hv_en = getMcuHvEn();
r.mcu_stat_fdbk = getMcuStatFdbk();

// ── High Voltage / MPS ───────────────────────────────────────────────────
r.low_contactor = getLowContactor();
r.use_dcdc = getUseDcdc();

// ── Battery / Supplemental ───────────────────────────────────────────────
r.supplemental_voltage = getSupplementalVoltage();
r.est_supplemental_soc = getEstSupplementalSoc();

// ── Main IO / Sensors ────────────────────────────────────────────────────
r.park_brake = getParkingBrake();
r.mainIO_temp = getMainIOTemp();
r.motor_controller_temp = getMotorControllerTemp();
r.motor_temp = getMotorTemp();

// ── Main IO / Lights ─────────────────────────────────────────────────────
r.l_turn_led_en = getLTurnLedEn();
r.r_turn_led_en = getRTurnLedEn();
r.headlights_led_en = getHeadlights();
r.hazards = getHazards();

// ── Main IO / Firmware Heartbeats ────────────────────────────────────────
r.bms_can_heartbeat = getBmsCanHeartbeat();
r.mainIO_heartbeat = getMainIOHeartbeat();

// ── Solar / MPPT ─────────────────────────────────────────────────────────
r.mppt_current_out = getMpptCurrentOut();
r.string1_temp = getString1Temp();
r.string2_temp = getString2Temp();
r.string3_temp = getString3Temp();

// ── Battery / BMS CAN ────────────────────────────────────────────────────
r.pack_temp = getPackTemp();
r.pack_current = getPackCurrent();
r.pack_voltage = getPackVoltage();
r.soc = getSoc();
r.fan_speed = static_cast<uint8_t>(getFanSpeed());
r.bms_input_voltage = getBmsInputVoltage();

// ── Battery / BMS Faults ─────────────────────────────────────────────────
r.bps_fault = getBpsFault();
r.voltage_failsafe = getVoltageFailsafe();
r.current_failsafe = getCurrentFailsafe();
r.relay_failsafe = getRelayFailsafe();
r.cell_balancing_active = getCellBalancingActive();
r.charge_interlock_failsafe = getChargeInterlockFailsafe();
r.thermistor_b_value_table_invalid = getThermistorBValueTableInvalid();
r.input_power_supply_failsafe = getInputPowerSupplyFailsafe();

// ── Battery / Cell Group Voltages ────────────────────────────────────────
const auto& cgv = getCellGroupVoltages();
if (cgv.size() > 0) r.cell_group1_voltage = cgv[0];
if (cgv.size() > 1) r.cell_group2_voltage = cgv[1];
if (cgv.size() > 2) r.cell_group3_voltage = cgv[2];
if (cgv.size() > 3) r.cell_group4_voltage = cgv[3];
if (cgv.size() > 4) r.cell_group5_voltage = cgv[4];
if (cgv.size() > 5) r.cell_group6_voltage = cgv[5];
if (cgv.size() > 6) r.cell_group7_voltage = cgv[6];
if (cgv.size() > 7) r.cell_group8_voltage = cgv[7];
if (cgv.size() > 8) r.cell_group9_voltage = cgv[8];
if (cgv.size() > 9) r.cell_group10_voltage = cgv[9];
if (cgv.size() > 10) r.cell_group11_voltage = cgv[10];
if (cgv.size() > 11) r.cell_group12_voltage = cgv[11];
if (cgv.size() > 12) r.cell_group13_voltage = cgv[12];
if (cgv.size() > 13) r.cell_group14_voltage = cgv[13];
if (cgv.size() > 14) r.cell_group15_voltage = cgv[14];
if (cgv.size() > 15) r.cell_group16_voltage = cgv[15];
if (cgv.size() > 16) r.cell_group17_voltage = cgv[16];
if (cgv.size() > 17) r.cell_group18_voltage = cgv[17];
if (cgv.size() > 18) r.cell_group19_voltage = cgv[18];
if (cgv.size() > 19) r.cell_group20_voltage = cgv[19];
if (cgv.size() > 20) r.cell_group21_voltage = cgv[20];
if (cgv.size() > 21) r.cell_group22_voltage = cgv[21];
if (cgv.size() > 22) r.cell_group23_voltage = cgv[22];
if (cgv.size() > 23) r.cell_group24_voltage = cgv[23];
if (cgv.size() > 24) r.cell_group25_voltage = cgv[24];
if (cgv.size() > 25) r.cell_group26_voltage = cgv[25];
if (cgv.size() > 26) r.cell_group27_voltage = cgv[26];
if (cgv.size() > 27) r.cell_group28_voltage = cgv[27];
if (cgv.size() > 28) r.cell_group29_voltage = cgv[28];
if (cgv.size() > 29) r.cell_group30_voltage = cgv[29];
if (cgv.size() > 30) r.cell_group31_voltage = cgv[30];

// ── Software / Timestamps ────────────────────────────────────────────────
r.tstamp_hr = static_cast<uint8_t>(getTstampHr());
r.tstamp_mn = static_cast<uint8_t>(getTstampMn());
r.tstamp_sc = static_cast<uint8_t>(getTstampSc());
r.tstamp_ms = static_cast<uint16_t>(getTstampMs());

// ── Software / GPS ───────────────────────────────────────────────────────
r.lat = getLat();
r.lon = getLon();
r.elev = getElev();

return r;
}

double bytesToDouble(const std::vector<uint8_t>& data, int start_pos)
{
double number;
Expand Down
5 changes: 5 additions & 0 deletions data_processor/dataUnpacker.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "backend/dataFetcher.h"
#include "3rdparty/rapidjson/document.h"
#include "3rdparty/rapidjson/filereadstream.h"
#include "TelemetryRecord.h"

using namespace rapidjson;

Expand Down Expand Up @@ -118,6 +119,10 @@ class DataUnpacker
float getElev() const { return elev; }

const std::vector<float>& getCellGroupVoltages() const { return cell_group_voltages; }

// Build a TelemetryRecord from current live data for InfluxDB publishing
TelemetryRecord buildTelemetryRecord() const;

private:
bool checkRestartEnable();

Expand Down
38 changes: 38 additions & 0 deletions influx/include/CsvParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once
#include <string>
#include <vector>
#include "TelemetryRecord.h"

/**
* CsvParser
*
* Reads a CSV file whose columns match the signal names in data_format.json
* and returns a vector of TelemetryRecord objects.
*
* The first row of the CSV must be a header row containing field names.
* Missing or empty values are silently skipped (field retains its default).
*/
class CsvParser {
public:
/**
* Parse the CSV file at @p filePath.
*
* @throws std::runtime_error if the file cannot be opened.
* @returns A vector of fully-populated TelemetryRecord structs.
*/
static std::vector<TelemetryRecord> parse(const std::string& filePath);

private:
// Split a single CSV line respecting quoted fields.
static std::vector<std::string> splitLine(const std::string& line);

// Apply one (header, value) pair to a TelemetryRecord.
static void applyField(TelemetryRecord& rec,
const std::string& name,
const std::string& value);

// Helpers
static double toDouble(const std::string& s, double fallback = 0.0);
static bool toBool (const std::string& s, bool fallback = false);
static int64_t toInt64 (const std::string& s, int64_t fallback = 0);
};
62 changes: 62 additions & 0 deletions influx/include/InfluxWriter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once
#include <string>
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InfluxWriter.h declares writeBatch(const std::vectorstd::string&) but doesn’t include , so any translation unit including this header can fail to compile. Include in the header (don’t rely on transitive includes).

Suggested change
#include <string>
#include <string>
#include <vector>

Copilot uses AI. Check for mistakes.
#include "TelemetryRecord.h"

/**
* InfluxWriter
*
* Converts TelemetryRecord structs to InfluxDB Line Protocol strings and
* POSTs them to InfluxDB Cloud via libcurl.
*
* All numeric values are written as floats (no integer 'i' suffix) to prevent
* schema conflicts when a field contains mixed whole/fractional values across
* different records.
*
* Connection parameters are read from environment variables:
* INFLUX_URL – e.g. https://us-east-1-1.aws.cloud2.influxdata.com
* INFLUX_TOKEN – All-access or write token
* INFLUX_ORG – Organisation ID (hex string)
* INFLUX_BUCKET – Bucket name, e.g. sc2-telemetry
*/
class InfluxWriter {
public:
/**
* Load connection parameters from environment variables.
* @throws std::runtime_error if any required variable is missing.
*/
explicit InfluxWriter();

/**
* Convert @p rec to a single InfluxDB Line Protocol line.
*
* @param rec The telemetry record to encode.
* @param timestampNs Nanosecond-precision Unix timestamp.
* Pass 0 to omit (InfluxDB uses server time).
* @returns A line-protocol string, e.g.:
* sc2_telemetry speed=42.3,soc=87.1 1700000000000000000
*/
std::string toLineProtocol(const TelemetryRecord& rec,
int64_t timestampNs = 0) const;

/**
* HTTP POST a single Line Protocol string to InfluxDB.
*
* @throws std::runtime_error on curl/HTTP error or non-2xx response.
*/
void write(const std::string& lineProtocol) const;

/**
* HTTP POST multiple Line Protocol lines (one per element) in a single
* request. Lines are joined with '\n'.
*/
void writeBatch(const std::vector<std::string>& lines) const;

private:
std::string url_;
std::string token_;
std::string org_;
std::string bucket_;

// Perform the actual HTTP POST; throws on failure.
void httpPost(const std::string& body) const;
};
Loading
Loading