From f457f6b864f11bb2d17f6a548e3afb939c0b8d43 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 29 Nov 2025 14:38:36 -0700 Subject: [PATCH 01/16] change snakecase to pascalcase for columns --- packages/db/src/entities/B3.entity.ts | 48 +++---- packages/db/src/entities/Battery.entity.ts | 72 +++++------ .../db/src/entities/BatteryFaults.entity.ts | 74 +++++------ packages/db/src/entities/Contactor.entity.ts | 104 +++++++-------- packages/db/src/entities/Driver.entity.ts | 13 +- packages/db/src/entities/KeyMotor.entity.ts | 10 +- packages/db/src/entities/Lap.entity.ts | 26 ++-- packages/db/src/entities/MBMS.entity.ts | 118 +++++++++--------- packages/db/src/entities/MPPT.entity.ts | 68 +++++----- .../db/src/entities/MotorDetails.entity.ts | 62 ++++----- .../src/entities/ProximitySensors.entity.ts | 12 +- packages/db/src/entities/Telemetry.entity.ts | 40 +++--- .../src/entities/TelemetryMetadata.entity.ts | 8 +- 13 files changed, 328 insertions(+), 327 deletions(-) diff --git a/packages/db/src/entities/B3.entity.ts b/packages/db/src/entities/B3.entity.ts index efdbe424..f890f550 100644 --- a/packages/db/src/entities/B3.entity.ts +++ b/packages/db/src/entities/B3.entity.ts @@ -19,74 +19,74 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class B3 { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - acceleration!: number; + Acceleration!: number; @Column({ type: "boolean" }) - b3_heartbeat!: boolean; + B3Heartbeat!: boolean; @Column({ type: "boolean" }) - brake_light_signal_status!: boolean; + BrakeLightSignalStatus!: boolean; @Column({ type: "boolean" }) - brake_switch_digital!: boolean; + BrakeSwitchDigital!: boolean; @Column({ type: "boolean" }) - daytime_running_light_signal_status!: boolean; + DaytimeRunningLightSignalStatus!: boolean; @Column({ type: "boolean" }) - forward_digital!: boolean; + ForwardDigital!: boolean; @Column({ type: "boolean" }) - handbrake_switch_digital!: boolean; + HandbrakeSwitchDigital!: boolean; @Column({ type: "boolean" }) - hazard_lights_input!: boolean; + HazardLightsInput!: boolean; @Column({ type: "boolean" }) - headlights_switch_input!: boolean; + HeadlightsSwitchInput!: boolean; @Column({ type: "boolean" }) - headlight_signal_status!: boolean; + HeadlightSignalStatus!: boolean; @Column({ type: "boolean" }) - horn_signal_status!: boolean; + HornSignalStatus!: boolean; @Column({ type: "boolean" }) - horn_switch_digital!: boolean; + HornSwitchDigital!: boolean; @Column({ type: "boolean" }) - lap_digital!: boolean; + LapDigital!: boolean; @Column({ type: "boolean" }) - left_signal_input!: boolean; + LeftSignalInput!: boolean; @Column({ type: "boolean" }) - left_signal_status!: boolean; + LeftSignalStatus!: boolean; @Column({ type: "boolean" }) - motor_reset_digital!: boolean; + MotorResetDigital!: boolean; @Column({ type: "boolean" }) - neutral_digital!: boolean; + NeutralDigital!: boolean; @Column({ type: "boolean" }) - race_mode_digital!: boolean; + RaceModeDigital!: boolean; @Column({ type: "float" }) - regen_braking!: number; + RegenBraking!: number; @Column({ type: "boolean" }) - reverse_digital!: boolean; + ReverseDigital!: boolean; @Column({ type: "boolean" }) - right_signal_input!: boolean; + RightSignalInput!: boolean; @Column({ type: "boolean" }) - right_signal_status!: boolean; + RightSignalStatus!: boolean; } diff --git a/packages/db/src/entities/Battery.entity.ts b/packages/db/src/entities/Battery.entity.ts index 703d524c..609b6660 100644 --- a/packages/db/src/entities/Battery.entity.ts +++ b/packages/db/src/entities/Battery.entity.ts @@ -19,110 +19,110 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class Battery { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "boolean" }) - always_on_signal_status!: boolean; + AlwaysOnSignalStatus!: boolean; @Column({ type: "float" }) - average_cell_voltage!: number; + AverageCellVoltage!: number; @Column({ type: "float" }) - average_temperature!: number; + AverageTemperature!: number; @Column({ type: "integer" }) - bmu_alive!: number; + BmuAlive!: number; @Column({ type: "boolean" }) - charge_relay_enabled!: boolean; + ChargeRelayEnabled!: boolean; @Column({ type: "boolean" }) - charger_safety_enabled!: boolean; + ChargerSafetyEnabled!: boolean; @Column({ type: "boolean" }) - discharge_relay_enabled!: boolean; + DischargeRelayEnabled!: boolean; @Column({ type: "float" }) - fan_speed!: number; + FanSpeed!: number; @Column({ type: "float" }) - fan_voltage!: number; + FanVoltage!: number; @Column({ type: "float" }) - high_cell_voltage!: number; + HighCellVoltage!: number; @Column({ type: "integer" }) - high_cell_voltage_id!: number; + HighCellVoltageId!: number; @Column({ type: "float" }) - high_temperature!: number; + HighTemperature!: number; @Column({ type: "integer" }) - high_thermistor_id!: number; + HighThermistorId!: number; @Column({ type: "float" }) - input_12v!: number; + Input12v!: number; @Column({ type: "float" }) - internal_temperature!: number; + InternalTemperature!: number; @Column({ type: "boolean" }) - is_charging_signal_status!: boolean; + IsChargingSignalStatus!: boolean; @Column({ type: "boolean" }) - is_ready_signal_status!: boolean; + IsReadySignalStatus!: boolean; @Column({ type: "float" }) - low_cell_voltage!: number; + LowCellVoltage!: number; @Column({ type: "integer" }) - low_cell_voltage_id!: number; + LowCellVoltageId!: number; @Column({ type: "float" }) - low_temperature!: number; + LowTemperature!: number; @Column({ type: "integer" }) - low_thermistor_id!: number; + LowThermistorId!: number; @Column({ type: "boolean" }) - malfunction_indicator_active!: boolean; + MalfunctionIndicatorActive!: boolean; @Column({ type: "float" }) - maximum_cell_voltage!: number; + MaximumCellVoltage!: number; @Column({ type: "float" }) - maximum_pack_voltage!: number; + MaximumPackVoltage!: number; @Column({ type: "float" }) - minimum_cell_voltage!: number; + MinimumCellVoltage!: number; @Column({ type: "float" }) - minimum_pack_voltage!: number; + MinimumPackVoltage!: number; @Column({ type: "boolean" }) - multi_purpose_input_signal_status!: boolean; + MultiPurposeInputSignalStatus!: boolean; @Column({ type: "float" }) - pack_amphours!: number; + PackAmphours!: number; @Column({ type: "float" }) - pack_current!: number; + PackCurrent!: number; @Column({ type: "float" }) - pack_depth_of_discharge!: number; + PackDepthOfDischarge!: number; @Column({ type: "float" }) - pack_state_of_charge!: number; + PackStateOfCharge!: number; @Column({ type: "float" }) - pack_voltage!: number; + PackVoltage!: number; @Column({ type: "integer" }) - populated_cells!: number; + PopulatedCells!: number; @Column({ type: "float" }) - requested_fan_speed!: number; + RequestedFanSpeed!: number; } diff --git a/packages/db/src/entities/BatteryFaults.entity.ts b/packages/db/src/entities/BatteryFaults.entity.ts index 97c5e322..c69381a5 100644 --- a/packages/db/src/entities/BatteryFaults.entity.ts +++ b/packages/db/src/entities/BatteryFaults.entity.ts @@ -19,115 +19,115 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class BatteryFaults { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; // Errors @Column({ type: "boolean" }) - error_always_on_supply_fault!: boolean; + ErrorAlwaysOnSupplyFault!: boolean; @Column({ type: "boolean" }) - error_canbus_communication_fault!: boolean; + ErrorCanbusCommunicationFault!: boolean; @Column({ type: "boolean" }) - error_charge_limit_enforcement_fault!: boolean; + ErrorChargeLimitEnforcementFault!: boolean; @Column({ type: "boolean" }) - error_charger_safety_relay_fault!: boolean; + ErrorChargerSafetyRelayFault!: boolean; @Column({ type: "boolean" }) - error_current_sensor_fault!: boolean; + ErrorCurrentSensorFault!: boolean; @Column({ type: "boolean" }) - error_discharge_limit_enforcement_fault!: boolean; + ErrorDischargeLimitEnforcementFault!: boolean; @Column({ type: "boolean" }) - error_fan_monitor_fault!: boolean; + ErrorFanMonitorFault!: boolean; @Column({ type: "boolean" }) - error_high_voltage_isolation_fault!: boolean; + ErrorHighVoltageIsolationFault!: boolean; @Column({ type: "boolean" }) - error_internal_communication_fault!: boolean; + ErrorInternalCommunicationFault!: boolean; @Column({ type: "boolean" }) - error_internal_conversion_fault!: boolean; + ErrorInternalConversionFault!: boolean; @Column({ type: "boolean" }) - error_internal_logic_fault!: boolean; + ErrorInternalLogicFault!: boolean; @Column({ type: "boolean" }) - error_internal_memory_fault!: boolean; + ErrorInternalMemoryFault!: boolean; @Column({ type: "boolean" }) - error_internal_thermistor_fault!: boolean; + ErrorInternalThermistorFault!: boolean; @Column({ type: "boolean" }) - error_low_cell_voltage_fault!: boolean; + ErrorLowCellVoltageFault!: boolean; @Column({ type: "boolean" }) - error_open_wiring_fault!: boolean; + ErrorOpenWiringFault!: boolean; @Column({ type: "boolean" }) - error_pack_voltage_sensor_fault!: boolean; + ErrorPackVoltageSensorFault!: boolean; @Column({ type: "boolean" }) - error_power_supply_12v_fault!: boolean; + ErrorPowerSupply12vFault!: boolean; @Column({ type: "boolean" }) - error_thermistor_fault!: boolean; + ErrorThermistorFault!: boolean; @Column({ type: "boolean" }) - error_voltage_redundancy_fault!: boolean; + ErrorVoltageRedundancyFault!: boolean; @Column({ type: "boolean" }) - error_weak_cell_fault!: boolean; + ErrorWeakCellFault!: boolean; @Column({ type: "boolean" }) - error_weak_pack_fault!: boolean; + ErrorWeakPackFault!: boolean; // Warnings @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_alternate_current_limit!: boolean; + WarningCclReducedDueToAlternateCurrentLimit!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_charger_latch!: boolean; + WarningCclReducedDueToChargerLatch!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_cell_resistance!: boolean; + WarningCclReducedDueToHighCellResistance!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_cell_voltage!: boolean; + WarningCclReducedDueToHighCellVoltage!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_pack_voltage!: boolean; + WarningCclReducedDueToHighPackVoltage!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_soc!: boolean; + WarningCclReducedDueToHighSoc!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_temperature!: boolean; + WarningCclReducedDueToTemperature!: boolean; @Column({ type: "boolean" }) - warning_dcl_and_ccl_reduced_due_to_communication_failsafe!: boolean; + WarningDclAndCclReducedDueToCommunicationFailsafe!: boolean; @Column({ type: "boolean" }) - warning_dcl_and_ccl_reduced_due_to_voltage_failsafe!: boolean; + WarningDclAndCclReducedDueToVoltageFailsafe!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_high_cell_resistance!: boolean; + WarningDclReducedDueToHighCellResistance!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_low_cell_voltage!: boolean; + WarningDclReducedDueToLowCellVoltage!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_low_pack_voltage!: boolean; + WarningDclReducedDueToLowPackVoltage!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_low_soc!: boolean; + WarningDclReducedDueToLowSoc!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_temperature!: boolean; + WarningDclReducedDueToTemperature!: boolean; } diff --git a/packages/db/src/entities/Contactor.entity.ts b/packages/db/src/entities/Contactor.entity.ts index a68797f7..3cf0d940 100644 --- a/packages/db/src/entities/Contactor.entity.ts +++ b/packages/db/src/entities/Contactor.entity.ts @@ -19,163 +19,163 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class Contactor { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; // Array Contactor @Column({ type: "boolean" }) - array_bps_error!: boolean; + ArrayBpsError!: boolean; @Column({ type: "float" }) - array_charge_current!: number; + ArrayChargeCurrent!: number; @Column({ type: "boolean" }) - array_contactor_closed!: boolean; + ArrayContactorClosed!: boolean; @Column({ type: "boolean" }) - array_contactor_closing!: boolean; + ArrayContactorClosing!: boolean; @Column({ type: "boolean" }) - array_contactor_error!: boolean; + ArrayContactorError!: boolean; @Column({ type: "boolean" }) - array_heartbeat!: boolean; + ArrayHeartbeat!: boolean; @Column({ type: "float" }) - array_line_current!: number; + ArrayLineCurrent!: number; @Column({ type: "boolean" }) - array_precharger_closed!: boolean; + ArrayPrechargerClosed!: boolean; @Column({ type: "boolean" }) - array_precharger_closing!: boolean; + ArrayPrechargerClosing!: boolean; @Column({ type: "boolean" }) - array_precharger_error!: boolean; + ArrayPrechargerError!: boolean; // Charge Contactor @Column({ type: "boolean" }) - charge_bps_error!: boolean; + ChargeBpsError!: boolean; @Column({ type: "float" }) - charge_charge_current!: number; + ChargeChargeCurrent!: number; @Column({ type: "boolean" }) - charge_contactor_closed!: boolean; + ChargeContactorClosed!: boolean; @Column({ type: "boolean" }) - charge_contactor_closing!: boolean; + ChargeContactorClosing!: boolean; @Column({ type: "boolean" }) - charge_contactor_error!: boolean; + ChargeContactorError!: boolean; @Column({ type: "boolean" }) - charge_heartbeat!: boolean; + ChargeHeartbeat!: boolean; @Column({ type: "float" }) - charge_line_current!: number; + ChargeLineCurrent!: number; @Column({ type: "boolean" }) - charge_precharger_closed!: boolean; + ChargePrechargerClosed!: boolean; @Column({ type: "boolean" }) - charge_precharger_closing!: boolean; + ChargePrechargerClosing!: boolean; @Column({ type: "boolean" }) - charge_precharger_error!: boolean; + ChargePrechargerError!: boolean; // Common Contactor @Column({ type: "float" }) - common_charge_current!: number; + CommonChargeCurrent!: number; @Column({ type: "boolean" }) - common_contactor_closed!: boolean; + CommonContactorClosed!: boolean; @Column({ type: "boolean" }) - common_contactor_closing!: boolean; + CommonContactorClosing!: boolean; @Column({ type: "boolean" }) - common_contactor_error!: boolean; + CommonContactorError!: boolean; @Column({ type: "boolean" }) - common_contactor_opening_error!: boolean; + CommonContactorOpeningError!: boolean; @Column({ type: "boolean" }) - common_heartbeat!: boolean; + CommonHeartbeat!: boolean; @Column({ type: "float" }) - common_line_current!: number; + CommonLineCurrent!: number; @Column({ type: "boolean" }) - common_precharger_closed!: boolean; + CommonPrechargerClosed!: boolean; @Column({ type: "boolean" }) - common_precharger_closing!: boolean; + CommonPrechargerClosing!: boolean; @Column({ type: "boolean" }) - common_precharger_error!: boolean; + CommonPrechargerError!: boolean; // LV Contactor @Column({ type: "boolean" }) - lv_bps_error!: boolean; + LvBpsError!: boolean; @Column({ type: "float" }) - lv_charge_current!: number; + LvChargeCurrent!: number; @Column({ type: "boolean" }) - lv_contactor_closed!: boolean; + LvContactorClosed!: boolean; @Column({ type: "boolean" }) - lv_contactor_closing!: boolean; + LvContactorClosing!: boolean; @Column({ type: "boolean" }) - lv_contactor_error!: boolean; + LvContactorError!: boolean; @Column({ type: "boolean" }) - lv_heartbeat!: boolean; + LvHeartbeat!: boolean; @Column({ type: "float" }) - lv_line_current!: number; + LvLineCurrent!: number; @Column({ type: "boolean" }) - lv_precharger_closed!: boolean; + LvPrechargerClosed!: boolean; @Column({ type: "boolean" }) - lv_precharger_closing!: boolean; + LvPrechargerClosing!: boolean; @Column({ type: "boolean" }) - lv_precharger_error!: boolean; + LvPrechargerError!: boolean; // Motor Contactor @Column({ type: "boolean" }) - motor_bps_error!: boolean; + MotorBpsError!: boolean; @Column({ type: "float" }) - motor_charge_current!: number; + MotorChargeCurrent!: number; @Column({ type: "boolean" }) - motor_contactor_closed!: boolean; + MotorContactorClosed!: boolean; @Column({ type: "boolean" }) - motor_contactor_closing!: boolean; + MotorContactorClosing!: boolean; @Column({ type: "boolean" }) - motor_contactor_error!: boolean; + MotorContactorError!: boolean; @Column({ type: "boolean" }) - motor_heartbeat!: boolean; + MotorHeartbeat!: boolean; @Column({ type: "float" }) - motor_line_current!: number; + MotorLineCurrent!: number; @Column({ type: "boolean" }) - motor_precharger_closed!: boolean; + MotorPrechargerClosed!: boolean; @Column({ type: "boolean" }) - motor_precharger_closing!: boolean; + MotorPrechargerClosing!: boolean; @Column({ type: "boolean" }) - motor_precharger_error!: boolean; + MotorPrechargerError!: boolean; } diff --git a/packages/db/src/entities/Driver.entity.ts b/packages/db/src/entities/Driver.entity.ts index 5c24197e..f7609b4a 100644 --- a/packages/db/src/entities/Driver.entity.ts +++ b/packages/db/src/entities/Driver.entity.ts @@ -14,14 +14,15 @@ import { @Entity("driver") export class Driver { @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "text" }) - name!: string; + Name!: string; - @CreateDateColumn() - created_at!: Date; + // might not need these two but good practice + // @CreateDateColumn() + // CreatedAt!: Date; - @UpdateDateColumn() - updated_at!: Date; + // @UpdateDateColumn() + // UpdatedAt!: Date; } diff --git a/packages/db/src/entities/KeyMotor.entity.ts b/packages/db/src/entities/KeyMotor.entity.ts index cf6df4db..3bb06444 100644 --- a/packages/db/src/entities/KeyMotor.entity.ts +++ b/packages/db/src/entities/KeyMotor.entity.ts @@ -19,17 +19,17 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class KeyMotor { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - bus_current_out!: number; + BusCurrentOut!: number; @Column({ type: "float" }) - key_motor_velocity!: number; + KeyMotorVelocity!: number; @Column({ type: "float" }) - motor_current!: number; + MotorCurrent!: number; } diff --git a/packages/db/src/entities/Lap.entity.ts b/packages/db/src/entities/Lap.entity.ts index dc066d88..8bc68eff 100644 --- a/packages/db/src/entities/Lap.entity.ts +++ b/packages/db/src/entities/Lap.entity.ts @@ -18,41 +18,41 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; @Index(["rfid", "timestamp"]) export class Lap { @PrimaryGeneratedColumn("uuid") - id!: string; + Id!: string; @TimeColumn() - timestamp!: Date; + Timestamp!: Date; @Column({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - lap_time!: number; + LapTime!: number; @Column({ type: "float" }) - total_power_in!: number; + TotalPowerIn!: number; @Column({ type: "float" }) - total_power_out!: number; + TotalPowerOut!: number; @Column({ type: "float" }) - net_power_out!: number; + NetPowerOut!: number; @Column({ type: "float" }) - distance!: number; + Distance!: number; @Column({ type: "float" }) - energy_consumed!: number; + EnergyConsumed!: number; @Column({ type: "float" }) - amp_hours!: number; + AmpHours!: number; @Column({ type: "float" }) - average_pack_current!: number; + AveragePackCurrent!: number; @Column({ type: "float" }) - battery_seconds_remaining!: number; + BatterySecondsRemaining!: number; @Column({ type: "float" }) - average_speed!: number; + AverageSpeed!: number; } diff --git a/packages/db/src/entities/MBMS.entity.ts b/packages/db/src/entities/MBMS.entity.ts index cf11158d..5873ca25 100644 --- a/packages/db/src/entities/MBMS.entity.ts +++ b/packages/db/src/entities/MBMS.entity.ts @@ -19,179 +19,179 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class MBMS { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "boolean" }) - abatt_disable!: boolean; + AbattDisable!: boolean; @Column({ type: "boolean" }) - array_contactor_command!: boolean; + ArrayContactorCommand!: boolean; @Column({ type: "boolean" }) - array_heartbeat_dead_trip!: boolean; + ArrayHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - array_high_current_trip!: boolean; + ArrayHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - array_high_current_warning!: boolean; + ArrayHighCurrentWarning!: boolean; @Column({ type: "float" }) - auxiliary_battery_voltage!: number; + AuxiliaryBatteryVoltage!: number; @Column({ type: "boolean" }) - can_oc_12v_warning!: boolean; + CanOc12vWarning!: boolean; @Column({ type: "boolean" }) - charge_contactor_command!: boolean; + ChargeContactorCommand!: boolean; @Column({ type: "boolean" }) - charge_enable!: boolean; + ChargeEnable!: boolean; @Column({ type: "boolean" }) - charge_heartbeat_dead_trip!: boolean; + ChargeHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - charge_high_current_trip!: boolean; + ChargeHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - charge_high_current_warning!: boolean; + ChargeHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - charge_safety!: boolean; + ChargeSafety!: boolean; @Column({ type: "boolean" }) - charge_should_trip!: boolean; + ChargeShouldTrip!: boolean; @Column({ type: "boolean" }) - chg_fault!: boolean; + ChgFault!: boolean; @Column({ type: "boolean" }) - chg_lv_en!: boolean; + ChgLvEn!: boolean; @Column({ type: "boolean" }) - chg_on!: boolean; + ChgOn!: boolean; @Column({ type: "boolean" }) - common_contactor_command!: boolean; + CommonContactorCommand!: boolean; @Column({ type: "boolean" }) - common_heartbeat_dead_trip!: boolean; + CommonHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - common_high_current_trip!: boolean; + CommonHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - common_high_current_warning!: boolean; + CommonHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - contactor_connected_unexpectedly_trip!: boolean; + ContactorConnectedUnexpectedlyTrip!: boolean; @Column({ type: "boolean" }) - contactor_disconnected_unexpectedly_trip!: boolean; + ContactorDisconnectedUnexpectedlyTrip!: boolean; @Column({ type: "boolean" }) - dcdc_fault!: boolean; + DcdcFault!: boolean; @Column({ type: "boolean" }) - dcdc_on!: boolean; + DcdcOn!: boolean; @Column({ type: "boolean" }) - discharge_enable!: boolean; + DischargeEnable!: boolean; @Column({ type: "boolean" }) - discharge_should_trip!: boolean; + DischargeShouldTrip!: boolean; @Column({ type: "boolean" }) - en1!: boolean; + En1!: boolean; @Column({ type: "boolean" }) - esd_enabled_trip!: boolean; + EsdEnabledTrip!: boolean; @Column({ type: "boolean" }) - external_shutdown!: boolean; + ExternalShutdown!: boolean; @Column({ type: "boolean" }) - heartbeat!: boolean; + Heartbeat!: boolean; @Column({ type: "boolean" }) - high_cell_voltage_trip!: boolean; + HighCellVoltageTrip!: boolean; @Column({ type: "boolean" }) - high_cell_voltage_warning!: boolean; + HighCellVoltageWarning!: boolean; @Column({ type: "boolean" }) - high_temperature_trip!: boolean; + HighTemperatureTrip!: boolean; @Column({ type: "boolean" }) - high_temperature_warning!: boolean; + HighTemperatureWarning!: boolean; @Column({ type: "boolean" }) - key!: boolean; + Key!: boolean; @Column({ type: "boolean" }) - low_cell_voltage_trip!: boolean; + LowCellVoltageTrip!: boolean; @Column({ type: "boolean" }) - low_cell_voltage_warning!: boolean; + LowCellVoltageWarning!: boolean; @Column({ type: "boolean" }) - low_temperature_trip!: boolean; + LowTemperatureTrip!: boolean; @Column({ type: "boolean" }) - low_temperature_warning!: boolean; + LowTemperatureWarning!: boolean; @Column({ type: "boolean" }) - lv_contactor_command!: boolean; + LvContactorCommand!: boolean; @Column({ type: "boolean" }) - lv_heartbeat_dead_trip!: boolean; + LvHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - lv_high_current_trip!: boolean; + LvHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - lv_high_current_warning!: boolean; + LvHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - main_power_switch!: boolean; + MainPowerSwitch!: boolean; @Column({ type: "boolean" }) - motor_contactor_command!: boolean; + MotorContactorCommand!: boolean; @Column({ type: "boolean" }) - motor_heartbeat_dead_trip!: boolean; + MotorHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - motor_high_current_trip!: boolean; + MotorHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - motor_high_current_warning!: boolean; + MotorHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - mps_disabled_trip!: boolean; + MpsDisabledTrip!: boolean; @Column({ type: "boolean" }) - orion_can_received_recently!: boolean; + OrionCanReceivedRecently!: boolean; @Column({ type: "boolean" }) - orion_message_timeout_trip!: boolean; + OrionMessageTimeoutTrip!: boolean; @Column({ type: "boolean" }) - protection_trip!: boolean; + ProtectionTrip!: boolean; @Column({ type: "float" }) - startup_state!: number; + StartupState!: number; @Column({ type: "boolean" }) - strobe_bms_light!: boolean; + StrobeBmsLight!: boolean; @Column({ type: "float" }) - system_state!: number; + SystemState!: number; @Column({ type: "boolean" }) - three_a_oc!: boolean; + ThreeAOc!: boolean; } diff --git a/packages/db/src/entities/MPPT.entity.ts b/packages/db/src/entities/MPPT.entity.ts index 8709f390..e4ae88e3 100644 --- a/packages/db/src/entities/MPPT.entity.ts +++ b/packages/db/src/entities/MPPT.entity.ts @@ -19,107 +19,107 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class MPPT { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - mppt0_ch0_array_current!: number; + Mppt0Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt0_ch0_array_voltage!: number; + Mppt0Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt0_ch0_battery_voltage!: number; + Mppt0Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt0_ch0_unit_temperature!: number; + Mppt0Ch0UnitTemperature!: number; @Column({ type: "float" }) - mppt0_ch1_array_current!: number; + Mppt0Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt0_ch1_array_voltage!: number; + Mppt0Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt0_ch1_battery_voltage!: number; + Mppt0Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt0_ch1_unit_temperature!: number; + Mppt0Ch1UnitTemperature!: number; @Column({ type: "float" }) - mppt1_ch0_array_current!: number; + Mppt1Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt1_ch0_array_voltage!: number; + Mppt1Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt1_ch0_battery_voltage!: number; + Mppt1Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt1_ch0_unit_temperature!: number; + Mppt1Ch0UnitTemperature!: number; @Column({ type: "float" }) - mppt1_ch1_array_current!: number; + Mppt1Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt1_ch1_array_voltage!: number; + Mppt1Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt1_ch1_battery_voltage!: number; + Mppt1Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt1_ch1_unit_temperature!: number; + Mppt1Ch1UnitTemperature!: number; @Column({ type: "float" }) - mppt2_ch0_array_current!: number; + Mppt2Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt2_ch0_array_voltage!: number; + Mppt2Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt2_ch0_battery_voltage!: number; + Mppt2Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt2_ch0_unit_temperature!: number; + Mppt2Ch0UnitTemperature!: number; // MPPT 2 Channel 1 @Column({ type: "float" }) - mppt2_ch1_array_current!: number; + Mppt2Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt2_ch1_array_voltage!: number; + Mppt2Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt2_ch1_battery_voltage!: number; + Mppt2Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt2_ch1_unit_temperature!: number; + Mppt2Ch1UnitTemperature!: number; // MPPT 3 Channel 0 @Column({ type: "float" }) - mppt3_ch0_array_current!: number; + Mppt3Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt3_ch0_array_voltage!: number; + Mppt3Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt3_ch0_battery_voltage!: number; + Mppt3Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt3_ch0_unit_temperature!: number; + Mppt3Ch0UnitTemperature!: number; // MPPT 3 Channel 1 @Column({ type: "float" }) - mppt3_ch1_array_current!: number; + Mppt3Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt3_ch1_array_voltage!: number; + Mppt3Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt3_ch1_battery_voltage!: number; + Mppt3Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt3_ch1_unit_temperature!: number; + Mppt3Ch1UnitTemperature!: number; } diff --git a/packages/db/src/entities/MotorDetails.entity.ts b/packages/db/src/entities/MotorDetails.entity.ts index 668c4ad4..fdc8122b 100644 --- a/packages/db/src/entities/MotorDetails.entity.ts +++ b/packages/db/src/entities/MotorDetails.entity.ts @@ -21,95 +21,95 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class MotorDetails { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @PrimaryColumn({ type: "int" }) - motor_id!: number; + MotorId!: number; @Column({ type: "float" }) - active_motor!: number; + ActiveMotor!: number; @Column({ type: "float" }) - bemf_d!: number; + BemfD!: number; @Column({ type: "float" }) - bemf_q!: number; + BemfQ!: number; @Column({ type: "float" }) - bus_current!: number; + BusCurrent!: number; @Column({ type: "float" }) - bus_voltage!: number; + BusVoltage!: number; @Column({ type: "float" }) - dc_bus_ah!: number; + DcBusAh!: number; @Column({ type: "float" }) - dsp_board_temperature!: number; + DspBoardTemperature!: number; @Column({ type: "float" }) - error_flags!: number; + ErrorFlags!: number; @Column({ type: "float" }) - heatsink_temperature!: number; + HeatsinkTemperature!: number; @Column({ type: "float" }) - id!: number; + Id!: number; @Column({ type: "float" }) - iq!: number; + Iq!: number; @Column({ type: "float" }) - limit_flags!: number; + LimitFlags!: number; @Column({ type: "float" }) - motor_temperature!: number; + MotorTemperature!: number; @Column({ type: "float" }) - motor_velocity!: number; + MotorVelocity!: number; @Column({ type: "float" }) - odometer!: number; + Odometer!: number; @Column({ type: "float" }) - phase_current_b!: number; + PhaseCurrentB!: number; @Column({ type: "float" }) - phase_current_c!: number; + PhaseCurrentC!: number; @Column({ type: "float" }) - rx_error_count!: number; + RxErrorCount!: number; @Column({ type: "float" }) - serial_number!: number; + SerialNumber!: number; @Column({ type: "float" }) - slip_speed!: number; + SlipSpeed!: number; @Column({ type: "float" }) - supply_15v!: number; + Supply15v!: number; @Column({ type: "float" }) - supply_1v9!: number; + Supply1v9!: number; @Column({ type: "float" }) - supply_3v3!: number; + Supply3v3!: number; @Column({ type: "float" }) - tritium_id!: number; + TritiumId!: number; @Column({ type: "float" }) - tx_error_count!: number; + TxErrorCount!: number; @Column({ type: "float" }) - vd!: number; + Vd!: number; @Column({ type: "float" }) - vehicle_velocity!: number; + VehicleVelocity!: number; @Column({ type: "float" }) - vq!: number; + Vq!: number; } diff --git a/packages/db/src/entities/ProximitySensors.entity.ts b/packages/db/src/entities/ProximitySensors.entity.ts index 7f4c68fa..26b62b4e 100644 --- a/packages/db/src/entities/ProximitySensors.entity.ts +++ b/packages/db/src/entities/ProximitySensors.entity.ts @@ -19,20 +19,20 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class ProximitySensors { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - proximity_sensor_1!: number; + ProximitySensor1!: number; @Column({ type: "float" }) - proximity_sensor_2!: number; + ProximitySensor2!: number; @Column({ type: "float" }) - proximity_sensor_3!: number; + RroximitySensor3!: number; @Column({ type: "float" }) - proximity_sensor_4!: number; + ProximitySensor4!: number; } diff --git a/packages/db/src/entities/Telemetry.entity.ts b/packages/db/src/entities/Telemetry.entity.ts index d556830d..8b34a8c3 100644 --- a/packages/db/src/entities/Telemetry.entity.ts +++ b/packages/db/src/entities/Telemetry.entity.ts @@ -19,64 +19,64 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class Telemetry { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; // GPS Data @Column({ type: "float" }) - gps_additional_flags!: number; + GpsAdditionalFlags!: number; @Column({ type: "float" }) - gps_day!: number; + GpsDay!: number; @Column({ type: "float" }) - gps_fix_status_flags!: number; + GpsFixStatusFlags!: number; @Column({ type: "float" }) - gps_hour!: number; + GpsHour!: number; @Column({ type: "float" }) - gps_latitude!: number; + GpsLatitude!: number; @Column({ type: "float" }) - gps_longitude!: number; + GpsLongitude!: number; @Column({ type: "float" }) - gps_minute!: number; + GpsMinute!: number; @Column({ type: "float" }) - gps_month!: number; + GpsMonth!: number; @Column({ type: "float" }) - gps_second!: number; + GpsSecond!: number; @Column({ type: "float" }) - gps_validity_flags!: number; + GpsValidityFlags!: number; @Column({ type: "float" }) - gps_year!: number; + GpsYear!: number; // MPU (Motion Processing Unit) Data @Column({ type: "float" }) - mpu_acceleration_x!: number; + MpuAccelerationX!: number; @Column({ type: "float" }) - mpu_acceleration_y!: number; + MpuAccelerationY!: number; @Column({ type: "float" }) - mpu_acceleration_z!: number; + MpuAccelerationZ!: number; @Column({ type: "float" }) - mpu_rotation_x!: number; + MpuRotationX!: number; @Column({ type: "float" }) - mpu_rotation_y!: number; + MpuRotationY!: number; @Column({ type: "float" }) - mpu_rotation_z!: number; + MpuRotationZ!: number; @Column({ type: "float" }) - mpu_temperature!: number; + MpuTemperature!: number; } diff --git a/packages/db/src/entities/TelemetryMetadata.entity.ts b/packages/db/src/entities/TelemetryMetadata.entity.ts index 25a8ebbe..ec660332 100644 --- a/packages/db/src/entities/TelemetryMetadata.entity.ts +++ b/packages/db/src/entities/TelemetryMetadata.entity.ts @@ -21,14 +21,14 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class TelemetryMetadata { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "text", nullable: true }) - race_name?: string; + RaceName?: string; @Column({ type: "text", nullable: true }) - title?: string; + Title?: string; } From 1bf9bf71a88ed9a0ff15a6b28c29e8db933943b5 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 10 Jan 2026 11:06:01 -0700 Subject: [PATCH 02/16] make flattened table instead of many for car parts --- packages/db/src/entities/Driver.entity.ts | 2 + .../db/src/entities/TelemetryPacket.entity.ts | 1023 +++++++++++++++++ packages/db/src/index.ts | 3 +- .../src/interfaces/repositories.interface.ts | 4 + .../db/src/repositories/BaseRepository.ts | 6 +- .../repositories/TelemetryPacketRepository.ts | 13 + packages/db/src/services/DatabaseService.ts | 240 +++- .../server/src/database/DatabaseManager.ts | 56 +- 8 files changed, 1320 insertions(+), 27 deletions(-) create mode 100644 packages/db/src/entities/TelemetryPacket.entity.ts create mode 100644 packages/db/src/repositories/TelemetryPacketRepository.ts diff --git a/packages/db/src/entities/Driver.entity.ts b/packages/db/src/entities/Driver.entity.ts index f7609b4a..8c7cf28a 100644 --- a/packages/db/src/entities/Driver.entity.ts +++ b/packages/db/src/entities/Driver.entity.ts @@ -7,6 +7,8 @@ import { Index, } from "typeorm"; +// use this table!! + /** * Driver table (not a hypertable) * Stores driver information linked by RFID diff --git a/packages/db/src/entities/TelemetryPacket.entity.ts b/packages/db/src/entities/TelemetryPacket.entity.ts new file mode 100644 index 00000000..82b354bc --- /dev/null +++ b/packages/db/src/entities/TelemetryPacket.entity.ts @@ -0,0 +1,1023 @@ +import { Entity, Column, Index, PrimaryColumn } from "typeorm"; +import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; + + +// use this table !! + +/** + * Flattened Telemetry hypertable + * Combines all sensor data into a single wide table for simplified insertions and queries + * Replaces the previous normalized structure with separate tables for each car component + */ +@Entity("telemetry_packet") +@Hypertable({ + timeColumnName: "timestamp", + chunkTimeInterval: "1 month", + compression: { + compress: true, + compress_segmentby: "rfid, race_name", + compress_orderby: "timestamp DESC", + }, +}) +@Index(["rfid", "timestamp"]) +@Index(["race_name", "timestamp"]) +export class TelemetryPacket { + @TimeColumn() + @PrimaryColumn({ type: "timestamptz" }) + Timestamp!: Date; + + @PrimaryColumn({ type: "text" }) + Rfid!: string; + + // =============================================== + // Metadata Fields (from TelemetryMetadata) + // =============================================== + @Column({ type: "text", nullable: true }) + RaceName?: string; + + @Column({ type: "text", nullable: true }) + Title?: string; + + // =============================================== + // B3 Fields (Button Board 3) + // =============================================== + @Column({ type: "float", nullable: true }) + Acceleration?: number; + + @Column({ type: "boolean", nullable: true }) + B3Heartbeat?: boolean; + + @Column({ type: "boolean", nullable: true }) + BrakeLightSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + BrakeSwitchDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + DaytimeRunningLightSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + ForwardDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + HandbrakeSwitchDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + HazardLightsInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + HeadlightsSwitchInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + HeadlightSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + HornSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + HornSwitchDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + LapDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + LeftSignalInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + LeftSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorResetDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + NeutralDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + RaceModeDigital?: boolean; + + @Column({ type: "float", nullable: true }) + RegenBraking?: number; + + @Column({ type: "boolean", nullable: true }) + ReverseDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + RightSignalInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + RightSignalStatus?: boolean; + + // =============================================== + // Battery Fields + // =============================================== + @Column({ type: "boolean", nullable: true }) + AlwaysOnSignalStatus?: boolean; + + @Column({ type: "float", nullable: true }) + AverageCellVoltage?: number; + + @Column({ type: "float", nullable: true }) + AverageTemperature?: number; + + @Column({ type: "integer", nullable: true }) + BmuAlive?: number; + + @Column({ type: "boolean", nullable: true }) + ChargeRelayEnabled?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargerSafetyEnabled?: boolean; + + @Column({ type: "boolean", nullable: true }) + DischargeRelayEnabled?: boolean; + + @Column({ type: "float", nullable: true }) + FanSpeed?: number; + + @Column({ type: "float", nullable: true }) + FanVoltage?: number; + + @Column({ type: "float", nullable: true }) + HighCellVoltage?: number; + + @Column({ type: "integer", nullable: true }) + HighCellVoltageId?: number; + + @Column({ type: "float", nullable: true }) + HighTemperature?: number; + + @Column({ type: "integer", nullable: true }) + HighThermistorId?: number; + + @Column({ type: "float", nullable: true }) + Input12v?: number; + + @Column({ type: "float", nullable: true }) + InternalTemperature?: number; + + @Column({ type: "boolean", nullable: true }) + IsChargingSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + IsReadySignalStatus?: boolean; + + @Column({ type: "float", nullable: true }) + LowCellVoltage?: number; + + @Column({ type: "integer", nullable: true }) + LowCellVoltageId?: number; + + @Column({ type: "float", nullable: true }) + LowTemperature?: number; + + @Column({ type: "integer", nullable: true }) + LowThermistorId?: number; + + @Column({ type: "boolean", nullable: true }) + MalfunctionIndicatorActive?: boolean; + + @Column({ type: "float", nullable: true }) + MaximumCellVoltage?: number; + + @Column({ type: "float", nullable: true }) + MaximumPackVoltage?: number; + + @Column({ type: "float", nullable: true }) + MinimumCellVoltage?: number; + + @Column({ type: "float", nullable: true }) + MinimumPackVoltage?: number; + + @Column({ type: "boolean", nullable: true }) + MultiPurposeInputSignalStatus?: boolean; + + @Column({ type: "float", nullable: true }) + PackAmphours?: number; + + @Column({ type: "float", nullable: true }) + PackCurrent?: number; + + @Column({ type: "float", nullable: true }) + PackDepthOfDischarge?: number; + + @Column({ type: "float", nullable: true }) + PackStateOfCharge?: number; + + @Column({ type: "float", nullable: true }) + PackVoltage?: number; + + @Column({ type: "integer", nullable: true }) + PopulatedCells?: number; + + @Column({ type: "float", nullable: true }) + RequestedFanSpeed?: number; + + // =============================================== + // Battery Faults Fields (Errors) + // =============================================== + @Column({ type: "boolean", nullable: true }) + ErrorAlwaysOnSupplyFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorCanbusCommunicationFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorChargeLimitEnforcementFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorChargerSafetyRelayFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorCurrentSensorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorDischargeLimitEnforcementFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorFanMonitorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorHighVoltageIsolationFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalCommunicationFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalConversionFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalLogicFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalMemoryFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalThermistorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorLowCellVoltageFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorOpenWiringFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorPackVoltageSensorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorPowerSupply12vFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorThermistorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorVoltageRedundancyFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorWeakCellFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorWeakPackFault?: boolean; + + // =============================================== + // Battery Faults Fields (Warnings) + // =============================================== + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToAlternateCurrentLimit?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToChargerLatch?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighCellResistance?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighCellVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighPackVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighSoc?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToTemperature?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclAndCclReducedDueToCommunicationFailsafe?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclAndCclReducedDueToVoltageFailsafe?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToHighCellResistance?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToLowCellVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToLowPackVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToLowSoc?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToTemperature?: boolean; + + // =============================================== + // Contactor Fields - Array + // =============================================== + @Column({ type: "boolean", nullable: true }) + ArrayBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + ArrayChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + ArrayLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ArrayPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayPrechargerError?: boolean; + + // =============================================== + // Contactor Fields - Charge + // =============================================== + @Column({ type: "boolean", nullable: true }) + ChargeBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + ChargeChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + ChargeLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ChargePrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargePrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargePrechargerError?: boolean; + + // =============================================== + // Contactor Fields - Common + // =============================================== + @Column({ type: "float", nullable: true }) + CommonChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + CommonContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorOpeningError?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + CommonLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + CommonPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonPrechargerError?: boolean; + + // =============================================== + // Contactor Fields - LV + // =============================================== + @Column({ type: "boolean", nullable: true }) + LvBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + LvChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + LvContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + LvLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + LvPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvPrechargerError?: boolean; + + // =============================================== + // Contactor Fields - Motor + // =============================================== + @Column({ type: "boolean", nullable: true }) + MotorBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + MotorChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + MotorContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + MotorLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + MotorPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorPrechargerError?: boolean; + + // =============================================== + // KeyMotor Fields + // =============================================== + @Column({ type: "float", nullable: true }) + BusCurrentOut?: number; + + @Column({ type: "float", nullable: true }) + KeyMotorVelocity?: number; + + @Column({ type: "float", nullable: true }) + MotorCurrent?: number; + + // =============================================== + // MBMS Fields (Master Battery Management System) + // =============================================== + @Column({ type: "boolean", nullable: true }) + AbattDisable?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHighCurrentWarning?: boolean; + + @Column({ type: "float", nullable: true }) + AuxiliaryBatteryVoltage?: number; + + @Column({ type: "boolean", nullable: true }) + CanOc12vWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeEnable?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeSafety?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeShouldTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChgFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChgLvEn?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChgOn?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + ContactorConnectedUnexpectedlyTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ContactorDisconnectedUnexpectedlyTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + DcdcFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + DcdcOn?: boolean; + + @Column({ type: "boolean", nullable: true }) + DischargeEnable?: boolean; + + @Column({ type: "boolean", nullable: true }) + DischargeShouldTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + En1?: boolean; + + @Column({ type: "boolean", nullable: true }) + EsdEnabledTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ExternalShutdown?: boolean; + + @Column({ type: "boolean", nullable: true }) + Heartbeat?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighCellVoltageTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighCellVoltageWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighTemperatureTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighTemperatureWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + Key?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowCellVoltageTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowCellVoltageWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowTemperatureTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowTemperatureWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + MainPowerSwitch?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + MpsDisabledTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + OrionCanReceivedRecently?: boolean; + + @Column({ type: "boolean", nullable: true }) + OrionMessageTimeoutTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ProtectionTrip?: boolean; + + @Column({ type: "float", nullable: true }) + StartupState?: number; + + @Column({ type: "boolean", nullable: true }) + StrobeBmsLight?: boolean; + + @Column({ type: "float", nullable: true }) + SystemState?: number; + + @Column({ type: "boolean", nullable: true }) + ThreeAOc?: boolean; + + // =============================================== + // MotorDetails Fields (Motor 0) + // Note: Motor 1 fields would need to be added separately with different names + // e.g., Motor0ActiveMotor, Motor1ActiveMotor + // =============================================== + @Column({ type: "float", nullable: true }) + Motor0ActiveMotor?: number; + + @Column({ type: "float", nullable: true }) + Motor0BemfD?: number; + + @Column({ type: "float", nullable: true }) + Motor0BemfQ?: number; + + @Column({ type: "float", nullable: true }) + Motor0BusCurrent?: number; + + @Column({ type: "float", nullable: true }) + Motor0BusVoltage?: number; + + @Column({ type: "float", nullable: true }) + Motor0DcBusAh?: number; + + @Column({ type: "float", nullable: true }) + Motor0DspBoardTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor0ErrorFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor0HeatsinkTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor0Id?: number; + + @Column({ type: "float", nullable: true }) + Motor0Iq?: number; + + @Column({ type: "float", nullable: true }) + Motor0LimitFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor0MotorTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor0MotorVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor0Odometer?: number; + + @Column({ type: "float", nullable: true }) + Motor0PhaseCurrentB?: number; + + @Column({ type: "float", nullable: true }) + Motor0PhaseCurrentC?: number; + + @Column({ type: "float", nullable: true }) + Motor0RxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor0SerialNumber?: number; + + @Column({ type: "float", nullable: true }) + Motor0SlipSpeed?: number; + + @Column({ type: "float", nullable: true }) + Motor0Supply15v?: number; + + @Column({ type: "float", nullable: true }) + Motor0Supply1v9?: number; + + @Column({ type: "float", nullable: true }) + Motor0Supply3v3?: number; + + @Column({ type: "float", nullable: true }) + Motor0TritiumId?: number; + + @Column({ type: "float", nullable: true }) + Motor0TxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor0Vd?: number; + + @Column({ type: "float", nullable: true }) + Motor0VehicleVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor0Vq?: number; + + // =============================================== + // MotorDetails Fields (Motor 1) + // =============================================== + @Column({ type: "float", nullable: true }) + Motor1ActiveMotor?: number; + + @Column({ type: "float", nullable: true }) + Motor1BemfD?: number; + + @Column({ type: "float", nullable: true }) + Motor1BemfQ?: number; + + @Column({ type: "float", nullable: true }) + Motor1BusCurrent?: number; + + @Column({ type: "float", nullable: true }) + Motor1BusVoltage?: number; + + @Column({ type: "float", nullable: true }) + Motor1DcBusAh?: number; + + @Column({ type: "float", nullable: true }) + Motor1DspBoardTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor1ErrorFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor1HeatsinkTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor1Id?: number; + + @Column({ type: "float", nullable: true }) + Motor1Iq?: number; + + @Column({ type: "float", nullable: true }) + Motor1LimitFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor1MotorTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor1MotorVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor1Odometer?: number; + + @Column({ type: "float", nullable: true }) + Motor1PhaseCurrentB?: number; + + @Column({ type: "float", nullable: true }) + Motor1PhaseCurrentC?: number; + + @Column({ type: "float", nullable: true }) + Motor1RxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor1SerialNumber?: number; + + @Column({ type: "float", nullable: true }) + Motor1SlipSpeed?: number; + + @Column({ type: "float", nullable: true }) + Motor1Supply15v?: number; + + @Column({ type: "float", nullable: true }) + Motor1Supply1v9?: number; + + @Column({ type: "float", nullable: true }) + Motor1Supply3v3?: number; + + @Column({ type: "float", nullable: true }) + Motor1TritiumId?: number; + + @Column({ type: "float", nullable: true }) + Motor1TxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor1Vd?: number; + + @Column({ type: "float", nullable: true }) + Motor1VehicleVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor1Vq?: number; + + // =============================================== + // MPPT Fields (Maximum Power Point Tracker) + // =============================================== + @Column({ type: "float", nullable: true }) + Mppt0Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1UnitTemperature?: number; + + // =============================================== + // ProximitySensors Fields + // =============================================== + @Column({ type: "float", nullable: true }) + ProximitySensor1?: number; + + @Column({ type: "float", nullable: true }) + ProximitySensor2?: number; + + @Column({ type: "float", nullable: true }) + RroximitySensor3?: number; + + @Column({ type: "float", nullable: true }) + ProximitySensor4?: number; + + // =============================================== + // Telemetry Fields (GPS and MPU) + // =============================================== + @Column({ type: "float", nullable: true }) + GpsAdditionalFlags?: number; + + @Column({ type: "float", nullable: true }) + GpsDay?: number; + + @Column({ type: "float", nullable: true }) + GpsFixStatusFlags?: number; + + @Column({ type: "float", nullable: true }) + GpsHour?: number; + + @Column({ type: "float", nullable: true }) + GpsLatitude?: number; + + @Column({ type: "float", nullable: true }) + GpsLongitude?: number; + + @Column({ type: "float", nullable: true }) + GpsMinute?: number; + + @Column({ type: "float", nullable: true }) + GpsMonth?: number; + + @Column({ type: "float", nullable: true }) + GpsSecond?: number; + + @Column({ type: "float", nullable: true }) + GpsValidityFlags?: number; + + @Column({ type: "float", nullable: true }) + GpsYear?: number; + + @Column({ type: "float", nullable: true }) + MpuAccelerationX?: number; + + @Column({ type: "float", nullable: true }) + MpuAccelerationY?: number; + + @Column({ type: "float", nullable: true }) + MpuAccelerationZ?: number; + + @Column({ type: "float", nullable: true }) + MpuRotationX?: number; + + @Column({ type: "float", nullable: true }) + MpuRotationY?: number; + + @Column({ type: "float", nullable: true }) + MpuRotationZ?: number; + + @Column({ type: "float", nullable: true }) + MpuTemperature?: number; +} diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index bf932795..dffd6deb 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,5 +1,6 @@ export { AppDataSource } from "./data-source"; - +export { BaseRepository } from "./repositories/BaseRepository"; +export { TelemetryService } from "./services/TelemetryService"; // entities // repositories diff --git a/packages/db/src/interfaces/repositories.interface.ts b/packages/db/src/interfaces/repositories.interface.ts index 7e13f894..f22d28b8 100644 --- a/packages/db/src/interfaces/repositories.interface.ts +++ b/packages/db/src/interfaces/repositories.interface.ts @@ -12,6 +12,7 @@ import { MPPT } from "../entities/MPPT.entity"; import { ProximitySensors } from "../entities/ProximitySensors.entity"; import { Telemetry } from "../entities/Telemetry.entity"; import { TelemetryMetadata } from "../entities/TelemetryMetadata.entity"; +import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export interface IBaseRepository { create(entity: DeepPartial): Promise; @@ -52,3 +53,6 @@ export interface ITelemetryRepository extends IBaseRepository {} export interface ITelemetryMetadataRepository extends IBaseRepository {} + +export interface ITelemetryPacketRepository + extends IBaseRepository {} diff --git a/packages/db/src/repositories/BaseRepository.ts b/packages/db/src/repositories/BaseRepository.ts index 3b424170..c82fbca5 100644 --- a/packages/db/src/repositories/BaseRepository.ts +++ b/packages/db/src/repositories/BaseRepository.ts @@ -6,10 +6,10 @@ import { } from "typeorm"; import { ObjectLiteral, FindOneOptions } from "typeorm"; -// every entity has either timestamp (car parts) or rfid (driver and car parts) as its primary key +// every entity has either timestamp (car parts) and/or rfid (driver and car parts) as its primary key interface BaseEntity { - timestamp?: Date; - rfid?: string; + Timestamp?: Date; + Rfid?: string; } export abstract class BaseRepository { diff --git a/packages/db/src/repositories/TelemetryPacketRepository.ts b/packages/db/src/repositories/TelemetryPacketRepository.ts new file mode 100644 index 00000000..b07fef54 --- /dev/null +++ b/packages/db/src/repositories/TelemetryPacketRepository.ts @@ -0,0 +1,13 @@ +import { Repository } from "typeorm"; +import { BaseRepository } from "./BaseRepository"; +import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; +import { ITelemetryPacketRepository } from "../interfaces/repositories.interface"; + +export class TelemetryRepository + extends BaseRepository + implements ITelemetryPacketRepository +{ + constructor(repository: Repository) { + super(repository); + } +} diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts index 5cdd0219..f3c6dc33 100644 --- a/packages/db/src/services/DatabaseService.ts +++ b/packages/db/src/services/DatabaseService.ts @@ -1,5 +1,7 @@ -import { DataSource } from "typeorm"; +import { DataSource, Repository } from "typeorm"; import { AppDataSource } from "../data-source"; +import { ITelemetryData } from "@shared/helios-types"; +import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export class DatabaseService { private dataSource: DataSource; @@ -37,3 +39,239 @@ export class DatabaseService { return this.dataSource; } } + +export class TelemetryService { + private telemetryPacketRepo: Repository; + + constructor() { + this.telemetryPacketRepo = AppDataSource.getRepository(TelemetryPacket); + } + + /** + * Insert complete telemetry packet into flattened table + * All sensor data is stored in a single wide table for simplified insertions + */ + async insertTelemetryPacket(packet: ITelemetryData): Promise { + const timestamp = new Date(packet.TimeStamp * 1000); + const Rfid = packet.Pi.Rfid; + + // Flatten all nested data structures into a single object + // Motor details are prefixed with Motor0_ and Motor1_ + const flattenedData: Partial = { + // Metadata + RaceName: packet.Title, + Rfid, + // Primary keys + Timestamp: timestamp, + Title: packet.Title, + + // B3 data + ...packet.B3, + + // Battery data + ...packet.Battery, + + // Battery Faults (flatten Errors and Warnings) + ...packet.BatteryFaults.Errors, + ...packet.BatteryFaults.Warnings, + + // Contactor data + ...packet.Contactor, + + // KeyMotor data + ...packet.KeyMotor, + + // MBMS data + ...packet.MBMS, + + // MPPT data + ...packet.MPPT, + + // Motor 0 data (prefix all fields with Motor0) + Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, + Motor0BemfD: packet.MotorDetails0.BEMF_D, + Motor0BemfQ: packet.MotorDetails0.BEMF_Q, + Motor0BusCurrent: packet.MotorDetails0.BusCurrent, + Motor0BusVoltage: packet.MotorDetails0.BusVoltage, + Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, + Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, + Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, + Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, + Motor0Id: packet.MotorDetails0.Id, + Motor0Iq: packet.MotorDetails0.Iq, + Motor0LimitFlags: packet.MotorDetails0.LimitFlags, + Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, + Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, + Motor0Odometer: packet.MotorDetails0.Odometer, + Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, + Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, + Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, + Motor0SerialNumber: packet.MotorDetails0.SerialNumber, + Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, + Motor0Supply15v: packet.MotorDetails0.Supply15V, + Motor0Supply1v9: packet.MotorDetails0.Supply1V9, + Motor0Supply3v3: packet.MotorDetails0.Supply3V3, + Motor0TritiumId: packet.MotorDetails0.TritiumId, + Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, + Motor0Vd: packet.MotorDetails0.Vd, + Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, + Motor0Vq: packet.MotorDetails0.Vq, + + // Motor 1 data (prefix all fields with Motor1) + Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, + Motor1BemfD: packet.MotorDetails1.BEMF_D, + Motor1BemfQ: packet.MotorDetails1.BEMF_Q, + Motor1BusCurrent: packet.MotorDetails1.BusCurrent, + Motor1BusVoltage: packet.MotorDetails1.BusVoltage, + Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, + Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, + Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, + Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, + Motor1Id: packet.MotorDetails1.Id, + Motor1Iq: packet.MotorDetails1.Iq, + Motor1LimitFlags: packet.MotorDetails1.LimitFlags, + Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, + Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, + Motor1Odometer: packet.MotorDetails1.Odometer, + Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, + Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, + Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, + Motor1SerialNumber: packet.MotorDetails1.SerialNumber, + Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, + Motor1Supply15v: packet.MotorDetails1.Supply15V, + Motor1Supply1v9: packet.MotorDetails1.Supply1V9, + Motor1Supply3v3: packet.MotorDetails1.Supply3V3, + Motor1TritiumId: packet.MotorDetails1.TritiumId, + Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, + Motor1Vd: packet.MotorDetails1.Vd, + Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, + Motor1Vq: packet.MotorDetails1.Vq, + + // ProximitySensors data + ...packet.ProximitySensors, + + // Telemetry data (GPS + MPU) + ...packet.Telemetry, + }; + + // Single insert into the flattened table + await this.telemetryPacketRepo.save(flattenedData); + } + + /** + * Query telemetry data between dates from the flattened table + */ + async getTelemetryBetweenDates( + startTime: Date, + endTime: Date, + rfid?: string, + ): Promise { + const queryBuilder = this.telemetryPacketRepo + .createQueryBuilder("t") + .where("t.Timestamp BETWEEN :start AND :end", { + end: endTime, + start: startTime, + }); + + if (rfid) { + queryBuilder.andWhere("t.Rfid = :rfid", { rfid }); + } + + return queryBuilder.getMany(); + } + + /** + * Get latest telemetry packet for a specific RFID + */ + async getLatestTelemetry(rfid: string): Promise { + return this.telemetryPacketRepo.findOne({ + order: { Timestamp: "DESC" }, + where: { Rfid: rfid }, + }); + } + + /** + * Batch insert multiple telemetry packets + */ + async insertTelemetryPacketBatch(packets: ITelemetryData[]): Promise { + const flattenedPackets = packets.map((packet) => { + const timestamp = new Date(packet.TimeStamp * 1000); + const Rfid = packet.Pi.Rfid; + + return { + RaceName: packet.Title, + Rfid, + Timestamp: timestamp, + Title: packet.Title, + ...packet.B3, + ...packet.Battery, + ...packet.BatteryFaults.Errors, + ...packet.BatteryFaults.Warnings, + ...packet.Contactor, + ...packet.KeyMotor, + ...packet.MBMS, + ...packet.MPPT, + Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, + Motor0BemfD: packet.MotorDetails0.BEMF_D, + Motor0BemfQ: packet.MotorDetails0.BEMF_Q, + Motor0BusCurrent: packet.MotorDetails0.BusCurrent, + Motor0BusVoltage: packet.MotorDetails0.BusVoltage, + Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, + Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, + Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, + Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, + Motor0Id: packet.MotorDetails0.Id, + Motor0Iq: packet.MotorDetails0.Iq, + Motor0LimitFlags: packet.MotorDetails0.LimitFlags, + Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, + Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, + Motor0Odometer: packet.MotorDetails0.Odometer, + Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, + Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, + Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, + Motor0SerialNumber: packet.MotorDetails0.SerialNumber, + Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, + Motor0Supply15v: packet.MotorDetails0.Supply15V, + Motor0Supply1v9: packet.MotorDetails0.Supply1V9, + Motor0Supply3v3: packet.MotorDetails0.Supply3V3, + Motor0TritiumId: packet.MotorDetails0.TritiumId, + Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, + Motor0Vd: packet.MotorDetails0.Vd, + Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, + Motor0Vq: packet.MotorDetails0.Vq, + Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, + Motor1BemfD: packet.MotorDetails1.BEMF_D, + Motor1BemfQ: packet.MotorDetails1.BEMF_Q, + Motor1BusCurrent: packet.MotorDetails1.BusCurrent, + Motor1BusVoltage: packet.MotorDetails1.BusVoltage, + Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, + Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, + Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, + Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, + Motor1Id: packet.MotorDetails1.Id, + Motor1Iq: packet.MotorDetails1.Iq, + Motor1LimitFlags: packet.MotorDetails1.LimitFlags, + Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, + Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, + Motor1Odometer: packet.MotorDetails1.Odometer, + Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, + Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, + Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, + Motor1SerialNumber: packet.MotorDetails1.SerialNumber, + Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, + Motor1Supply15v: packet.MotorDetails1.Supply15V, + Motor1Supply1v9: packet.MotorDetails1.Supply1V9, + Motor1Supply3v3: packet.MotorDetails1.Supply3V3, + Motor1TritiumId: packet.MotorDetails1.TritiumId, + Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, + Motor1Vd: packet.MotorDetails1.Vd, + Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, + Motor1Vq: packet.MotorDetails1.Vq, + ...packet.ProximitySensors, + ...packet.Telemetry, + } as Partial; + }); + + await this.telemetryPacketRepo.save(flattenedPackets); + } +} diff --git a/packages/server/src/database/DatabaseManager.ts b/packages/server/src/database/DatabaseManager.ts index d87b2171..361cc5a4 100644 --- a/packages/server/src/database/DatabaseManager.ts +++ b/packages/server/src/database/DatabaseManager.ts @@ -1,42 +1,54 @@ -import { DatabaseService } from "db"; +// +import { createLightweightApplicationLogger } from "@/utils/logger"; + +import { AppDataSource } from "@db/data-source"; +import { TelemetryService } from "@db/services/TelemetryService"; +import { ITelemetryData } from "@shared/helios-types"; + +const logger = createLightweightApplicationLogger("DatabaseManager.ts"); export class DatabaseManager { private static instance: DatabaseManager; - private databaseService: DatabaseService; + private telemetryService?: TelemetryService; + private isConnected = false; - private constructor() { - this.databaseService = new DatabaseService(); - } + private constructor() {} - public static getInstance(): DatabaseManager { + static getInstance(): DatabaseManager { if (!DatabaseManager.instance) { DatabaseManager.instance = new DatabaseManager(); } return DatabaseManager.instance; } - public async initialize(): Promise { - try { - await this.databaseService.initialize(); - console.log("DatabaseManager initialized successfully"); - } catch (error) { - console.error("Failed to initialize DatabaseManager:", error); - throw error; + async initialize() { + if (!this.isConnected) { + await AppDataSource.initialize(); + this.telemetryService = new TelemetryService(); + this.isConnected = true; + logger.info("Database initialized successfully"); } } - public async close(): Promise { - await this.databaseService.close(); - console.log("DatabaseManager closed"); + async insertTelemetryData(packet: ITelemetryData) { + if (!this.telemetryService) { + throw new Error("DatabaseManager not initialized"); + } + return this.telemetryService.insertTelemetryPacket(packet); } - // Helper method to check connection status - public get isConnected(): boolean { - return this.databaseService.isConnected; + async getTelemetryBetweenDates(start: Date, end: Date, rfid?: string) { + if (!this.telemetryService) { + throw new Error("DatabaseManager not initialized"); + } + return this.telemetryService.getTelemetryBetweenDates(start, end, rfid); } - // Get raw database service if needed - public get rawDatabaseService(): DatabaseService { - return this.databaseService; + async close() { + if (this.isConnected) { + await AppDataSource.destroy(); + this.isConnected = false; + logger.info("Database connection closed"); + } } } From b3380be1e8beba8049f0d57e9ab5e7f3fc6568a2 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 10 Jan 2026 11:13:04 -0700 Subject: [PATCH 03/16] add new table to data-source --- packages/db/src/data-source.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts index ef50035f..e746873e 100644 --- a/packages/db/src/data-source.ts +++ b/packages/db/src/data-source.ts @@ -16,6 +16,7 @@ import { Telemetry } from "./entities/Telemetry.entity"; import { ProximitySensors } from "./entities/ProximitySensors.entity"; import { Driver } from "./entities/Driver.entity"; import { Lap } from "./entities/Lap.entity"; +import { TelemetryPacket } from "./entities/TelemetryPacket.entity"; dotenv.config({ path: ".db.env" }); @@ -37,6 +38,7 @@ export const AppDataSource = new DataSource({ ProximitySensors, Driver, Lap, + TelemetryPacket, ], // database host (if it's localhost or not) host: process.env.DB_HOST || "localhost", From c04a648571f5c8a3428d2d114181babaa2179b51 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 10 Jan 2026 12:58:07 -0700 Subject: [PATCH 04/16] doesnt work --- packages/db/src/data-source.ts | 11 ++ packages/db/src/index.ts | 2 - packages/db/src/services/DatabaseService.ts | 124 ++++++++++++++++++ .../BackendController/BackendController.ts | 10 +- .../server/src/database/DatabaseManager.ts | 23 +--- 5 files changed, 146 insertions(+), 24 deletions(-) diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts index e746873e..d58f8c29 100644 --- a/packages/db/src/data-source.ts +++ b/packages/db/src/data-source.ts @@ -54,3 +54,14 @@ export const AppDataSource = new DataSource({ type: "postgres", username: process.env.POSTGRES_USERNAME || "postgres", }); + +// export const AppDataSource = new DataSource({ +// entities: [TelemetryPacket, Driver, Lap], +// logging: process.env.NODE_ENV === "development", +// migrations: [__dirname + "/migrations/*.{js,ts}"], +// synchronize: process.env.NODE_ENV === "development", +// type: "postgres", +// url: +// process.env.DATABASE_URL ?? +// "postgres://tsdbadmin@y0b9h9zgeh.wy9edrssy5.tsdb.cloud.timescale.com:34124/tsdb?sslmode=require", +// }); diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index dffd6deb..421f9d6a 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,10 +1,8 @@ export { AppDataSource } from "./data-source"; export { BaseRepository } from "./repositories/BaseRepository"; -export { TelemetryService } from "./services/TelemetryService"; // entities // repositories -export { BaseRepository } from "./repositories/BaseRepository"; // interfaces export * from "./interfaces/repositories.interface"; diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts index f3c6dc33..e5949bde 100644 --- a/packages/db/src/services/DatabaseService.ts +++ b/packages/db/src/services/DatabaseService.ts @@ -5,9 +5,12 @@ import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export class DatabaseService { private dataSource: DataSource; + private static instance: DatabaseService; + private telemetryPacketRepo: Repository; constructor() { this.dataSource = AppDataSource; + this.telemetryPacketRepo = this.dataSource.getRepository(TelemetryPacket); } async initialize(): Promise { @@ -22,6 +25,127 @@ export class DatabaseService { } } + static getInstance(): DatabaseService { + if (!DatabaseService.instance) { + DatabaseService.instance = new DatabaseService(); + } + return DatabaseService.instance; + } + + /** + * Helper function to flatten ITelemetryData into TelemetryPacket format + */ + private flattenTelemetryData( + packet: ITelemetryData, + ): Partial { + const timestamp = new Date(packet.TimeStamp * 1000); + const Rfid = packet.Pi.Rfid; + + return { + // Metadata + RaceName: packet.Title, + Rfid, + // Primary keys + Timestamp: timestamp, + Title: packet.Title, + + // B3 data + ...packet.B3, + + // Battery data + ...packet.Battery, + + // Battery Faults (flatten Errors and Warnings) + ...packet.BatteryFaults.Errors, + ...packet.BatteryFaults.Warnings, + + // Contactor data + ...packet.Contactor, + + // KeyMotor data + ...packet.KeyMotor, + + // MBMS data + ...packet.MBMS, + + // MPPT data + ...packet.MPPT, + + // Motor 0 data (prefix all fields with Motor0) + Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, + Motor0BemfD: packet.MotorDetails0.BEMF_D, + Motor0BemfQ: packet.MotorDetails0.BEMF_Q, + Motor0BusCurrent: packet.MotorDetails0.BusCurrent, + Motor0BusVoltage: packet.MotorDetails0.BusVoltage, + Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, + Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, + Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, + Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, + Motor0Id: packet.MotorDetails0.Id, + Motor0Iq: packet.MotorDetails0.Iq, + Motor0LimitFlags: packet.MotorDetails0.LimitFlags, + Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, + Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, + Motor0Odometer: packet.MotorDetails0.Odometer, + Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, + Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, + Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, + Motor0SerialNumber: packet.MotorDetails0.SerialNumber, + Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, + Motor0Supply15v: packet.MotorDetails0.Supply15V, + Motor0Supply1v9: packet.MotorDetails0.Supply1V9, + Motor0Supply3v3: packet.MotorDetails0.Supply3V3, + Motor0TritiumId: packet.MotorDetails0.TritiumId, + Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, + Motor0Vd: packet.MotorDetails0.Vd, + Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, + Motor0Vq: packet.MotorDetails0.Vq, + + // Motor 1 data (prefix all fields with Motor1) + Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, + Motor1BemfD: packet.MotorDetails1.BEMF_D, + Motor1BemfQ: packet.MotorDetails1.BEMF_Q, + Motor1BusCurrent: packet.MotorDetails1.BusCurrent, + Motor1BusVoltage: packet.MotorDetails1.BusVoltage, + Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, + Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, + Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, + Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, + Motor1Id: packet.MotorDetails1.Id, + Motor1Iq: packet.MotorDetails1.Iq, + Motor1LimitFlags: packet.MotorDetails1.LimitFlags, + Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, + Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, + Motor1Odometer: packet.MotorDetails1.Odometer, + Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, + Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, + Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, + Motor1SerialNumber: packet.MotorDetails1.SerialNumber, + Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, + Motor1Supply15v: packet.MotorDetails1.Supply15V, + Motor1Supply1v9: packet.MotorDetails1.Supply1V9, + Motor1Supply3v3: packet.MotorDetails1.Supply3V3, + Motor1TritiumId: packet.MotorDetails1.TritiumId, + Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, + Motor1Vd: packet.MotorDetails1.Vd, + Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, + Motor1Vq: packet.MotorDetails1.Vq, + + // ProximitySensors data + ...packet.ProximitySensors, + + // Telemetry data (GPS + MPU) + ...packet.Telemetry, + }; + } + + async insertPacketData(message: ITelemetryData): Promise { + if (this.dataSource.isInitialized) { + const flattenedData = this.flattenTelemetryData(message); + await this.telemetryPacketRepo.save(flattenedData); + } + } + async close(): Promise { if (this.dataSource.isInitialized) { await this.dataSource.destroy(); diff --git a/packages/server/src/controllers/BackendController/BackendController.ts b/packages/server/src/controllers/BackendController/BackendController.ts index 9f4e4bb2..293de1a7 100644 --- a/packages/server/src/controllers/BackendController/BackendController.ts +++ b/packages/server/src/controllers/BackendController/BackendController.ts @@ -1,3 +1,4 @@ +import { DatabaseService } from "db"; import type { IncomingMessage, Server, ServerResponse } from "http"; import type { BackendControllerTypes } from "@/controllers/BackendController/BackendController.types"; @@ -7,8 +8,8 @@ import DynamoDB from "@/datasources/DynamoDB/DynamoDB"; import { SocketIO } from "@/datasources/SocketIO/SocketIO"; import { SolarMQTTClient } from "@/datasources/SolarMQTTClient/SolarMQTTClient"; import { options } from "@/datasources/SolarMQTTClient/SolarMQTTClient.types"; -import { DatabaseManager } from "@/database/DatabaseManager"; +import { DatabaseManager } from "@/database/DatabaseManager"; import { logger } from "@/index"; import { type ITelemetryData } from "@shared/helios-types"; @@ -20,6 +21,7 @@ export class BackendController implements BackendControllerTypes { public mqtt: SolarMQTTClient; public databaseManager: DatabaseManager; public carLatency: number; + public databaseService: DatabaseService; constructor( httpsServer: Server, ) { @@ -31,12 +33,14 @@ export class BackendController implements BackendControllerTypes { this.establishCarPinging(); this.carLatency = 0; this.initializeDatabase(); + this.databaseService = DatabaseService.getInstance(); // this.handleCarLatency(); } private async initializeDatabase() { try { await this.databaseManager.initialize(); + // await this.databaseService.initialize(); logger.info("Database connection established successfully"); } catch (error) { logger.error("Failed to initialize database:", error); @@ -68,6 +72,9 @@ export class BackendController implements BackendControllerTypes { // Handle the packet in the lap controller await this.lapController.handlePacket(message); + + // insert to timescaledb + await this.databaseService.insertPacketData(message); } public handleCarDisconnect() { @@ -84,6 +91,7 @@ export class BackendController implements BackendControllerTypes { public async cleanup() { try { await this.databaseManager.close(); + // await this.databaseService.close(); logger.info("Database connection closed successfully"); } catch (error) { logger.error("Error closing database connection:", error); diff --git a/packages/server/src/database/DatabaseManager.ts b/packages/server/src/database/DatabaseManager.ts index 361cc5a4..38e735f2 100644 --- a/packages/server/src/database/DatabaseManager.ts +++ b/packages/server/src/database/DatabaseManager.ts @@ -1,15 +1,11 @@ -// -import { createLightweightApplicationLogger } from "@/utils/logger"; +import { AppDataSource } from "db"; -import { AppDataSource } from "@db/data-source"; -import { TelemetryService } from "@db/services/TelemetryService"; -import { ITelemetryData } from "@shared/helios-types"; +import { createLightweightApplicationLogger } from "@/utils/logger"; const logger = createLightweightApplicationLogger("DatabaseManager.ts"); export class DatabaseManager { private static instance: DatabaseManager; - private telemetryService?: TelemetryService; private isConnected = false; private constructor() {} @@ -24,26 +20,11 @@ export class DatabaseManager { async initialize() { if (!this.isConnected) { await AppDataSource.initialize(); - this.telemetryService = new TelemetryService(); this.isConnected = true; logger.info("Database initialized successfully"); } } - async insertTelemetryData(packet: ITelemetryData) { - if (!this.telemetryService) { - throw new Error("DatabaseManager not initialized"); - } - return this.telemetryService.insertTelemetryPacket(packet); - } - - async getTelemetryBetweenDates(start: Date, end: Date, rfid?: string) { - if (!this.telemetryService) { - throw new Error("DatabaseManager not initialized"); - } - return this.telemetryService.getTelemetryBetweenDates(start, end, rfid); - } - async close() { if (this.isConnected) { await AppDataSource.destroy(); From 1d7040f3775582b8defb51af2b1227fbfed59031 Mon Sep 17 00:00:00 2001 From: burtonjong Date: Sat, 10 Jan 2026 14:42:48 -0700 Subject: [PATCH 05/16] fix --- packages/db/src/data-source.ts | 54 +--- packages/db/src/entities/Driver.entity.ts | 14 +- packages/db/src/entities/Lap.entity.ts | 13 +- .../db/src/entities/TelemetryPacket.entity.ts | 18 +- packages/db/src/index.ts | 3 - packages/db/src/services/DatabaseService.ts | 271 +----------------- .../BackendController/BackendController.ts | 18 +- .../server/src/database/DatabaseManager.ts | 35 --- 8 files changed, 33 insertions(+), 393 deletions(-) delete mode 100644 packages/server/src/database/DatabaseManager.ts diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts index d58f8c29..29437e80 100644 --- a/packages/db/src/data-source.ts +++ b/packages/db/src/data-source.ts @@ -3,17 +3,6 @@ import { DataSource } from "typeorm"; import dotenv from "dotenv"; // import your entities/tables here -import { TelemetryMetadata } from "./entities/TelemetryMetadata.entity"; -import { Battery } from "./entities/Battery.entity"; -import { BatteryFaults } from "./entities/BatteryFaults.entity"; -import { MotorDetails } from "./entities/MotorDetails.entity"; -import { KeyMotor } from "./entities/KeyMotor.entity"; -import { MPPT } from "./entities/MPPT.entity"; -import { Contactor } from "./entities/Contactor.entity"; -import { B3 } from "./entities/B3.entity"; -import { MBMS } from "./entities/MBMS.entity"; -import { Telemetry } from "./entities/Telemetry.entity"; -import { ProximitySensors } from "./entities/ProximitySensors.entity"; import { Driver } from "./entities/Driver.entity"; import { Lap } from "./entities/Lap.entity"; import { TelemetryPacket } from "./entities/TelemetryPacket.entity"; @@ -21,47 +10,10 @@ import { TelemetryPacket } from "./entities/TelemetryPacket.entity"; dotenv.config({ path: ".db.env" }); export const AppDataSource = new DataSource({ - // database name - database: process.env.DB_NAME || "postgres", - // entity schemas, whenever you make a table you have to add it here - entities: [ - TelemetryMetadata, - Battery, - BatteryFaults, - MotorDetails, - KeyMotor, - MPPT, - Contactor, - B3, - MBMS, - Telemetry, - ProximitySensors, - Driver, - Lap, - TelemetryPacket, - ], - // database host (if it's localhost or not) - host: process.env.DB_HOST || "localhost", - // logging: https://typeorm.io/docs/advanced-topics/logging - // you can enable logging only in development to avoid performance issues - // there are also different level of logging you can set like "all", "query", etc. + entities: [TelemetryPacket, Driver, Lap], logging: process.env.NODE_ENV === "development", - // the migrations directory (not sure if this works yet) migrations: [__dirname + "/migrations/*.{js,ts}"], - password: process.env.POSTGRES_PASSWORD || "postgres", - port: parseInt(process.env.DB_PORT || "5432"), - synchronize: process.env.NODE_ENV === "development", // Only in development + synchronize: process.env.NODE_ENV === "development", type: "postgres", - username: process.env.POSTGRES_USERNAME || "postgres", + url: "postgres://tsdbadmin:l61gksd8ona24d62@l6yyro58r7.wy9edrssy5.tsdb.cloud.timescale.com:38572/tsdb", }); - -// export const AppDataSource = new DataSource({ -// entities: [TelemetryPacket, Driver, Lap], -// logging: process.env.NODE_ENV === "development", -// migrations: [__dirname + "/migrations/*.{js,ts}"], -// synchronize: process.env.NODE_ENV === "development", -// type: "postgres", -// url: -// process.env.DATABASE_URL ?? -// "postgres://tsdbadmin@y0b9h9zgeh.wy9edrssy5.tsdb.cloud.timescale.com:34124/tsdb?sslmode=require", -// }); diff --git a/packages/db/src/entities/Driver.entity.ts b/packages/db/src/entities/Driver.entity.ts index 8c7cf28a..1081234b 100644 --- a/packages/db/src/entities/Driver.entity.ts +++ b/packages/db/src/entities/Driver.entity.ts @@ -4,13 +4,10 @@ import { PrimaryColumn, CreateDateColumn, UpdateDateColumn, - Index, } from "typeorm"; -// use this table!! - /** - * Driver table (not a hypertable) + * Driver table * Stores driver information linked by RFID */ @Entity("driver") @@ -21,10 +18,9 @@ export class Driver { @Column({ type: "text" }) Name!: string; - // might not need these two but good practice - // @CreateDateColumn() - // CreatedAt!: Date; + @CreateDateColumn() + CreatedAt!: Date; - // @UpdateDateColumn() - // UpdatedAt!: Date; + @UpdateDateColumn() + UpdatedAt!: Date; } diff --git a/packages/db/src/entities/Lap.entity.ts b/packages/db/src/entities/Lap.entity.ts index 8bc68eff..58f16ede 100644 --- a/packages/db/src/entities/Lap.entity.ts +++ b/packages/db/src/entities/Lap.entity.ts @@ -1,21 +1,12 @@ import { Entity, Column, Index, PrimaryGeneratedColumn } from "typeorm"; -import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; +import { TimeColumn } from "@timescaledb/typeorm"; /** * Lap data hypertable * Stores computed lap metrics for each lap */ @Entity("lap") -@Hypertable({ - timeColumnName: "timestamp", - chunkTimeInterval: "1 month", - compression: { - compress: true, - compress_segmentby: "rfid", - compress_orderby: "timestamp DESC", - }, -}) -@Index(["rfid", "timestamp"]) +@Index(["Rfid", "Timestamp"]) export class Lap { @PrimaryGeneratedColumn("uuid") Id!: string; diff --git a/packages/db/src/entities/TelemetryPacket.entity.ts b/packages/db/src/entities/TelemetryPacket.entity.ts index 82b354bc..481baf2c 100644 --- a/packages/db/src/entities/TelemetryPacket.entity.ts +++ b/packages/db/src/entities/TelemetryPacket.entity.ts @@ -1,26 +1,22 @@ import { Entity, Column, Index, PrimaryColumn } from "typeorm"; import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; - -// use this table !! - /** - * Flattened Telemetry hypertable * Combines all sensor data into a single wide table for simplified insertions and queries - * Replaces the previous normalized structure with separate tables for each car component */ @Entity("telemetry_packet") @Hypertable({ - timeColumnName: "timestamp", - chunkTimeInterval: "1 month", compression: { compress: true, - compress_segmentby: "rfid, race_name", - compress_orderby: "timestamp DESC", + compress_segmentby: "Rfid", + compress_orderby: "Timestamp", + policy: { + schedule_interval: "7 days", + }, }, }) -@Index(["rfid", "timestamp"]) -@Index(["race_name", "timestamp"]) +@Index(["Rfid", "Timestamp"]) +@Index(["RaceName", "Timestamp"]) export class TelemetryPacket { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 421f9d6a..8c4fbe5d 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,8 +1,5 @@ export { AppDataSource } from "./data-source"; export { BaseRepository } from "./repositories/BaseRepository"; -// entities - -// repositories // interfaces export * from "./interfaces/repositories.interface"; diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts index e5949bde..2e9cf072 100644 --- a/packages/db/src/services/DatabaseService.ts +++ b/packages/db/src/services/DatabaseService.ts @@ -4,24 +4,18 @@ import { ITelemetryData } from "@shared/helios-types"; import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export class DatabaseService { - private dataSource: DataSource; + private isConnected = false; private static instance: DatabaseService; private telemetryPacketRepo: Repository; constructor() { - this.dataSource = AppDataSource; - this.telemetryPacketRepo = this.dataSource.getRepository(TelemetryPacket); + this.telemetryPacketRepo = AppDataSource.getRepository(TelemetryPacket); } - async initialize(): Promise { - try { - if (!this.dataSource.isInitialized) { - await this.dataSource.initialize(); - console.log("Database connection initialized successfully"); - } - } catch (error) { - console.error("Error during database initialization:", error); - throw error; + async initialize() { + if (!this.isConnected) { + await AppDataSource.initialize(); + this.isConnected = true; } } @@ -140,262 +134,17 @@ export class DatabaseService { } async insertPacketData(message: ITelemetryData): Promise { - if (this.dataSource.isInitialized) { + if (this.isConnected) { + console.log("Inserting packet data into database"); const flattenedData = this.flattenTelemetryData(message); await this.telemetryPacketRepo.save(flattenedData); } } async close(): Promise { - if (this.dataSource.isInitialized) { - await this.dataSource.destroy(); + if (this.isConnected) { + await AppDataSource.destroy(); console.log("Database connection closed"); } } - - // Helper method to check if database is connected - get isConnected(): boolean { - return this.dataSource.isInitialized; - } - - // Get the raw data source if needed for advanced operations - get rawDataSource(): DataSource { - return this.dataSource; - } -} - -export class TelemetryService { - private telemetryPacketRepo: Repository; - - constructor() { - this.telemetryPacketRepo = AppDataSource.getRepository(TelemetryPacket); - } - - /** - * Insert complete telemetry packet into flattened table - * All sensor data is stored in a single wide table for simplified insertions - */ - async insertTelemetryPacket(packet: ITelemetryData): Promise { - const timestamp = new Date(packet.TimeStamp * 1000); - const Rfid = packet.Pi.Rfid; - - // Flatten all nested data structures into a single object - // Motor details are prefixed with Motor0_ and Motor1_ - const flattenedData: Partial = { - // Metadata - RaceName: packet.Title, - Rfid, - // Primary keys - Timestamp: timestamp, - Title: packet.Title, - - // B3 data - ...packet.B3, - - // Battery data - ...packet.Battery, - - // Battery Faults (flatten Errors and Warnings) - ...packet.BatteryFaults.Errors, - ...packet.BatteryFaults.Warnings, - - // Contactor data - ...packet.Contactor, - - // KeyMotor data - ...packet.KeyMotor, - - // MBMS data - ...packet.MBMS, - - // MPPT data - ...packet.MPPT, - - // Motor 0 data (prefix all fields with Motor0) - Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, - Motor0BemfD: packet.MotorDetails0.BEMF_D, - Motor0BemfQ: packet.MotorDetails0.BEMF_Q, - Motor0BusCurrent: packet.MotorDetails0.BusCurrent, - Motor0BusVoltage: packet.MotorDetails0.BusVoltage, - Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, - Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, - Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, - Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, - Motor0Id: packet.MotorDetails0.Id, - Motor0Iq: packet.MotorDetails0.Iq, - Motor0LimitFlags: packet.MotorDetails0.LimitFlags, - Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, - Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, - Motor0Odometer: packet.MotorDetails0.Odometer, - Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, - Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, - Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, - Motor0SerialNumber: packet.MotorDetails0.SerialNumber, - Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, - Motor0Supply15v: packet.MotorDetails0.Supply15V, - Motor0Supply1v9: packet.MotorDetails0.Supply1V9, - Motor0Supply3v3: packet.MotorDetails0.Supply3V3, - Motor0TritiumId: packet.MotorDetails0.TritiumId, - Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, - Motor0Vd: packet.MotorDetails0.Vd, - Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, - Motor0Vq: packet.MotorDetails0.Vq, - - // Motor 1 data (prefix all fields with Motor1) - Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, - Motor1BemfD: packet.MotorDetails1.BEMF_D, - Motor1BemfQ: packet.MotorDetails1.BEMF_Q, - Motor1BusCurrent: packet.MotorDetails1.BusCurrent, - Motor1BusVoltage: packet.MotorDetails1.BusVoltage, - Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, - Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, - Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, - Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, - Motor1Id: packet.MotorDetails1.Id, - Motor1Iq: packet.MotorDetails1.Iq, - Motor1LimitFlags: packet.MotorDetails1.LimitFlags, - Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, - Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, - Motor1Odometer: packet.MotorDetails1.Odometer, - Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, - Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, - Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, - Motor1SerialNumber: packet.MotorDetails1.SerialNumber, - Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, - Motor1Supply15v: packet.MotorDetails1.Supply15V, - Motor1Supply1v9: packet.MotorDetails1.Supply1V9, - Motor1Supply3v3: packet.MotorDetails1.Supply3V3, - Motor1TritiumId: packet.MotorDetails1.TritiumId, - Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, - Motor1Vd: packet.MotorDetails1.Vd, - Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, - Motor1Vq: packet.MotorDetails1.Vq, - - // ProximitySensors data - ...packet.ProximitySensors, - - // Telemetry data (GPS + MPU) - ...packet.Telemetry, - }; - - // Single insert into the flattened table - await this.telemetryPacketRepo.save(flattenedData); - } - - /** - * Query telemetry data between dates from the flattened table - */ - async getTelemetryBetweenDates( - startTime: Date, - endTime: Date, - rfid?: string, - ): Promise { - const queryBuilder = this.telemetryPacketRepo - .createQueryBuilder("t") - .where("t.Timestamp BETWEEN :start AND :end", { - end: endTime, - start: startTime, - }); - - if (rfid) { - queryBuilder.andWhere("t.Rfid = :rfid", { rfid }); - } - - return queryBuilder.getMany(); - } - - /** - * Get latest telemetry packet for a specific RFID - */ - async getLatestTelemetry(rfid: string): Promise { - return this.telemetryPacketRepo.findOne({ - order: { Timestamp: "DESC" }, - where: { Rfid: rfid }, - }); - } - - /** - * Batch insert multiple telemetry packets - */ - async insertTelemetryPacketBatch(packets: ITelemetryData[]): Promise { - const flattenedPackets = packets.map((packet) => { - const timestamp = new Date(packet.TimeStamp * 1000); - const Rfid = packet.Pi.Rfid; - - return { - RaceName: packet.Title, - Rfid, - Timestamp: timestamp, - Title: packet.Title, - ...packet.B3, - ...packet.Battery, - ...packet.BatteryFaults.Errors, - ...packet.BatteryFaults.Warnings, - ...packet.Contactor, - ...packet.KeyMotor, - ...packet.MBMS, - ...packet.MPPT, - Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, - Motor0BemfD: packet.MotorDetails0.BEMF_D, - Motor0BemfQ: packet.MotorDetails0.BEMF_Q, - Motor0BusCurrent: packet.MotorDetails0.BusCurrent, - Motor0BusVoltage: packet.MotorDetails0.BusVoltage, - Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, - Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, - Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, - Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, - Motor0Id: packet.MotorDetails0.Id, - Motor0Iq: packet.MotorDetails0.Iq, - Motor0LimitFlags: packet.MotorDetails0.LimitFlags, - Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, - Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, - Motor0Odometer: packet.MotorDetails0.Odometer, - Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, - Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, - Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, - Motor0SerialNumber: packet.MotorDetails0.SerialNumber, - Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, - Motor0Supply15v: packet.MotorDetails0.Supply15V, - Motor0Supply1v9: packet.MotorDetails0.Supply1V9, - Motor0Supply3v3: packet.MotorDetails0.Supply3V3, - Motor0TritiumId: packet.MotorDetails0.TritiumId, - Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, - Motor0Vd: packet.MotorDetails0.Vd, - Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, - Motor0Vq: packet.MotorDetails0.Vq, - Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, - Motor1BemfD: packet.MotorDetails1.BEMF_D, - Motor1BemfQ: packet.MotorDetails1.BEMF_Q, - Motor1BusCurrent: packet.MotorDetails1.BusCurrent, - Motor1BusVoltage: packet.MotorDetails1.BusVoltage, - Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, - Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, - Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, - Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, - Motor1Id: packet.MotorDetails1.Id, - Motor1Iq: packet.MotorDetails1.Iq, - Motor1LimitFlags: packet.MotorDetails1.LimitFlags, - Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, - Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, - Motor1Odometer: packet.MotorDetails1.Odometer, - Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, - Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, - Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, - Motor1SerialNumber: packet.MotorDetails1.SerialNumber, - Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, - Motor1Supply15v: packet.MotorDetails1.Supply15V, - Motor1Supply1v9: packet.MotorDetails1.Supply1V9, - Motor1Supply3v3: packet.MotorDetails1.Supply3V3, - Motor1TritiumId: packet.MotorDetails1.TritiumId, - Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, - Motor1Vd: packet.MotorDetails1.Vd, - Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, - Motor1Vq: packet.MotorDetails1.Vq, - ...packet.ProximitySensors, - ...packet.Telemetry, - } as Partial; - }); - - await this.telemetryPacketRepo.save(flattenedPackets); - } } diff --git a/packages/server/src/controllers/BackendController/BackendController.ts b/packages/server/src/controllers/BackendController/BackendController.ts index 293de1a7..4f5834cc 100644 --- a/packages/server/src/controllers/BackendController/BackendController.ts +++ b/packages/server/src/controllers/BackendController/BackendController.ts @@ -9,7 +9,6 @@ import { SocketIO } from "@/datasources/SocketIO/SocketIO"; import { SolarMQTTClient } from "@/datasources/SolarMQTTClient/SolarMQTTClient"; import { options } from "@/datasources/SolarMQTTClient/SolarMQTTClient.types"; -import { DatabaseManager } from "@/database/DatabaseManager"; import { logger } from "@/index"; import { type ITelemetryData } from "@shared/helios-types"; @@ -19,9 +18,8 @@ export class BackendController implements BackendControllerTypes { public socketIO: SocketIO; public lapController: LapController; public mqtt: SolarMQTTClient; - public databaseManager: DatabaseManager; - public carLatency: number; public databaseService: DatabaseService; + public carLatency: number; constructor( httpsServer: Server, ) { @@ -29,18 +27,16 @@ export class BackendController implements BackendControllerTypes { this.socketIO = new SocketIO(httpsServer, this); this.mqtt = new SolarMQTTClient(options, this); this.lapController = new LapController(this); - this.databaseManager = DatabaseManager.getInstance(); + this.databaseService = DatabaseService.getInstance(); this.establishCarPinging(); this.carLatency = 0; this.initializeDatabase(); - this.databaseService = DatabaseService.getInstance(); // this.handleCarLatency(); } private async initializeDatabase() { try { - await this.databaseManager.initialize(); - // await this.databaseService.initialize(); + await this.databaseService.initialize(); logger.info("Database connection established successfully"); } catch (error) { logger.error("Failed to initialize database:", error); @@ -67,14 +63,13 @@ export class BackendController implements BackendControllerTypes { // Insert the packet into the database this.dynamoDB.insertPacketData(message); + this.databaseService.insertPacketData(message); + // Broadcast the packet to the frontend this.socketIO.broadcastPacket(message); // Handle the packet in the lap controller await this.lapController.handlePacket(message); - - // insert to timescaledb - await this.databaseService.insertPacketData(message); } public handleCarDisconnect() { @@ -90,8 +85,7 @@ export class BackendController implements BackendControllerTypes { public async cleanup() { try { - await this.databaseManager.close(); - // await this.databaseService.close(); + await this.databaseService.close(); logger.info("Database connection closed successfully"); } catch (error) { logger.error("Error closing database connection:", error); diff --git a/packages/server/src/database/DatabaseManager.ts b/packages/server/src/database/DatabaseManager.ts deleted file mode 100644 index 38e735f2..00000000 --- a/packages/server/src/database/DatabaseManager.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { AppDataSource } from "db"; - -import { createLightweightApplicationLogger } from "@/utils/logger"; - -const logger = createLightweightApplicationLogger("DatabaseManager.ts"); - -export class DatabaseManager { - private static instance: DatabaseManager; - private isConnected = false; - - private constructor() {} - - static getInstance(): DatabaseManager { - if (!DatabaseManager.instance) { - DatabaseManager.instance = new DatabaseManager(); - } - return DatabaseManager.instance; - } - - async initialize() { - if (!this.isConnected) { - await AppDataSource.initialize(); - this.isConnected = true; - logger.info("Database initialized successfully"); - } - } - - async close() { - if (this.isConnected) { - await AppDataSource.destroy(); - this.isConnected = false; - logger.info("Database connection closed"); - } - } -} From f5271610ff024843a739014acc631720f2ebfbe6 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 29 Nov 2025 14:38:36 -0700 Subject: [PATCH 06/16] change snakecase to pascalcase for columns --- packages/db/src/entities/B3.entity.ts | 48 +++---- packages/db/src/entities/Battery.entity.ts | 72 +++++------ .../db/src/entities/BatteryFaults.entity.ts | 74 +++++------ packages/db/src/entities/Contactor.entity.ts | 104 +++++++-------- packages/db/src/entities/Driver.entity.ts | 13 +- packages/db/src/entities/KeyMotor.entity.ts | 10 +- packages/db/src/entities/Lap.entity.ts | 26 ++-- packages/db/src/entities/MBMS.entity.ts | 118 +++++++++--------- packages/db/src/entities/MPPT.entity.ts | 68 +++++----- .../db/src/entities/MotorDetails.entity.ts | 62 ++++----- .../src/entities/ProximitySensors.entity.ts | 12 +- packages/db/src/entities/Telemetry.entity.ts | 40 +++--- .../src/entities/TelemetryMetadata.entity.ts | 8 +- 13 files changed, 328 insertions(+), 327 deletions(-) diff --git a/packages/db/src/entities/B3.entity.ts b/packages/db/src/entities/B3.entity.ts index efdbe424..f890f550 100644 --- a/packages/db/src/entities/B3.entity.ts +++ b/packages/db/src/entities/B3.entity.ts @@ -19,74 +19,74 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class B3 { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - acceleration!: number; + Acceleration!: number; @Column({ type: "boolean" }) - b3_heartbeat!: boolean; + B3Heartbeat!: boolean; @Column({ type: "boolean" }) - brake_light_signal_status!: boolean; + BrakeLightSignalStatus!: boolean; @Column({ type: "boolean" }) - brake_switch_digital!: boolean; + BrakeSwitchDigital!: boolean; @Column({ type: "boolean" }) - daytime_running_light_signal_status!: boolean; + DaytimeRunningLightSignalStatus!: boolean; @Column({ type: "boolean" }) - forward_digital!: boolean; + ForwardDigital!: boolean; @Column({ type: "boolean" }) - handbrake_switch_digital!: boolean; + HandbrakeSwitchDigital!: boolean; @Column({ type: "boolean" }) - hazard_lights_input!: boolean; + HazardLightsInput!: boolean; @Column({ type: "boolean" }) - headlights_switch_input!: boolean; + HeadlightsSwitchInput!: boolean; @Column({ type: "boolean" }) - headlight_signal_status!: boolean; + HeadlightSignalStatus!: boolean; @Column({ type: "boolean" }) - horn_signal_status!: boolean; + HornSignalStatus!: boolean; @Column({ type: "boolean" }) - horn_switch_digital!: boolean; + HornSwitchDigital!: boolean; @Column({ type: "boolean" }) - lap_digital!: boolean; + LapDigital!: boolean; @Column({ type: "boolean" }) - left_signal_input!: boolean; + LeftSignalInput!: boolean; @Column({ type: "boolean" }) - left_signal_status!: boolean; + LeftSignalStatus!: boolean; @Column({ type: "boolean" }) - motor_reset_digital!: boolean; + MotorResetDigital!: boolean; @Column({ type: "boolean" }) - neutral_digital!: boolean; + NeutralDigital!: boolean; @Column({ type: "boolean" }) - race_mode_digital!: boolean; + RaceModeDigital!: boolean; @Column({ type: "float" }) - regen_braking!: number; + RegenBraking!: number; @Column({ type: "boolean" }) - reverse_digital!: boolean; + ReverseDigital!: boolean; @Column({ type: "boolean" }) - right_signal_input!: boolean; + RightSignalInput!: boolean; @Column({ type: "boolean" }) - right_signal_status!: boolean; + RightSignalStatus!: boolean; } diff --git a/packages/db/src/entities/Battery.entity.ts b/packages/db/src/entities/Battery.entity.ts index 703d524c..609b6660 100644 --- a/packages/db/src/entities/Battery.entity.ts +++ b/packages/db/src/entities/Battery.entity.ts @@ -19,110 +19,110 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class Battery { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "boolean" }) - always_on_signal_status!: boolean; + AlwaysOnSignalStatus!: boolean; @Column({ type: "float" }) - average_cell_voltage!: number; + AverageCellVoltage!: number; @Column({ type: "float" }) - average_temperature!: number; + AverageTemperature!: number; @Column({ type: "integer" }) - bmu_alive!: number; + BmuAlive!: number; @Column({ type: "boolean" }) - charge_relay_enabled!: boolean; + ChargeRelayEnabled!: boolean; @Column({ type: "boolean" }) - charger_safety_enabled!: boolean; + ChargerSafetyEnabled!: boolean; @Column({ type: "boolean" }) - discharge_relay_enabled!: boolean; + DischargeRelayEnabled!: boolean; @Column({ type: "float" }) - fan_speed!: number; + FanSpeed!: number; @Column({ type: "float" }) - fan_voltage!: number; + FanVoltage!: number; @Column({ type: "float" }) - high_cell_voltage!: number; + HighCellVoltage!: number; @Column({ type: "integer" }) - high_cell_voltage_id!: number; + HighCellVoltageId!: number; @Column({ type: "float" }) - high_temperature!: number; + HighTemperature!: number; @Column({ type: "integer" }) - high_thermistor_id!: number; + HighThermistorId!: number; @Column({ type: "float" }) - input_12v!: number; + Input12v!: number; @Column({ type: "float" }) - internal_temperature!: number; + InternalTemperature!: number; @Column({ type: "boolean" }) - is_charging_signal_status!: boolean; + IsChargingSignalStatus!: boolean; @Column({ type: "boolean" }) - is_ready_signal_status!: boolean; + IsReadySignalStatus!: boolean; @Column({ type: "float" }) - low_cell_voltage!: number; + LowCellVoltage!: number; @Column({ type: "integer" }) - low_cell_voltage_id!: number; + LowCellVoltageId!: number; @Column({ type: "float" }) - low_temperature!: number; + LowTemperature!: number; @Column({ type: "integer" }) - low_thermistor_id!: number; + LowThermistorId!: number; @Column({ type: "boolean" }) - malfunction_indicator_active!: boolean; + MalfunctionIndicatorActive!: boolean; @Column({ type: "float" }) - maximum_cell_voltage!: number; + MaximumCellVoltage!: number; @Column({ type: "float" }) - maximum_pack_voltage!: number; + MaximumPackVoltage!: number; @Column({ type: "float" }) - minimum_cell_voltage!: number; + MinimumCellVoltage!: number; @Column({ type: "float" }) - minimum_pack_voltage!: number; + MinimumPackVoltage!: number; @Column({ type: "boolean" }) - multi_purpose_input_signal_status!: boolean; + MultiPurposeInputSignalStatus!: boolean; @Column({ type: "float" }) - pack_amphours!: number; + PackAmphours!: number; @Column({ type: "float" }) - pack_current!: number; + PackCurrent!: number; @Column({ type: "float" }) - pack_depth_of_discharge!: number; + PackDepthOfDischarge!: number; @Column({ type: "float" }) - pack_state_of_charge!: number; + PackStateOfCharge!: number; @Column({ type: "float" }) - pack_voltage!: number; + PackVoltage!: number; @Column({ type: "integer" }) - populated_cells!: number; + PopulatedCells!: number; @Column({ type: "float" }) - requested_fan_speed!: number; + RequestedFanSpeed!: number; } diff --git a/packages/db/src/entities/BatteryFaults.entity.ts b/packages/db/src/entities/BatteryFaults.entity.ts index 97c5e322..c69381a5 100644 --- a/packages/db/src/entities/BatteryFaults.entity.ts +++ b/packages/db/src/entities/BatteryFaults.entity.ts @@ -19,115 +19,115 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class BatteryFaults { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; // Errors @Column({ type: "boolean" }) - error_always_on_supply_fault!: boolean; + ErrorAlwaysOnSupplyFault!: boolean; @Column({ type: "boolean" }) - error_canbus_communication_fault!: boolean; + ErrorCanbusCommunicationFault!: boolean; @Column({ type: "boolean" }) - error_charge_limit_enforcement_fault!: boolean; + ErrorChargeLimitEnforcementFault!: boolean; @Column({ type: "boolean" }) - error_charger_safety_relay_fault!: boolean; + ErrorChargerSafetyRelayFault!: boolean; @Column({ type: "boolean" }) - error_current_sensor_fault!: boolean; + ErrorCurrentSensorFault!: boolean; @Column({ type: "boolean" }) - error_discharge_limit_enforcement_fault!: boolean; + ErrorDischargeLimitEnforcementFault!: boolean; @Column({ type: "boolean" }) - error_fan_monitor_fault!: boolean; + ErrorFanMonitorFault!: boolean; @Column({ type: "boolean" }) - error_high_voltage_isolation_fault!: boolean; + ErrorHighVoltageIsolationFault!: boolean; @Column({ type: "boolean" }) - error_internal_communication_fault!: boolean; + ErrorInternalCommunicationFault!: boolean; @Column({ type: "boolean" }) - error_internal_conversion_fault!: boolean; + ErrorInternalConversionFault!: boolean; @Column({ type: "boolean" }) - error_internal_logic_fault!: boolean; + ErrorInternalLogicFault!: boolean; @Column({ type: "boolean" }) - error_internal_memory_fault!: boolean; + ErrorInternalMemoryFault!: boolean; @Column({ type: "boolean" }) - error_internal_thermistor_fault!: boolean; + ErrorInternalThermistorFault!: boolean; @Column({ type: "boolean" }) - error_low_cell_voltage_fault!: boolean; + ErrorLowCellVoltageFault!: boolean; @Column({ type: "boolean" }) - error_open_wiring_fault!: boolean; + ErrorOpenWiringFault!: boolean; @Column({ type: "boolean" }) - error_pack_voltage_sensor_fault!: boolean; + ErrorPackVoltageSensorFault!: boolean; @Column({ type: "boolean" }) - error_power_supply_12v_fault!: boolean; + ErrorPowerSupply12vFault!: boolean; @Column({ type: "boolean" }) - error_thermistor_fault!: boolean; + ErrorThermistorFault!: boolean; @Column({ type: "boolean" }) - error_voltage_redundancy_fault!: boolean; + ErrorVoltageRedundancyFault!: boolean; @Column({ type: "boolean" }) - error_weak_cell_fault!: boolean; + ErrorWeakCellFault!: boolean; @Column({ type: "boolean" }) - error_weak_pack_fault!: boolean; + ErrorWeakPackFault!: boolean; // Warnings @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_alternate_current_limit!: boolean; + WarningCclReducedDueToAlternateCurrentLimit!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_charger_latch!: boolean; + WarningCclReducedDueToChargerLatch!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_cell_resistance!: boolean; + WarningCclReducedDueToHighCellResistance!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_cell_voltage!: boolean; + WarningCclReducedDueToHighCellVoltage!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_pack_voltage!: boolean; + WarningCclReducedDueToHighPackVoltage!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_high_soc!: boolean; + WarningCclReducedDueToHighSoc!: boolean; @Column({ type: "boolean" }) - warning_ccl_reduced_due_to_temperature!: boolean; + WarningCclReducedDueToTemperature!: boolean; @Column({ type: "boolean" }) - warning_dcl_and_ccl_reduced_due_to_communication_failsafe!: boolean; + WarningDclAndCclReducedDueToCommunicationFailsafe!: boolean; @Column({ type: "boolean" }) - warning_dcl_and_ccl_reduced_due_to_voltage_failsafe!: boolean; + WarningDclAndCclReducedDueToVoltageFailsafe!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_high_cell_resistance!: boolean; + WarningDclReducedDueToHighCellResistance!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_low_cell_voltage!: boolean; + WarningDclReducedDueToLowCellVoltage!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_low_pack_voltage!: boolean; + WarningDclReducedDueToLowPackVoltage!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_low_soc!: boolean; + WarningDclReducedDueToLowSoc!: boolean; @Column({ type: "boolean" }) - warning_dcl_reduced_due_to_temperature!: boolean; + WarningDclReducedDueToTemperature!: boolean; } diff --git a/packages/db/src/entities/Contactor.entity.ts b/packages/db/src/entities/Contactor.entity.ts index a68797f7..3cf0d940 100644 --- a/packages/db/src/entities/Contactor.entity.ts +++ b/packages/db/src/entities/Contactor.entity.ts @@ -19,163 +19,163 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class Contactor { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; // Array Contactor @Column({ type: "boolean" }) - array_bps_error!: boolean; + ArrayBpsError!: boolean; @Column({ type: "float" }) - array_charge_current!: number; + ArrayChargeCurrent!: number; @Column({ type: "boolean" }) - array_contactor_closed!: boolean; + ArrayContactorClosed!: boolean; @Column({ type: "boolean" }) - array_contactor_closing!: boolean; + ArrayContactorClosing!: boolean; @Column({ type: "boolean" }) - array_contactor_error!: boolean; + ArrayContactorError!: boolean; @Column({ type: "boolean" }) - array_heartbeat!: boolean; + ArrayHeartbeat!: boolean; @Column({ type: "float" }) - array_line_current!: number; + ArrayLineCurrent!: number; @Column({ type: "boolean" }) - array_precharger_closed!: boolean; + ArrayPrechargerClosed!: boolean; @Column({ type: "boolean" }) - array_precharger_closing!: boolean; + ArrayPrechargerClosing!: boolean; @Column({ type: "boolean" }) - array_precharger_error!: boolean; + ArrayPrechargerError!: boolean; // Charge Contactor @Column({ type: "boolean" }) - charge_bps_error!: boolean; + ChargeBpsError!: boolean; @Column({ type: "float" }) - charge_charge_current!: number; + ChargeChargeCurrent!: number; @Column({ type: "boolean" }) - charge_contactor_closed!: boolean; + ChargeContactorClosed!: boolean; @Column({ type: "boolean" }) - charge_contactor_closing!: boolean; + ChargeContactorClosing!: boolean; @Column({ type: "boolean" }) - charge_contactor_error!: boolean; + ChargeContactorError!: boolean; @Column({ type: "boolean" }) - charge_heartbeat!: boolean; + ChargeHeartbeat!: boolean; @Column({ type: "float" }) - charge_line_current!: number; + ChargeLineCurrent!: number; @Column({ type: "boolean" }) - charge_precharger_closed!: boolean; + ChargePrechargerClosed!: boolean; @Column({ type: "boolean" }) - charge_precharger_closing!: boolean; + ChargePrechargerClosing!: boolean; @Column({ type: "boolean" }) - charge_precharger_error!: boolean; + ChargePrechargerError!: boolean; // Common Contactor @Column({ type: "float" }) - common_charge_current!: number; + CommonChargeCurrent!: number; @Column({ type: "boolean" }) - common_contactor_closed!: boolean; + CommonContactorClosed!: boolean; @Column({ type: "boolean" }) - common_contactor_closing!: boolean; + CommonContactorClosing!: boolean; @Column({ type: "boolean" }) - common_contactor_error!: boolean; + CommonContactorError!: boolean; @Column({ type: "boolean" }) - common_contactor_opening_error!: boolean; + CommonContactorOpeningError!: boolean; @Column({ type: "boolean" }) - common_heartbeat!: boolean; + CommonHeartbeat!: boolean; @Column({ type: "float" }) - common_line_current!: number; + CommonLineCurrent!: number; @Column({ type: "boolean" }) - common_precharger_closed!: boolean; + CommonPrechargerClosed!: boolean; @Column({ type: "boolean" }) - common_precharger_closing!: boolean; + CommonPrechargerClosing!: boolean; @Column({ type: "boolean" }) - common_precharger_error!: boolean; + CommonPrechargerError!: boolean; // LV Contactor @Column({ type: "boolean" }) - lv_bps_error!: boolean; + LvBpsError!: boolean; @Column({ type: "float" }) - lv_charge_current!: number; + LvChargeCurrent!: number; @Column({ type: "boolean" }) - lv_contactor_closed!: boolean; + LvContactorClosed!: boolean; @Column({ type: "boolean" }) - lv_contactor_closing!: boolean; + LvContactorClosing!: boolean; @Column({ type: "boolean" }) - lv_contactor_error!: boolean; + LvContactorError!: boolean; @Column({ type: "boolean" }) - lv_heartbeat!: boolean; + LvHeartbeat!: boolean; @Column({ type: "float" }) - lv_line_current!: number; + LvLineCurrent!: number; @Column({ type: "boolean" }) - lv_precharger_closed!: boolean; + LvPrechargerClosed!: boolean; @Column({ type: "boolean" }) - lv_precharger_closing!: boolean; + LvPrechargerClosing!: boolean; @Column({ type: "boolean" }) - lv_precharger_error!: boolean; + LvPrechargerError!: boolean; // Motor Contactor @Column({ type: "boolean" }) - motor_bps_error!: boolean; + MotorBpsError!: boolean; @Column({ type: "float" }) - motor_charge_current!: number; + MotorChargeCurrent!: number; @Column({ type: "boolean" }) - motor_contactor_closed!: boolean; + MotorContactorClosed!: boolean; @Column({ type: "boolean" }) - motor_contactor_closing!: boolean; + MotorContactorClosing!: boolean; @Column({ type: "boolean" }) - motor_contactor_error!: boolean; + MotorContactorError!: boolean; @Column({ type: "boolean" }) - motor_heartbeat!: boolean; + MotorHeartbeat!: boolean; @Column({ type: "float" }) - motor_line_current!: number; + MotorLineCurrent!: number; @Column({ type: "boolean" }) - motor_precharger_closed!: boolean; + MotorPrechargerClosed!: boolean; @Column({ type: "boolean" }) - motor_precharger_closing!: boolean; + MotorPrechargerClosing!: boolean; @Column({ type: "boolean" }) - motor_precharger_error!: boolean; + MotorPrechargerError!: boolean; } diff --git a/packages/db/src/entities/Driver.entity.ts b/packages/db/src/entities/Driver.entity.ts index 5c24197e..f7609b4a 100644 --- a/packages/db/src/entities/Driver.entity.ts +++ b/packages/db/src/entities/Driver.entity.ts @@ -14,14 +14,15 @@ import { @Entity("driver") export class Driver { @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "text" }) - name!: string; + Name!: string; - @CreateDateColumn() - created_at!: Date; + // might not need these two but good practice + // @CreateDateColumn() + // CreatedAt!: Date; - @UpdateDateColumn() - updated_at!: Date; + // @UpdateDateColumn() + // UpdatedAt!: Date; } diff --git a/packages/db/src/entities/KeyMotor.entity.ts b/packages/db/src/entities/KeyMotor.entity.ts index cf6df4db..3bb06444 100644 --- a/packages/db/src/entities/KeyMotor.entity.ts +++ b/packages/db/src/entities/KeyMotor.entity.ts @@ -19,17 +19,17 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class KeyMotor { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - bus_current_out!: number; + BusCurrentOut!: number; @Column({ type: "float" }) - key_motor_velocity!: number; + KeyMotorVelocity!: number; @Column({ type: "float" }) - motor_current!: number; + MotorCurrent!: number; } diff --git a/packages/db/src/entities/Lap.entity.ts b/packages/db/src/entities/Lap.entity.ts index dc066d88..8bc68eff 100644 --- a/packages/db/src/entities/Lap.entity.ts +++ b/packages/db/src/entities/Lap.entity.ts @@ -18,41 +18,41 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; @Index(["rfid", "timestamp"]) export class Lap { @PrimaryGeneratedColumn("uuid") - id!: string; + Id!: string; @TimeColumn() - timestamp!: Date; + Timestamp!: Date; @Column({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - lap_time!: number; + LapTime!: number; @Column({ type: "float" }) - total_power_in!: number; + TotalPowerIn!: number; @Column({ type: "float" }) - total_power_out!: number; + TotalPowerOut!: number; @Column({ type: "float" }) - net_power_out!: number; + NetPowerOut!: number; @Column({ type: "float" }) - distance!: number; + Distance!: number; @Column({ type: "float" }) - energy_consumed!: number; + EnergyConsumed!: number; @Column({ type: "float" }) - amp_hours!: number; + AmpHours!: number; @Column({ type: "float" }) - average_pack_current!: number; + AveragePackCurrent!: number; @Column({ type: "float" }) - battery_seconds_remaining!: number; + BatterySecondsRemaining!: number; @Column({ type: "float" }) - average_speed!: number; + AverageSpeed!: number; } diff --git a/packages/db/src/entities/MBMS.entity.ts b/packages/db/src/entities/MBMS.entity.ts index cf11158d..5873ca25 100644 --- a/packages/db/src/entities/MBMS.entity.ts +++ b/packages/db/src/entities/MBMS.entity.ts @@ -19,179 +19,179 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class MBMS { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "boolean" }) - abatt_disable!: boolean; + AbattDisable!: boolean; @Column({ type: "boolean" }) - array_contactor_command!: boolean; + ArrayContactorCommand!: boolean; @Column({ type: "boolean" }) - array_heartbeat_dead_trip!: boolean; + ArrayHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - array_high_current_trip!: boolean; + ArrayHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - array_high_current_warning!: boolean; + ArrayHighCurrentWarning!: boolean; @Column({ type: "float" }) - auxiliary_battery_voltage!: number; + AuxiliaryBatteryVoltage!: number; @Column({ type: "boolean" }) - can_oc_12v_warning!: boolean; + CanOc12vWarning!: boolean; @Column({ type: "boolean" }) - charge_contactor_command!: boolean; + ChargeContactorCommand!: boolean; @Column({ type: "boolean" }) - charge_enable!: boolean; + ChargeEnable!: boolean; @Column({ type: "boolean" }) - charge_heartbeat_dead_trip!: boolean; + ChargeHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - charge_high_current_trip!: boolean; + ChargeHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - charge_high_current_warning!: boolean; + ChargeHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - charge_safety!: boolean; + ChargeSafety!: boolean; @Column({ type: "boolean" }) - charge_should_trip!: boolean; + ChargeShouldTrip!: boolean; @Column({ type: "boolean" }) - chg_fault!: boolean; + ChgFault!: boolean; @Column({ type: "boolean" }) - chg_lv_en!: boolean; + ChgLvEn!: boolean; @Column({ type: "boolean" }) - chg_on!: boolean; + ChgOn!: boolean; @Column({ type: "boolean" }) - common_contactor_command!: boolean; + CommonContactorCommand!: boolean; @Column({ type: "boolean" }) - common_heartbeat_dead_trip!: boolean; + CommonHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - common_high_current_trip!: boolean; + CommonHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - common_high_current_warning!: boolean; + CommonHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - contactor_connected_unexpectedly_trip!: boolean; + ContactorConnectedUnexpectedlyTrip!: boolean; @Column({ type: "boolean" }) - contactor_disconnected_unexpectedly_trip!: boolean; + ContactorDisconnectedUnexpectedlyTrip!: boolean; @Column({ type: "boolean" }) - dcdc_fault!: boolean; + DcdcFault!: boolean; @Column({ type: "boolean" }) - dcdc_on!: boolean; + DcdcOn!: boolean; @Column({ type: "boolean" }) - discharge_enable!: boolean; + DischargeEnable!: boolean; @Column({ type: "boolean" }) - discharge_should_trip!: boolean; + DischargeShouldTrip!: boolean; @Column({ type: "boolean" }) - en1!: boolean; + En1!: boolean; @Column({ type: "boolean" }) - esd_enabled_trip!: boolean; + EsdEnabledTrip!: boolean; @Column({ type: "boolean" }) - external_shutdown!: boolean; + ExternalShutdown!: boolean; @Column({ type: "boolean" }) - heartbeat!: boolean; + Heartbeat!: boolean; @Column({ type: "boolean" }) - high_cell_voltage_trip!: boolean; + HighCellVoltageTrip!: boolean; @Column({ type: "boolean" }) - high_cell_voltage_warning!: boolean; + HighCellVoltageWarning!: boolean; @Column({ type: "boolean" }) - high_temperature_trip!: boolean; + HighTemperatureTrip!: boolean; @Column({ type: "boolean" }) - high_temperature_warning!: boolean; + HighTemperatureWarning!: boolean; @Column({ type: "boolean" }) - key!: boolean; + Key!: boolean; @Column({ type: "boolean" }) - low_cell_voltage_trip!: boolean; + LowCellVoltageTrip!: boolean; @Column({ type: "boolean" }) - low_cell_voltage_warning!: boolean; + LowCellVoltageWarning!: boolean; @Column({ type: "boolean" }) - low_temperature_trip!: boolean; + LowTemperatureTrip!: boolean; @Column({ type: "boolean" }) - low_temperature_warning!: boolean; + LowTemperatureWarning!: boolean; @Column({ type: "boolean" }) - lv_contactor_command!: boolean; + LvContactorCommand!: boolean; @Column({ type: "boolean" }) - lv_heartbeat_dead_trip!: boolean; + LvHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - lv_high_current_trip!: boolean; + LvHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - lv_high_current_warning!: boolean; + LvHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - main_power_switch!: boolean; + MainPowerSwitch!: boolean; @Column({ type: "boolean" }) - motor_contactor_command!: boolean; + MotorContactorCommand!: boolean; @Column({ type: "boolean" }) - motor_heartbeat_dead_trip!: boolean; + MotorHeartbeatDeadTrip!: boolean; @Column({ type: "boolean" }) - motor_high_current_trip!: boolean; + MotorHighCurrentTrip!: boolean; @Column({ type: "boolean" }) - motor_high_current_warning!: boolean; + MotorHighCurrentWarning!: boolean; @Column({ type: "boolean" }) - mps_disabled_trip!: boolean; + MpsDisabledTrip!: boolean; @Column({ type: "boolean" }) - orion_can_received_recently!: boolean; + OrionCanReceivedRecently!: boolean; @Column({ type: "boolean" }) - orion_message_timeout_trip!: boolean; + OrionMessageTimeoutTrip!: boolean; @Column({ type: "boolean" }) - protection_trip!: boolean; + ProtectionTrip!: boolean; @Column({ type: "float" }) - startup_state!: number; + StartupState!: number; @Column({ type: "boolean" }) - strobe_bms_light!: boolean; + StrobeBmsLight!: boolean; @Column({ type: "float" }) - system_state!: number; + SystemState!: number; @Column({ type: "boolean" }) - three_a_oc!: boolean; + ThreeAOc!: boolean; } diff --git a/packages/db/src/entities/MPPT.entity.ts b/packages/db/src/entities/MPPT.entity.ts index 8709f390..e4ae88e3 100644 --- a/packages/db/src/entities/MPPT.entity.ts +++ b/packages/db/src/entities/MPPT.entity.ts @@ -19,107 +19,107 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class MPPT { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - mppt0_ch0_array_current!: number; + Mppt0Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt0_ch0_array_voltage!: number; + Mppt0Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt0_ch0_battery_voltage!: number; + Mppt0Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt0_ch0_unit_temperature!: number; + Mppt0Ch0UnitTemperature!: number; @Column({ type: "float" }) - mppt0_ch1_array_current!: number; + Mppt0Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt0_ch1_array_voltage!: number; + Mppt0Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt0_ch1_battery_voltage!: number; + Mppt0Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt0_ch1_unit_temperature!: number; + Mppt0Ch1UnitTemperature!: number; @Column({ type: "float" }) - mppt1_ch0_array_current!: number; + Mppt1Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt1_ch0_array_voltage!: number; + Mppt1Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt1_ch0_battery_voltage!: number; + Mppt1Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt1_ch0_unit_temperature!: number; + Mppt1Ch0UnitTemperature!: number; @Column({ type: "float" }) - mppt1_ch1_array_current!: number; + Mppt1Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt1_ch1_array_voltage!: number; + Mppt1Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt1_ch1_battery_voltage!: number; + Mppt1Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt1_ch1_unit_temperature!: number; + Mppt1Ch1UnitTemperature!: number; @Column({ type: "float" }) - mppt2_ch0_array_current!: number; + Mppt2Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt2_ch0_array_voltage!: number; + Mppt2Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt2_ch0_battery_voltage!: number; + Mppt2Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt2_ch0_unit_temperature!: number; + Mppt2Ch0UnitTemperature!: number; // MPPT 2 Channel 1 @Column({ type: "float" }) - mppt2_ch1_array_current!: number; + Mppt2Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt2_ch1_array_voltage!: number; + Mppt2Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt2_ch1_battery_voltage!: number; + Mppt2Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt2_ch1_unit_temperature!: number; + Mppt2Ch1UnitTemperature!: number; // MPPT 3 Channel 0 @Column({ type: "float" }) - mppt3_ch0_array_current!: number; + Mppt3Ch0ArrayCurrent!: number; @Column({ type: "float" }) - mppt3_ch0_array_voltage!: number; + Mppt3Ch0ArrayVoltage!: number; @Column({ type: "float" }) - mppt3_ch0_battery_voltage!: number; + Mppt3Ch0BatteryVoltage!: number; @Column({ type: "float" }) - mppt3_ch0_unit_temperature!: number; + Mppt3Ch0UnitTemperature!: number; // MPPT 3 Channel 1 @Column({ type: "float" }) - mppt3_ch1_array_current!: number; + Mppt3Ch1ArrayCurrent!: number; @Column({ type: "float" }) - mppt3_ch1_array_voltage!: number; + Mppt3Ch1ArrayVoltage!: number; @Column({ type: "float" }) - mppt3_ch1_battery_voltage!: number; + Mppt3Ch1BatteryVoltage!: number; @Column({ type: "float" }) - mppt3_ch1_unit_temperature!: number; + Mppt3Ch1UnitTemperature!: number; } diff --git a/packages/db/src/entities/MotorDetails.entity.ts b/packages/db/src/entities/MotorDetails.entity.ts index 668c4ad4..fdc8122b 100644 --- a/packages/db/src/entities/MotorDetails.entity.ts +++ b/packages/db/src/entities/MotorDetails.entity.ts @@ -21,95 +21,95 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class MotorDetails { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @PrimaryColumn({ type: "int" }) - motor_id!: number; + MotorId!: number; @Column({ type: "float" }) - active_motor!: number; + ActiveMotor!: number; @Column({ type: "float" }) - bemf_d!: number; + BemfD!: number; @Column({ type: "float" }) - bemf_q!: number; + BemfQ!: number; @Column({ type: "float" }) - bus_current!: number; + BusCurrent!: number; @Column({ type: "float" }) - bus_voltage!: number; + BusVoltage!: number; @Column({ type: "float" }) - dc_bus_ah!: number; + DcBusAh!: number; @Column({ type: "float" }) - dsp_board_temperature!: number; + DspBoardTemperature!: number; @Column({ type: "float" }) - error_flags!: number; + ErrorFlags!: number; @Column({ type: "float" }) - heatsink_temperature!: number; + HeatsinkTemperature!: number; @Column({ type: "float" }) - id!: number; + Id!: number; @Column({ type: "float" }) - iq!: number; + Iq!: number; @Column({ type: "float" }) - limit_flags!: number; + LimitFlags!: number; @Column({ type: "float" }) - motor_temperature!: number; + MotorTemperature!: number; @Column({ type: "float" }) - motor_velocity!: number; + MotorVelocity!: number; @Column({ type: "float" }) - odometer!: number; + Odometer!: number; @Column({ type: "float" }) - phase_current_b!: number; + PhaseCurrentB!: number; @Column({ type: "float" }) - phase_current_c!: number; + PhaseCurrentC!: number; @Column({ type: "float" }) - rx_error_count!: number; + RxErrorCount!: number; @Column({ type: "float" }) - serial_number!: number; + SerialNumber!: number; @Column({ type: "float" }) - slip_speed!: number; + SlipSpeed!: number; @Column({ type: "float" }) - supply_15v!: number; + Supply15v!: number; @Column({ type: "float" }) - supply_1v9!: number; + Supply1v9!: number; @Column({ type: "float" }) - supply_3v3!: number; + Supply3v3!: number; @Column({ type: "float" }) - tritium_id!: number; + TritiumId!: number; @Column({ type: "float" }) - tx_error_count!: number; + TxErrorCount!: number; @Column({ type: "float" }) - vd!: number; + Vd!: number; @Column({ type: "float" }) - vehicle_velocity!: number; + VehicleVelocity!: number; @Column({ type: "float" }) - vq!: number; + Vq!: number; } diff --git a/packages/db/src/entities/ProximitySensors.entity.ts b/packages/db/src/entities/ProximitySensors.entity.ts index 7f4c68fa..26b62b4e 100644 --- a/packages/db/src/entities/ProximitySensors.entity.ts +++ b/packages/db/src/entities/ProximitySensors.entity.ts @@ -19,20 +19,20 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class ProximitySensors { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "float" }) - proximity_sensor_1!: number; + ProximitySensor1!: number; @Column({ type: "float" }) - proximity_sensor_2!: number; + ProximitySensor2!: number; @Column({ type: "float" }) - proximity_sensor_3!: number; + RroximitySensor3!: number; @Column({ type: "float" }) - proximity_sensor_4!: number; + ProximitySensor4!: number; } diff --git a/packages/db/src/entities/Telemetry.entity.ts b/packages/db/src/entities/Telemetry.entity.ts index d556830d..8b34a8c3 100644 --- a/packages/db/src/entities/Telemetry.entity.ts +++ b/packages/db/src/entities/Telemetry.entity.ts @@ -19,64 +19,64 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class Telemetry { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; // GPS Data @Column({ type: "float" }) - gps_additional_flags!: number; + GpsAdditionalFlags!: number; @Column({ type: "float" }) - gps_day!: number; + GpsDay!: number; @Column({ type: "float" }) - gps_fix_status_flags!: number; + GpsFixStatusFlags!: number; @Column({ type: "float" }) - gps_hour!: number; + GpsHour!: number; @Column({ type: "float" }) - gps_latitude!: number; + GpsLatitude!: number; @Column({ type: "float" }) - gps_longitude!: number; + GpsLongitude!: number; @Column({ type: "float" }) - gps_minute!: number; + GpsMinute!: number; @Column({ type: "float" }) - gps_month!: number; + GpsMonth!: number; @Column({ type: "float" }) - gps_second!: number; + GpsSecond!: number; @Column({ type: "float" }) - gps_validity_flags!: number; + GpsValidityFlags!: number; @Column({ type: "float" }) - gps_year!: number; + GpsYear!: number; // MPU (Motion Processing Unit) Data @Column({ type: "float" }) - mpu_acceleration_x!: number; + MpuAccelerationX!: number; @Column({ type: "float" }) - mpu_acceleration_y!: number; + MpuAccelerationY!: number; @Column({ type: "float" }) - mpu_acceleration_z!: number; + MpuAccelerationZ!: number; @Column({ type: "float" }) - mpu_rotation_x!: number; + MpuRotationX!: number; @Column({ type: "float" }) - mpu_rotation_y!: number; + MpuRotationY!: number; @Column({ type: "float" }) - mpu_rotation_z!: number; + MpuRotationZ!: number; @Column({ type: "float" }) - mpu_temperature!: number; + MpuTemperature!: number; } diff --git a/packages/db/src/entities/TelemetryMetadata.entity.ts b/packages/db/src/entities/TelemetryMetadata.entity.ts index 25a8ebbe..ec660332 100644 --- a/packages/db/src/entities/TelemetryMetadata.entity.ts +++ b/packages/db/src/entities/TelemetryMetadata.entity.ts @@ -21,14 +21,14 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; export class TelemetryMetadata { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) - timestamp!: Date; + Timestamp!: Date; @PrimaryColumn({ type: "text" }) - rfid!: string; + Rfid!: string; @Column({ type: "text", nullable: true }) - race_name?: string; + RaceName?: string; @Column({ type: "text", nullable: true }) - title?: string; + Title?: string; } From 2f4cc167ffc7eab087cb0e826fa7767e62291cce Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 10 Jan 2026 11:06:01 -0700 Subject: [PATCH 07/16] make flattened table instead of many for car parts --- packages/db/src/entities/Driver.entity.ts | 2 + .../db/src/entities/TelemetryPacket.entity.ts | 1023 +++++++++++++++++ packages/db/src/index.ts | 3 +- .../src/interfaces/repositories.interface.ts | 4 + .../db/src/repositories/BaseRepository.ts | 6 +- .../repositories/TelemetryPacketRepository.ts | 13 + packages/db/src/services/DatabaseService.ts | 240 +++- .../server/src/database/DatabaseManager.ts | 56 +- 8 files changed, 1320 insertions(+), 27 deletions(-) create mode 100644 packages/db/src/entities/TelemetryPacket.entity.ts create mode 100644 packages/db/src/repositories/TelemetryPacketRepository.ts diff --git a/packages/db/src/entities/Driver.entity.ts b/packages/db/src/entities/Driver.entity.ts index f7609b4a..8c7cf28a 100644 --- a/packages/db/src/entities/Driver.entity.ts +++ b/packages/db/src/entities/Driver.entity.ts @@ -7,6 +7,8 @@ import { Index, } from "typeorm"; +// use this table!! + /** * Driver table (not a hypertable) * Stores driver information linked by RFID diff --git a/packages/db/src/entities/TelemetryPacket.entity.ts b/packages/db/src/entities/TelemetryPacket.entity.ts new file mode 100644 index 00000000..82b354bc --- /dev/null +++ b/packages/db/src/entities/TelemetryPacket.entity.ts @@ -0,0 +1,1023 @@ +import { Entity, Column, Index, PrimaryColumn } from "typeorm"; +import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; + + +// use this table !! + +/** + * Flattened Telemetry hypertable + * Combines all sensor data into a single wide table for simplified insertions and queries + * Replaces the previous normalized structure with separate tables for each car component + */ +@Entity("telemetry_packet") +@Hypertable({ + timeColumnName: "timestamp", + chunkTimeInterval: "1 month", + compression: { + compress: true, + compress_segmentby: "rfid, race_name", + compress_orderby: "timestamp DESC", + }, +}) +@Index(["rfid", "timestamp"]) +@Index(["race_name", "timestamp"]) +export class TelemetryPacket { + @TimeColumn() + @PrimaryColumn({ type: "timestamptz" }) + Timestamp!: Date; + + @PrimaryColumn({ type: "text" }) + Rfid!: string; + + // =============================================== + // Metadata Fields (from TelemetryMetadata) + // =============================================== + @Column({ type: "text", nullable: true }) + RaceName?: string; + + @Column({ type: "text", nullable: true }) + Title?: string; + + // =============================================== + // B3 Fields (Button Board 3) + // =============================================== + @Column({ type: "float", nullable: true }) + Acceleration?: number; + + @Column({ type: "boolean", nullable: true }) + B3Heartbeat?: boolean; + + @Column({ type: "boolean", nullable: true }) + BrakeLightSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + BrakeSwitchDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + DaytimeRunningLightSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + ForwardDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + HandbrakeSwitchDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + HazardLightsInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + HeadlightsSwitchInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + HeadlightSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + HornSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + HornSwitchDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + LapDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + LeftSignalInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + LeftSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorResetDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + NeutralDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + RaceModeDigital?: boolean; + + @Column({ type: "float", nullable: true }) + RegenBraking?: number; + + @Column({ type: "boolean", nullable: true }) + ReverseDigital?: boolean; + + @Column({ type: "boolean", nullable: true }) + RightSignalInput?: boolean; + + @Column({ type: "boolean", nullable: true }) + RightSignalStatus?: boolean; + + // =============================================== + // Battery Fields + // =============================================== + @Column({ type: "boolean", nullable: true }) + AlwaysOnSignalStatus?: boolean; + + @Column({ type: "float", nullable: true }) + AverageCellVoltage?: number; + + @Column({ type: "float", nullable: true }) + AverageTemperature?: number; + + @Column({ type: "integer", nullable: true }) + BmuAlive?: number; + + @Column({ type: "boolean", nullable: true }) + ChargeRelayEnabled?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargerSafetyEnabled?: boolean; + + @Column({ type: "boolean", nullable: true }) + DischargeRelayEnabled?: boolean; + + @Column({ type: "float", nullable: true }) + FanSpeed?: number; + + @Column({ type: "float", nullable: true }) + FanVoltage?: number; + + @Column({ type: "float", nullable: true }) + HighCellVoltage?: number; + + @Column({ type: "integer", nullable: true }) + HighCellVoltageId?: number; + + @Column({ type: "float", nullable: true }) + HighTemperature?: number; + + @Column({ type: "integer", nullable: true }) + HighThermistorId?: number; + + @Column({ type: "float", nullable: true }) + Input12v?: number; + + @Column({ type: "float", nullable: true }) + InternalTemperature?: number; + + @Column({ type: "boolean", nullable: true }) + IsChargingSignalStatus?: boolean; + + @Column({ type: "boolean", nullable: true }) + IsReadySignalStatus?: boolean; + + @Column({ type: "float", nullable: true }) + LowCellVoltage?: number; + + @Column({ type: "integer", nullable: true }) + LowCellVoltageId?: number; + + @Column({ type: "float", nullable: true }) + LowTemperature?: number; + + @Column({ type: "integer", nullable: true }) + LowThermistorId?: number; + + @Column({ type: "boolean", nullable: true }) + MalfunctionIndicatorActive?: boolean; + + @Column({ type: "float", nullable: true }) + MaximumCellVoltage?: number; + + @Column({ type: "float", nullable: true }) + MaximumPackVoltage?: number; + + @Column({ type: "float", nullable: true }) + MinimumCellVoltage?: number; + + @Column({ type: "float", nullable: true }) + MinimumPackVoltage?: number; + + @Column({ type: "boolean", nullable: true }) + MultiPurposeInputSignalStatus?: boolean; + + @Column({ type: "float", nullable: true }) + PackAmphours?: number; + + @Column({ type: "float", nullable: true }) + PackCurrent?: number; + + @Column({ type: "float", nullable: true }) + PackDepthOfDischarge?: number; + + @Column({ type: "float", nullable: true }) + PackStateOfCharge?: number; + + @Column({ type: "float", nullable: true }) + PackVoltage?: number; + + @Column({ type: "integer", nullable: true }) + PopulatedCells?: number; + + @Column({ type: "float", nullable: true }) + RequestedFanSpeed?: number; + + // =============================================== + // Battery Faults Fields (Errors) + // =============================================== + @Column({ type: "boolean", nullable: true }) + ErrorAlwaysOnSupplyFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorCanbusCommunicationFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorChargeLimitEnforcementFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorChargerSafetyRelayFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorCurrentSensorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorDischargeLimitEnforcementFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorFanMonitorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorHighVoltageIsolationFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalCommunicationFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalConversionFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalLogicFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalMemoryFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorInternalThermistorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorLowCellVoltageFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorOpenWiringFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorPackVoltageSensorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorPowerSupply12vFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorThermistorFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorVoltageRedundancyFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorWeakCellFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ErrorWeakPackFault?: boolean; + + // =============================================== + // Battery Faults Fields (Warnings) + // =============================================== + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToAlternateCurrentLimit?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToChargerLatch?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighCellResistance?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighCellVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighPackVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToHighSoc?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningCclReducedDueToTemperature?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclAndCclReducedDueToCommunicationFailsafe?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclAndCclReducedDueToVoltageFailsafe?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToHighCellResistance?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToLowCellVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToLowPackVoltage?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToLowSoc?: boolean; + + @Column({ type: "boolean", nullable: true }) + WarningDclReducedDueToTemperature?: boolean; + + // =============================================== + // Contactor Fields - Array + // =============================================== + @Column({ type: "boolean", nullable: true }) + ArrayBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + ArrayChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + ArrayLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ArrayPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayPrechargerError?: boolean; + + // =============================================== + // Contactor Fields - Charge + // =============================================== + @Column({ type: "boolean", nullable: true }) + ChargeBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + ChargeChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + ChargeLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + ChargePrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargePrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargePrechargerError?: boolean; + + // =============================================== + // Contactor Fields - Common + // =============================================== + @Column({ type: "float", nullable: true }) + CommonChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + CommonContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorOpeningError?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + CommonLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + CommonPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonPrechargerError?: boolean; + + // =============================================== + // Contactor Fields - LV + // =============================================== + @Column({ type: "boolean", nullable: true }) + LvBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + LvChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + LvContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + LvLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + LvPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvPrechargerError?: boolean; + + // =============================================== + // Contactor Fields - Motor + // =============================================== + @Column({ type: "boolean", nullable: true }) + MotorBpsError?: boolean; + + @Column({ type: "float", nullable: true }) + MotorChargeCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + MotorContactorClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorContactorClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorContactorError?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHeartbeat?: boolean; + + @Column({ type: "float", nullable: true }) + MotorLineCurrent?: number; + + @Column({ type: "boolean", nullable: true }) + MotorPrechargerClosed?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorPrechargerClosing?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorPrechargerError?: boolean; + + // =============================================== + // KeyMotor Fields + // =============================================== + @Column({ type: "float", nullable: true }) + BusCurrentOut?: number; + + @Column({ type: "float", nullable: true }) + KeyMotorVelocity?: number; + + @Column({ type: "float", nullable: true }) + MotorCurrent?: number; + + // =============================================== + // MBMS Fields (Master Battery Management System) + // =============================================== + @Column({ type: "boolean", nullable: true }) + AbattDisable?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ArrayHighCurrentWarning?: boolean; + + @Column({ type: "float", nullable: true }) + AuxiliaryBatteryVoltage?: number; + + @Column({ type: "boolean", nullable: true }) + CanOc12vWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeEnable?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeSafety?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChargeShouldTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChgFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChgLvEn?: boolean; + + @Column({ type: "boolean", nullable: true }) + ChgOn?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + CommonHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + ContactorConnectedUnexpectedlyTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ContactorDisconnectedUnexpectedlyTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + DcdcFault?: boolean; + + @Column({ type: "boolean", nullable: true }) + DcdcOn?: boolean; + + @Column({ type: "boolean", nullable: true }) + DischargeEnable?: boolean; + + @Column({ type: "boolean", nullable: true }) + DischargeShouldTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + En1?: boolean; + + @Column({ type: "boolean", nullable: true }) + EsdEnabledTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ExternalShutdown?: boolean; + + @Column({ type: "boolean", nullable: true }) + Heartbeat?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighCellVoltageTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighCellVoltageWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighTemperatureTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + HighTemperatureWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + Key?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowCellVoltageTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowCellVoltageWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowTemperatureTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LowTemperatureWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + LvHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + MainPowerSwitch?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorContactorCommand?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHeartbeatDeadTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHighCurrentTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + MotorHighCurrentWarning?: boolean; + + @Column({ type: "boolean", nullable: true }) + MpsDisabledTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + OrionCanReceivedRecently?: boolean; + + @Column({ type: "boolean", nullable: true }) + OrionMessageTimeoutTrip?: boolean; + + @Column({ type: "boolean", nullable: true }) + ProtectionTrip?: boolean; + + @Column({ type: "float", nullable: true }) + StartupState?: number; + + @Column({ type: "boolean", nullable: true }) + StrobeBmsLight?: boolean; + + @Column({ type: "float", nullable: true }) + SystemState?: number; + + @Column({ type: "boolean", nullable: true }) + ThreeAOc?: boolean; + + // =============================================== + // MotorDetails Fields (Motor 0) + // Note: Motor 1 fields would need to be added separately with different names + // e.g., Motor0ActiveMotor, Motor1ActiveMotor + // =============================================== + @Column({ type: "float", nullable: true }) + Motor0ActiveMotor?: number; + + @Column({ type: "float", nullable: true }) + Motor0BemfD?: number; + + @Column({ type: "float", nullable: true }) + Motor0BemfQ?: number; + + @Column({ type: "float", nullable: true }) + Motor0BusCurrent?: number; + + @Column({ type: "float", nullable: true }) + Motor0BusVoltage?: number; + + @Column({ type: "float", nullable: true }) + Motor0DcBusAh?: number; + + @Column({ type: "float", nullable: true }) + Motor0DspBoardTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor0ErrorFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor0HeatsinkTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor0Id?: number; + + @Column({ type: "float", nullable: true }) + Motor0Iq?: number; + + @Column({ type: "float", nullable: true }) + Motor0LimitFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor0MotorTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor0MotorVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor0Odometer?: number; + + @Column({ type: "float", nullable: true }) + Motor0PhaseCurrentB?: number; + + @Column({ type: "float", nullable: true }) + Motor0PhaseCurrentC?: number; + + @Column({ type: "float", nullable: true }) + Motor0RxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor0SerialNumber?: number; + + @Column({ type: "float", nullable: true }) + Motor0SlipSpeed?: number; + + @Column({ type: "float", nullable: true }) + Motor0Supply15v?: number; + + @Column({ type: "float", nullable: true }) + Motor0Supply1v9?: number; + + @Column({ type: "float", nullable: true }) + Motor0Supply3v3?: number; + + @Column({ type: "float", nullable: true }) + Motor0TritiumId?: number; + + @Column({ type: "float", nullable: true }) + Motor0TxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor0Vd?: number; + + @Column({ type: "float", nullable: true }) + Motor0VehicleVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor0Vq?: number; + + // =============================================== + // MotorDetails Fields (Motor 1) + // =============================================== + @Column({ type: "float", nullable: true }) + Motor1ActiveMotor?: number; + + @Column({ type: "float", nullable: true }) + Motor1BemfD?: number; + + @Column({ type: "float", nullable: true }) + Motor1BemfQ?: number; + + @Column({ type: "float", nullable: true }) + Motor1BusCurrent?: number; + + @Column({ type: "float", nullable: true }) + Motor1BusVoltage?: number; + + @Column({ type: "float", nullable: true }) + Motor1DcBusAh?: number; + + @Column({ type: "float", nullable: true }) + Motor1DspBoardTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor1ErrorFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor1HeatsinkTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor1Id?: number; + + @Column({ type: "float", nullable: true }) + Motor1Iq?: number; + + @Column({ type: "float", nullable: true }) + Motor1LimitFlags?: number; + + @Column({ type: "float", nullable: true }) + Motor1MotorTemperature?: number; + + @Column({ type: "float", nullable: true }) + Motor1MotorVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor1Odometer?: number; + + @Column({ type: "float", nullable: true }) + Motor1PhaseCurrentB?: number; + + @Column({ type: "float", nullable: true }) + Motor1PhaseCurrentC?: number; + + @Column({ type: "float", nullable: true }) + Motor1RxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor1SerialNumber?: number; + + @Column({ type: "float", nullable: true }) + Motor1SlipSpeed?: number; + + @Column({ type: "float", nullable: true }) + Motor1Supply15v?: number; + + @Column({ type: "float", nullable: true }) + Motor1Supply1v9?: number; + + @Column({ type: "float", nullable: true }) + Motor1Supply3v3?: number; + + @Column({ type: "float", nullable: true }) + Motor1TritiumId?: number; + + @Column({ type: "float", nullable: true }) + Motor1TxErrorCount?: number; + + @Column({ type: "float", nullable: true }) + Motor1Vd?: number; + + @Column({ type: "float", nullable: true }) + Motor1VehicleVelocity?: number; + + @Column({ type: "float", nullable: true }) + Motor1Vq?: number; + + // =============================================== + // MPPT Fields (Maximum Power Point Tracker) + // =============================================== + @Column({ type: "float", nullable: true }) + Mppt0Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt0Ch1UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt1Ch1UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt2Ch1UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch0UnitTemperature?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1ArrayCurrent?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1ArrayVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1BatteryVoltage?: number; + + @Column({ type: "float", nullable: true }) + Mppt3Ch1UnitTemperature?: number; + + // =============================================== + // ProximitySensors Fields + // =============================================== + @Column({ type: "float", nullable: true }) + ProximitySensor1?: number; + + @Column({ type: "float", nullable: true }) + ProximitySensor2?: number; + + @Column({ type: "float", nullable: true }) + RroximitySensor3?: number; + + @Column({ type: "float", nullable: true }) + ProximitySensor4?: number; + + // =============================================== + // Telemetry Fields (GPS and MPU) + // =============================================== + @Column({ type: "float", nullable: true }) + GpsAdditionalFlags?: number; + + @Column({ type: "float", nullable: true }) + GpsDay?: number; + + @Column({ type: "float", nullable: true }) + GpsFixStatusFlags?: number; + + @Column({ type: "float", nullable: true }) + GpsHour?: number; + + @Column({ type: "float", nullable: true }) + GpsLatitude?: number; + + @Column({ type: "float", nullable: true }) + GpsLongitude?: number; + + @Column({ type: "float", nullable: true }) + GpsMinute?: number; + + @Column({ type: "float", nullable: true }) + GpsMonth?: number; + + @Column({ type: "float", nullable: true }) + GpsSecond?: number; + + @Column({ type: "float", nullable: true }) + GpsValidityFlags?: number; + + @Column({ type: "float", nullable: true }) + GpsYear?: number; + + @Column({ type: "float", nullable: true }) + MpuAccelerationX?: number; + + @Column({ type: "float", nullable: true }) + MpuAccelerationY?: number; + + @Column({ type: "float", nullable: true }) + MpuAccelerationZ?: number; + + @Column({ type: "float", nullable: true }) + MpuRotationX?: number; + + @Column({ type: "float", nullable: true }) + MpuRotationY?: number; + + @Column({ type: "float", nullable: true }) + MpuRotationZ?: number; + + @Column({ type: "float", nullable: true }) + MpuTemperature?: number; +} diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index bf932795..dffd6deb 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,5 +1,6 @@ export { AppDataSource } from "./data-source"; - +export { BaseRepository } from "./repositories/BaseRepository"; +export { TelemetryService } from "./services/TelemetryService"; // entities // repositories diff --git a/packages/db/src/interfaces/repositories.interface.ts b/packages/db/src/interfaces/repositories.interface.ts index 7e13f894..f22d28b8 100644 --- a/packages/db/src/interfaces/repositories.interface.ts +++ b/packages/db/src/interfaces/repositories.interface.ts @@ -12,6 +12,7 @@ import { MPPT } from "../entities/MPPT.entity"; import { ProximitySensors } from "../entities/ProximitySensors.entity"; import { Telemetry } from "../entities/Telemetry.entity"; import { TelemetryMetadata } from "../entities/TelemetryMetadata.entity"; +import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export interface IBaseRepository { create(entity: DeepPartial): Promise; @@ -52,3 +53,6 @@ export interface ITelemetryRepository extends IBaseRepository {} export interface ITelemetryMetadataRepository extends IBaseRepository {} + +export interface ITelemetryPacketRepository + extends IBaseRepository {} diff --git a/packages/db/src/repositories/BaseRepository.ts b/packages/db/src/repositories/BaseRepository.ts index 3b424170..c82fbca5 100644 --- a/packages/db/src/repositories/BaseRepository.ts +++ b/packages/db/src/repositories/BaseRepository.ts @@ -6,10 +6,10 @@ import { } from "typeorm"; import { ObjectLiteral, FindOneOptions } from "typeorm"; -// every entity has either timestamp (car parts) or rfid (driver and car parts) as its primary key +// every entity has either timestamp (car parts) and/or rfid (driver and car parts) as its primary key interface BaseEntity { - timestamp?: Date; - rfid?: string; + Timestamp?: Date; + Rfid?: string; } export abstract class BaseRepository { diff --git a/packages/db/src/repositories/TelemetryPacketRepository.ts b/packages/db/src/repositories/TelemetryPacketRepository.ts new file mode 100644 index 00000000..b07fef54 --- /dev/null +++ b/packages/db/src/repositories/TelemetryPacketRepository.ts @@ -0,0 +1,13 @@ +import { Repository } from "typeorm"; +import { BaseRepository } from "./BaseRepository"; +import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; +import { ITelemetryPacketRepository } from "../interfaces/repositories.interface"; + +export class TelemetryRepository + extends BaseRepository + implements ITelemetryPacketRepository +{ + constructor(repository: Repository) { + super(repository); + } +} diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts index 5cdd0219..f3c6dc33 100644 --- a/packages/db/src/services/DatabaseService.ts +++ b/packages/db/src/services/DatabaseService.ts @@ -1,5 +1,7 @@ -import { DataSource } from "typeorm"; +import { DataSource, Repository } from "typeorm"; import { AppDataSource } from "../data-source"; +import { ITelemetryData } from "@shared/helios-types"; +import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export class DatabaseService { private dataSource: DataSource; @@ -37,3 +39,239 @@ export class DatabaseService { return this.dataSource; } } + +export class TelemetryService { + private telemetryPacketRepo: Repository; + + constructor() { + this.telemetryPacketRepo = AppDataSource.getRepository(TelemetryPacket); + } + + /** + * Insert complete telemetry packet into flattened table + * All sensor data is stored in a single wide table for simplified insertions + */ + async insertTelemetryPacket(packet: ITelemetryData): Promise { + const timestamp = new Date(packet.TimeStamp * 1000); + const Rfid = packet.Pi.Rfid; + + // Flatten all nested data structures into a single object + // Motor details are prefixed with Motor0_ and Motor1_ + const flattenedData: Partial = { + // Metadata + RaceName: packet.Title, + Rfid, + // Primary keys + Timestamp: timestamp, + Title: packet.Title, + + // B3 data + ...packet.B3, + + // Battery data + ...packet.Battery, + + // Battery Faults (flatten Errors and Warnings) + ...packet.BatteryFaults.Errors, + ...packet.BatteryFaults.Warnings, + + // Contactor data + ...packet.Contactor, + + // KeyMotor data + ...packet.KeyMotor, + + // MBMS data + ...packet.MBMS, + + // MPPT data + ...packet.MPPT, + + // Motor 0 data (prefix all fields with Motor0) + Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, + Motor0BemfD: packet.MotorDetails0.BEMF_D, + Motor0BemfQ: packet.MotorDetails0.BEMF_Q, + Motor0BusCurrent: packet.MotorDetails0.BusCurrent, + Motor0BusVoltage: packet.MotorDetails0.BusVoltage, + Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, + Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, + Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, + Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, + Motor0Id: packet.MotorDetails0.Id, + Motor0Iq: packet.MotorDetails0.Iq, + Motor0LimitFlags: packet.MotorDetails0.LimitFlags, + Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, + Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, + Motor0Odometer: packet.MotorDetails0.Odometer, + Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, + Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, + Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, + Motor0SerialNumber: packet.MotorDetails0.SerialNumber, + Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, + Motor0Supply15v: packet.MotorDetails0.Supply15V, + Motor0Supply1v9: packet.MotorDetails0.Supply1V9, + Motor0Supply3v3: packet.MotorDetails0.Supply3V3, + Motor0TritiumId: packet.MotorDetails0.TritiumId, + Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, + Motor0Vd: packet.MotorDetails0.Vd, + Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, + Motor0Vq: packet.MotorDetails0.Vq, + + // Motor 1 data (prefix all fields with Motor1) + Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, + Motor1BemfD: packet.MotorDetails1.BEMF_D, + Motor1BemfQ: packet.MotorDetails1.BEMF_Q, + Motor1BusCurrent: packet.MotorDetails1.BusCurrent, + Motor1BusVoltage: packet.MotorDetails1.BusVoltage, + Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, + Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, + Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, + Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, + Motor1Id: packet.MotorDetails1.Id, + Motor1Iq: packet.MotorDetails1.Iq, + Motor1LimitFlags: packet.MotorDetails1.LimitFlags, + Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, + Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, + Motor1Odometer: packet.MotorDetails1.Odometer, + Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, + Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, + Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, + Motor1SerialNumber: packet.MotorDetails1.SerialNumber, + Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, + Motor1Supply15v: packet.MotorDetails1.Supply15V, + Motor1Supply1v9: packet.MotorDetails1.Supply1V9, + Motor1Supply3v3: packet.MotorDetails1.Supply3V3, + Motor1TritiumId: packet.MotorDetails1.TritiumId, + Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, + Motor1Vd: packet.MotorDetails1.Vd, + Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, + Motor1Vq: packet.MotorDetails1.Vq, + + // ProximitySensors data + ...packet.ProximitySensors, + + // Telemetry data (GPS + MPU) + ...packet.Telemetry, + }; + + // Single insert into the flattened table + await this.telemetryPacketRepo.save(flattenedData); + } + + /** + * Query telemetry data between dates from the flattened table + */ + async getTelemetryBetweenDates( + startTime: Date, + endTime: Date, + rfid?: string, + ): Promise { + const queryBuilder = this.telemetryPacketRepo + .createQueryBuilder("t") + .where("t.Timestamp BETWEEN :start AND :end", { + end: endTime, + start: startTime, + }); + + if (rfid) { + queryBuilder.andWhere("t.Rfid = :rfid", { rfid }); + } + + return queryBuilder.getMany(); + } + + /** + * Get latest telemetry packet for a specific RFID + */ + async getLatestTelemetry(rfid: string): Promise { + return this.telemetryPacketRepo.findOne({ + order: { Timestamp: "DESC" }, + where: { Rfid: rfid }, + }); + } + + /** + * Batch insert multiple telemetry packets + */ + async insertTelemetryPacketBatch(packets: ITelemetryData[]): Promise { + const flattenedPackets = packets.map((packet) => { + const timestamp = new Date(packet.TimeStamp * 1000); + const Rfid = packet.Pi.Rfid; + + return { + RaceName: packet.Title, + Rfid, + Timestamp: timestamp, + Title: packet.Title, + ...packet.B3, + ...packet.Battery, + ...packet.BatteryFaults.Errors, + ...packet.BatteryFaults.Warnings, + ...packet.Contactor, + ...packet.KeyMotor, + ...packet.MBMS, + ...packet.MPPT, + Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, + Motor0BemfD: packet.MotorDetails0.BEMF_D, + Motor0BemfQ: packet.MotorDetails0.BEMF_Q, + Motor0BusCurrent: packet.MotorDetails0.BusCurrent, + Motor0BusVoltage: packet.MotorDetails0.BusVoltage, + Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, + Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, + Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, + Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, + Motor0Id: packet.MotorDetails0.Id, + Motor0Iq: packet.MotorDetails0.Iq, + Motor0LimitFlags: packet.MotorDetails0.LimitFlags, + Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, + Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, + Motor0Odometer: packet.MotorDetails0.Odometer, + Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, + Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, + Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, + Motor0SerialNumber: packet.MotorDetails0.SerialNumber, + Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, + Motor0Supply15v: packet.MotorDetails0.Supply15V, + Motor0Supply1v9: packet.MotorDetails0.Supply1V9, + Motor0Supply3v3: packet.MotorDetails0.Supply3V3, + Motor0TritiumId: packet.MotorDetails0.TritiumId, + Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, + Motor0Vd: packet.MotorDetails0.Vd, + Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, + Motor0Vq: packet.MotorDetails0.Vq, + Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, + Motor1BemfD: packet.MotorDetails1.BEMF_D, + Motor1BemfQ: packet.MotorDetails1.BEMF_Q, + Motor1BusCurrent: packet.MotorDetails1.BusCurrent, + Motor1BusVoltage: packet.MotorDetails1.BusVoltage, + Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, + Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, + Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, + Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, + Motor1Id: packet.MotorDetails1.Id, + Motor1Iq: packet.MotorDetails1.Iq, + Motor1LimitFlags: packet.MotorDetails1.LimitFlags, + Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, + Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, + Motor1Odometer: packet.MotorDetails1.Odometer, + Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, + Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, + Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, + Motor1SerialNumber: packet.MotorDetails1.SerialNumber, + Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, + Motor1Supply15v: packet.MotorDetails1.Supply15V, + Motor1Supply1v9: packet.MotorDetails1.Supply1V9, + Motor1Supply3v3: packet.MotorDetails1.Supply3V3, + Motor1TritiumId: packet.MotorDetails1.TritiumId, + Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, + Motor1Vd: packet.MotorDetails1.Vd, + Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, + Motor1Vq: packet.MotorDetails1.Vq, + ...packet.ProximitySensors, + ...packet.Telemetry, + } as Partial; + }); + + await this.telemetryPacketRepo.save(flattenedPackets); + } +} diff --git a/packages/server/src/database/DatabaseManager.ts b/packages/server/src/database/DatabaseManager.ts index d87b2171..361cc5a4 100644 --- a/packages/server/src/database/DatabaseManager.ts +++ b/packages/server/src/database/DatabaseManager.ts @@ -1,42 +1,54 @@ -import { DatabaseService } from "db"; +// +import { createLightweightApplicationLogger } from "@/utils/logger"; + +import { AppDataSource } from "@db/data-source"; +import { TelemetryService } from "@db/services/TelemetryService"; +import { ITelemetryData } from "@shared/helios-types"; + +const logger = createLightweightApplicationLogger("DatabaseManager.ts"); export class DatabaseManager { private static instance: DatabaseManager; - private databaseService: DatabaseService; + private telemetryService?: TelemetryService; + private isConnected = false; - private constructor() { - this.databaseService = new DatabaseService(); - } + private constructor() {} - public static getInstance(): DatabaseManager { + static getInstance(): DatabaseManager { if (!DatabaseManager.instance) { DatabaseManager.instance = new DatabaseManager(); } return DatabaseManager.instance; } - public async initialize(): Promise { - try { - await this.databaseService.initialize(); - console.log("DatabaseManager initialized successfully"); - } catch (error) { - console.error("Failed to initialize DatabaseManager:", error); - throw error; + async initialize() { + if (!this.isConnected) { + await AppDataSource.initialize(); + this.telemetryService = new TelemetryService(); + this.isConnected = true; + logger.info("Database initialized successfully"); } } - public async close(): Promise { - await this.databaseService.close(); - console.log("DatabaseManager closed"); + async insertTelemetryData(packet: ITelemetryData) { + if (!this.telemetryService) { + throw new Error("DatabaseManager not initialized"); + } + return this.telemetryService.insertTelemetryPacket(packet); } - // Helper method to check connection status - public get isConnected(): boolean { - return this.databaseService.isConnected; + async getTelemetryBetweenDates(start: Date, end: Date, rfid?: string) { + if (!this.telemetryService) { + throw new Error("DatabaseManager not initialized"); + } + return this.telemetryService.getTelemetryBetweenDates(start, end, rfid); } - // Get raw database service if needed - public get rawDatabaseService(): DatabaseService { - return this.databaseService; + async close() { + if (this.isConnected) { + await AppDataSource.destroy(); + this.isConnected = false; + logger.info("Database connection closed"); + } } } From 22a248438d2c1eb19dbe847f76ae9de660d742ca Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 10 Jan 2026 11:13:04 -0700 Subject: [PATCH 08/16] add new table to data-source --- packages/db/src/data-source.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts index ef50035f..e746873e 100644 --- a/packages/db/src/data-source.ts +++ b/packages/db/src/data-source.ts @@ -16,6 +16,7 @@ import { Telemetry } from "./entities/Telemetry.entity"; import { ProximitySensors } from "./entities/ProximitySensors.entity"; import { Driver } from "./entities/Driver.entity"; import { Lap } from "./entities/Lap.entity"; +import { TelemetryPacket } from "./entities/TelemetryPacket.entity"; dotenv.config({ path: ".db.env" }); @@ -37,6 +38,7 @@ export const AppDataSource = new DataSource({ ProximitySensors, Driver, Lap, + TelemetryPacket, ], // database host (if it's localhost or not) host: process.env.DB_HOST || "localhost", From 4f260370d244ae807d3f7dcbc0585fa1d00c4b8b Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 10 Jan 2026 12:58:07 -0700 Subject: [PATCH 09/16] doesnt work --- packages/db/src/data-source.ts | 11 ++ packages/db/src/index.ts | 2 - packages/db/src/services/DatabaseService.ts | 124 ++++++++++++++++++ .../BackendController/BackendController.ts | 10 +- .../server/src/database/DatabaseManager.ts | 23 +--- 5 files changed, 146 insertions(+), 24 deletions(-) diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts index e746873e..d58f8c29 100644 --- a/packages/db/src/data-source.ts +++ b/packages/db/src/data-source.ts @@ -54,3 +54,14 @@ export const AppDataSource = new DataSource({ type: "postgres", username: process.env.POSTGRES_USERNAME || "postgres", }); + +// export const AppDataSource = new DataSource({ +// entities: [TelemetryPacket, Driver, Lap], +// logging: process.env.NODE_ENV === "development", +// migrations: [__dirname + "/migrations/*.{js,ts}"], +// synchronize: process.env.NODE_ENV === "development", +// type: "postgres", +// url: +// process.env.DATABASE_URL ?? +// "postgres://tsdbadmin@y0b9h9zgeh.wy9edrssy5.tsdb.cloud.timescale.com:34124/tsdb?sslmode=require", +// }); diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index dffd6deb..421f9d6a 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,10 +1,8 @@ export { AppDataSource } from "./data-source"; export { BaseRepository } from "./repositories/BaseRepository"; -export { TelemetryService } from "./services/TelemetryService"; // entities // repositories -export { BaseRepository } from "./repositories/BaseRepository"; // interfaces export * from "./interfaces/repositories.interface"; diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts index f3c6dc33..e5949bde 100644 --- a/packages/db/src/services/DatabaseService.ts +++ b/packages/db/src/services/DatabaseService.ts @@ -5,9 +5,12 @@ import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export class DatabaseService { private dataSource: DataSource; + private static instance: DatabaseService; + private telemetryPacketRepo: Repository; constructor() { this.dataSource = AppDataSource; + this.telemetryPacketRepo = this.dataSource.getRepository(TelemetryPacket); } async initialize(): Promise { @@ -22,6 +25,127 @@ export class DatabaseService { } } + static getInstance(): DatabaseService { + if (!DatabaseService.instance) { + DatabaseService.instance = new DatabaseService(); + } + return DatabaseService.instance; + } + + /** + * Helper function to flatten ITelemetryData into TelemetryPacket format + */ + private flattenTelemetryData( + packet: ITelemetryData, + ): Partial { + const timestamp = new Date(packet.TimeStamp * 1000); + const Rfid = packet.Pi.Rfid; + + return { + // Metadata + RaceName: packet.Title, + Rfid, + // Primary keys + Timestamp: timestamp, + Title: packet.Title, + + // B3 data + ...packet.B3, + + // Battery data + ...packet.Battery, + + // Battery Faults (flatten Errors and Warnings) + ...packet.BatteryFaults.Errors, + ...packet.BatteryFaults.Warnings, + + // Contactor data + ...packet.Contactor, + + // KeyMotor data + ...packet.KeyMotor, + + // MBMS data + ...packet.MBMS, + + // MPPT data + ...packet.MPPT, + + // Motor 0 data (prefix all fields with Motor0) + Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, + Motor0BemfD: packet.MotorDetails0.BEMF_D, + Motor0BemfQ: packet.MotorDetails0.BEMF_Q, + Motor0BusCurrent: packet.MotorDetails0.BusCurrent, + Motor0BusVoltage: packet.MotorDetails0.BusVoltage, + Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, + Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, + Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, + Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, + Motor0Id: packet.MotorDetails0.Id, + Motor0Iq: packet.MotorDetails0.Iq, + Motor0LimitFlags: packet.MotorDetails0.LimitFlags, + Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, + Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, + Motor0Odometer: packet.MotorDetails0.Odometer, + Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, + Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, + Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, + Motor0SerialNumber: packet.MotorDetails0.SerialNumber, + Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, + Motor0Supply15v: packet.MotorDetails0.Supply15V, + Motor0Supply1v9: packet.MotorDetails0.Supply1V9, + Motor0Supply3v3: packet.MotorDetails0.Supply3V3, + Motor0TritiumId: packet.MotorDetails0.TritiumId, + Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, + Motor0Vd: packet.MotorDetails0.Vd, + Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, + Motor0Vq: packet.MotorDetails0.Vq, + + // Motor 1 data (prefix all fields with Motor1) + Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, + Motor1BemfD: packet.MotorDetails1.BEMF_D, + Motor1BemfQ: packet.MotorDetails1.BEMF_Q, + Motor1BusCurrent: packet.MotorDetails1.BusCurrent, + Motor1BusVoltage: packet.MotorDetails1.BusVoltage, + Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, + Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, + Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, + Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, + Motor1Id: packet.MotorDetails1.Id, + Motor1Iq: packet.MotorDetails1.Iq, + Motor1LimitFlags: packet.MotorDetails1.LimitFlags, + Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, + Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, + Motor1Odometer: packet.MotorDetails1.Odometer, + Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, + Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, + Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, + Motor1SerialNumber: packet.MotorDetails1.SerialNumber, + Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, + Motor1Supply15v: packet.MotorDetails1.Supply15V, + Motor1Supply1v9: packet.MotorDetails1.Supply1V9, + Motor1Supply3v3: packet.MotorDetails1.Supply3V3, + Motor1TritiumId: packet.MotorDetails1.TritiumId, + Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, + Motor1Vd: packet.MotorDetails1.Vd, + Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, + Motor1Vq: packet.MotorDetails1.Vq, + + // ProximitySensors data + ...packet.ProximitySensors, + + // Telemetry data (GPS + MPU) + ...packet.Telemetry, + }; + } + + async insertPacketData(message: ITelemetryData): Promise { + if (this.dataSource.isInitialized) { + const flattenedData = this.flattenTelemetryData(message); + await this.telemetryPacketRepo.save(flattenedData); + } + } + async close(): Promise { if (this.dataSource.isInitialized) { await this.dataSource.destroy(); diff --git a/packages/server/src/controllers/BackendController/BackendController.ts b/packages/server/src/controllers/BackendController/BackendController.ts index 9f4e4bb2..293de1a7 100644 --- a/packages/server/src/controllers/BackendController/BackendController.ts +++ b/packages/server/src/controllers/BackendController/BackendController.ts @@ -1,3 +1,4 @@ +import { DatabaseService } from "db"; import type { IncomingMessage, Server, ServerResponse } from "http"; import type { BackendControllerTypes } from "@/controllers/BackendController/BackendController.types"; @@ -7,8 +8,8 @@ import DynamoDB from "@/datasources/DynamoDB/DynamoDB"; import { SocketIO } from "@/datasources/SocketIO/SocketIO"; import { SolarMQTTClient } from "@/datasources/SolarMQTTClient/SolarMQTTClient"; import { options } from "@/datasources/SolarMQTTClient/SolarMQTTClient.types"; -import { DatabaseManager } from "@/database/DatabaseManager"; +import { DatabaseManager } from "@/database/DatabaseManager"; import { logger } from "@/index"; import { type ITelemetryData } from "@shared/helios-types"; @@ -20,6 +21,7 @@ export class BackendController implements BackendControllerTypes { public mqtt: SolarMQTTClient; public databaseManager: DatabaseManager; public carLatency: number; + public databaseService: DatabaseService; constructor( httpsServer: Server, ) { @@ -31,12 +33,14 @@ export class BackendController implements BackendControllerTypes { this.establishCarPinging(); this.carLatency = 0; this.initializeDatabase(); + this.databaseService = DatabaseService.getInstance(); // this.handleCarLatency(); } private async initializeDatabase() { try { await this.databaseManager.initialize(); + // await this.databaseService.initialize(); logger.info("Database connection established successfully"); } catch (error) { logger.error("Failed to initialize database:", error); @@ -68,6 +72,9 @@ export class BackendController implements BackendControllerTypes { // Handle the packet in the lap controller await this.lapController.handlePacket(message); + + // insert to timescaledb + await this.databaseService.insertPacketData(message); } public handleCarDisconnect() { @@ -84,6 +91,7 @@ export class BackendController implements BackendControllerTypes { public async cleanup() { try { await this.databaseManager.close(); + // await this.databaseService.close(); logger.info("Database connection closed successfully"); } catch (error) { logger.error("Error closing database connection:", error); diff --git a/packages/server/src/database/DatabaseManager.ts b/packages/server/src/database/DatabaseManager.ts index 361cc5a4..38e735f2 100644 --- a/packages/server/src/database/DatabaseManager.ts +++ b/packages/server/src/database/DatabaseManager.ts @@ -1,15 +1,11 @@ -// -import { createLightweightApplicationLogger } from "@/utils/logger"; +import { AppDataSource } from "db"; -import { AppDataSource } from "@db/data-source"; -import { TelemetryService } from "@db/services/TelemetryService"; -import { ITelemetryData } from "@shared/helios-types"; +import { createLightweightApplicationLogger } from "@/utils/logger"; const logger = createLightweightApplicationLogger("DatabaseManager.ts"); export class DatabaseManager { private static instance: DatabaseManager; - private telemetryService?: TelemetryService; private isConnected = false; private constructor() {} @@ -24,26 +20,11 @@ export class DatabaseManager { async initialize() { if (!this.isConnected) { await AppDataSource.initialize(); - this.telemetryService = new TelemetryService(); this.isConnected = true; logger.info("Database initialized successfully"); } } - async insertTelemetryData(packet: ITelemetryData) { - if (!this.telemetryService) { - throw new Error("DatabaseManager not initialized"); - } - return this.telemetryService.insertTelemetryPacket(packet); - } - - async getTelemetryBetweenDates(start: Date, end: Date, rfid?: string) { - if (!this.telemetryService) { - throw new Error("DatabaseManager not initialized"); - } - return this.telemetryService.getTelemetryBetweenDates(start, end, rfid); - } - async close() { if (this.isConnected) { await AppDataSource.destroy(); From 83a9f30a528520c230896d77140f541c991951ed Mon Sep 17 00:00:00 2001 From: burtonjong Date: Sat, 10 Jan 2026 14:42:48 -0700 Subject: [PATCH 10/16] fix --- packages/db/src/data-source.ts | 54 +--- packages/db/src/entities/Driver.entity.ts | 14 +- packages/db/src/entities/Lap.entity.ts | 13 +- .../db/src/entities/TelemetryPacket.entity.ts | 18 +- packages/db/src/index.ts | 3 - packages/db/src/services/DatabaseService.ts | 271 +----------------- .../BackendController/BackendController.ts | 18 +- .../server/src/database/DatabaseManager.ts | 35 --- 8 files changed, 33 insertions(+), 393 deletions(-) delete mode 100644 packages/server/src/database/DatabaseManager.ts diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts index d58f8c29..29437e80 100644 --- a/packages/db/src/data-source.ts +++ b/packages/db/src/data-source.ts @@ -3,17 +3,6 @@ import { DataSource } from "typeorm"; import dotenv from "dotenv"; // import your entities/tables here -import { TelemetryMetadata } from "./entities/TelemetryMetadata.entity"; -import { Battery } from "./entities/Battery.entity"; -import { BatteryFaults } from "./entities/BatteryFaults.entity"; -import { MotorDetails } from "./entities/MotorDetails.entity"; -import { KeyMotor } from "./entities/KeyMotor.entity"; -import { MPPT } from "./entities/MPPT.entity"; -import { Contactor } from "./entities/Contactor.entity"; -import { B3 } from "./entities/B3.entity"; -import { MBMS } from "./entities/MBMS.entity"; -import { Telemetry } from "./entities/Telemetry.entity"; -import { ProximitySensors } from "./entities/ProximitySensors.entity"; import { Driver } from "./entities/Driver.entity"; import { Lap } from "./entities/Lap.entity"; import { TelemetryPacket } from "./entities/TelemetryPacket.entity"; @@ -21,47 +10,10 @@ import { TelemetryPacket } from "./entities/TelemetryPacket.entity"; dotenv.config({ path: ".db.env" }); export const AppDataSource = new DataSource({ - // database name - database: process.env.DB_NAME || "postgres", - // entity schemas, whenever you make a table you have to add it here - entities: [ - TelemetryMetadata, - Battery, - BatteryFaults, - MotorDetails, - KeyMotor, - MPPT, - Contactor, - B3, - MBMS, - Telemetry, - ProximitySensors, - Driver, - Lap, - TelemetryPacket, - ], - // database host (if it's localhost or not) - host: process.env.DB_HOST || "localhost", - // logging: https://typeorm.io/docs/advanced-topics/logging - // you can enable logging only in development to avoid performance issues - // there are also different level of logging you can set like "all", "query", etc. + entities: [TelemetryPacket, Driver, Lap], logging: process.env.NODE_ENV === "development", - // the migrations directory (not sure if this works yet) migrations: [__dirname + "/migrations/*.{js,ts}"], - password: process.env.POSTGRES_PASSWORD || "postgres", - port: parseInt(process.env.DB_PORT || "5432"), - synchronize: process.env.NODE_ENV === "development", // Only in development + synchronize: process.env.NODE_ENV === "development", type: "postgres", - username: process.env.POSTGRES_USERNAME || "postgres", + url: "postgres://tsdbadmin:l61gksd8ona24d62@l6yyro58r7.wy9edrssy5.tsdb.cloud.timescale.com:38572/tsdb", }); - -// export const AppDataSource = new DataSource({ -// entities: [TelemetryPacket, Driver, Lap], -// logging: process.env.NODE_ENV === "development", -// migrations: [__dirname + "/migrations/*.{js,ts}"], -// synchronize: process.env.NODE_ENV === "development", -// type: "postgres", -// url: -// process.env.DATABASE_URL ?? -// "postgres://tsdbadmin@y0b9h9zgeh.wy9edrssy5.tsdb.cloud.timescale.com:34124/tsdb?sslmode=require", -// }); diff --git a/packages/db/src/entities/Driver.entity.ts b/packages/db/src/entities/Driver.entity.ts index 8c7cf28a..1081234b 100644 --- a/packages/db/src/entities/Driver.entity.ts +++ b/packages/db/src/entities/Driver.entity.ts @@ -4,13 +4,10 @@ import { PrimaryColumn, CreateDateColumn, UpdateDateColumn, - Index, } from "typeorm"; -// use this table!! - /** - * Driver table (not a hypertable) + * Driver table * Stores driver information linked by RFID */ @Entity("driver") @@ -21,10 +18,9 @@ export class Driver { @Column({ type: "text" }) Name!: string; - // might not need these two but good practice - // @CreateDateColumn() - // CreatedAt!: Date; + @CreateDateColumn() + CreatedAt!: Date; - // @UpdateDateColumn() - // UpdatedAt!: Date; + @UpdateDateColumn() + UpdatedAt!: Date; } diff --git a/packages/db/src/entities/Lap.entity.ts b/packages/db/src/entities/Lap.entity.ts index 8bc68eff..58f16ede 100644 --- a/packages/db/src/entities/Lap.entity.ts +++ b/packages/db/src/entities/Lap.entity.ts @@ -1,21 +1,12 @@ import { Entity, Column, Index, PrimaryGeneratedColumn } from "typeorm"; -import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; +import { TimeColumn } from "@timescaledb/typeorm"; /** * Lap data hypertable * Stores computed lap metrics for each lap */ @Entity("lap") -@Hypertable({ - timeColumnName: "timestamp", - chunkTimeInterval: "1 month", - compression: { - compress: true, - compress_segmentby: "rfid", - compress_orderby: "timestamp DESC", - }, -}) -@Index(["rfid", "timestamp"]) +@Index(["Rfid", "Timestamp"]) export class Lap { @PrimaryGeneratedColumn("uuid") Id!: string; diff --git a/packages/db/src/entities/TelemetryPacket.entity.ts b/packages/db/src/entities/TelemetryPacket.entity.ts index 82b354bc..481baf2c 100644 --- a/packages/db/src/entities/TelemetryPacket.entity.ts +++ b/packages/db/src/entities/TelemetryPacket.entity.ts @@ -1,26 +1,22 @@ import { Entity, Column, Index, PrimaryColumn } from "typeorm"; import { Hypertable, TimeColumn } from "@timescaledb/typeorm"; - -// use this table !! - /** - * Flattened Telemetry hypertable * Combines all sensor data into a single wide table for simplified insertions and queries - * Replaces the previous normalized structure with separate tables for each car component */ @Entity("telemetry_packet") @Hypertable({ - timeColumnName: "timestamp", - chunkTimeInterval: "1 month", compression: { compress: true, - compress_segmentby: "rfid, race_name", - compress_orderby: "timestamp DESC", + compress_segmentby: "Rfid", + compress_orderby: "Timestamp", + policy: { + schedule_interval: "7 days", + }, }, }) -@Index(["rfid", "timestamp"]) -@Index(["race_name", "timestamp"]) +@Index(["Rfid", "Timestamp"]) +@Index(["RaceName", "Timestamp"]) export class TelemetryPacket { @TimeColumn() @PrimaryColumn({ type: "timestamptz" }) diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 421f9d6a..8c4fbe5d 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,8 +1,5 @@ export { AppDataSource } from "./data-source"; export { BaseRepository } from "./repositories/BaseRepository"; -// entities - -// repositories // interfaces export * from "./interfaces/repositories.interface"; diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts index e5949bde..2e9cf072 100644 --- a/packages/db/src/services/DatabaseService.ts +++ b/packages/db/src/services/DatabaseService.ts @@ -4,24 +4,18 @@ import { ITelemetryData } from "@shared/helios-types"; import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; export class DatabaseService { - private dataSource: DataSource; + private isConnected = false; private static instance: DatabaseService; private telemetryPacketRepo: Repository; constructor() { - this.dataSource = AppDataSource; - this.telemetryPacketRepo = this.dataSource.getRepository(TelemetryPacket); + this.telemetryPacketRepo = AppDataSource.getRepository(TelemetryPacket); } - async initialize(): Promise { - try { - if (!this.dataSource.isInitialized) { - await this.dataSource.initialize(); - console.log("Database connection initialized successfully"); - } - } catch (error) { - console.error("Error during database initialization:", error); - throw error; + async initialize() { + if (!this.isConnected) { + await AppDataSource.initialize(); + this.isConnected = true; } } @@ -140,262 +134,17 @@ export class DatabaseService { } async insertPacketData(message: ITelemetryData): Promise { - if (this.dataSource.isInitialized) { + if (this.isConnected) { + console.log("Inserting packet data into database"); const flattenedData = this.flattenTelemetryData(message); await this.telemetryPacketRepo.save(flattenedData); } } async close(): Promise { - if (this.dataSource.isInitialized) { - await this.dataSource.destroy(); + if (this.isConnected) { + await AppDataSource.destroy(); console.log("Database connection closed"); } } - - // Helper method to check if database is connected - get isConnected(): boolean { - return this.dataSource.isInitialized; - } - - // Get the raw data source if needed for advanced operations - get rawDataSource(): DataSource { - return this.dataSource; - } -} - -export class TelemetryService { - private telemetryPacketRepo: Repository; - - constructor() { - this.telemetryPacketRepo = AppDataSource.getRepository(TelemetryPacket); - } - - /** - * Insert complete telemetry packet into flattened table - * All sensor data is stored in a single wide table for simplified insertions - */ - async insertTelemetryPacket(packet: ITelemetryData): Promise { - const timestamp = new Date(packet.TimeStamp * 1000); - const Rfid = packet.Pi.Rfid; - - // Flatten all nested data structures into a single object - // Motor details are prefixed with Motor0_ and Motor1_ - const flattenedData: Partial = { - // Metadata - RaceName: packet.Title, - Rfid, - // Primary keys - Timestamp: timestamp, - Title: packet.Title, - - // B3 data - ...packet.B3, - - // Battery data - ...packet.Battery, - - // Battery Faults (flatten Errors and Warnings) - ...packet.BatteryFaults.Errors, - ...packet.BatteryFaults.Warnings, - - // Contactor data - ...packet.Contactor, - - // KeyMotor data - ...packet.KeyMotor, - - // MBMS data - ...packet.MBMS, - - // MPPT data - ...packet.MPPT, - - // Motor 0 data (prefix all fields with Motor0) - Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, - Motor0BemfD: packet.MotorDetails0.BEMF_D, - Motor0BemfQ: packet.MotorDetails0.BEMF_Q, - Motor0BusCurrent: packet.MotorDetails0.BusCurrent, - Motor0BusVoltage: packet.MotorDetails0.BusVoltage, - Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, - Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, - Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, - Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, - Motor0Id: packet.MotorDetails0.Id, - Motor0Iq: packet.MotorDetails0.Iq, - Motor0LimitFlags: packet.MotorDetails0.LimitFlags, - Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, - Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, - Motor0Odometer: packet.MotorDetails0.Odometer, - Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, - Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, - Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, - Motor0SerialNumber: packet.MotorDetails0.SerialNumber, - Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, - Motor0Supply15v: packet.MotorDetails0.Supply15V, - Motor0Supply1v9: packet.MotorDetails0.Supply1V9, - Motor0Supply3v3: packet.MotorDetails0.Supply3V3, - Motor0TritiumId: packet.MotorDetails0.TritiumId, - Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, - Motor0Vd: packet.MotorDetails0.Vd, - Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, - Motor0Vq: packet.MotorDetails0.Vq, - - // Motor 1 data (prefix all fields with Motor1) - Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, - Motor1BemfD: packet.MotorDetails1.BEMF_D, - Motor1BemfQ: packet.MotorDetails1.BEMF_Q, - Motor1BusCurrent: packet.MotorDetails1.BusCurrent, - Motor1BusVoltage: packet.MotorDetails1.BusVoltage, - Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, - Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, - Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, - Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, - Motor1Id: packet.MotorDetails1.Id, - Motor1Iq: packet.MotorDetails1.Iq, - Motor1LimitFlags: packet.MotorDetails1.LimitFlags, - Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, - Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, - Motor1Odometer: packet.MotorDetails1.Odometer, - Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, - Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, - Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, - Motor1SerialNumber: packet.MotorDetails1.SerialNumber, - Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, - Motor1Supply15v: packet.MotorDetails1.Supply15V, - Motor1Supply1v9: packet.MotorDetails1.Supply1V9, - Motor1Supply3v3: packet.MotorDetails1.Supply3V3, - Motor1TritiumId: packet.MotorDetails1.TritiumId, - Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, - Motor1Vd: packet.MotorDetails1.Vd, - Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, - Motor1Vq: packet.MotorDetails1.Vq, - - // ProximitySensors data - ...packet.ProximitySensors, - - // Telemetry data (GPS + MPU) - ...packet.Telemetry, - }; - - // Single insert into the flattened table - await this.telemetryPacketRepo.save(flattenedData); - } - - /** - * Query telemetry data between dates from the flattened table - */ - async getTelemetryBetweenDates( - startTime: Date, - endTime: Date, - rfid?: string, - ): Promise { - const queryBuilder = this.telemetryPacketRepo - .createQueryBuilder("t") - .where("t.Timestamp BETWEEN :start AND :end", { - end: endTime, - start: startTime, - }); - - if (rfid) { - queryBuilder.andWhere("t.Rfid = :rfid", { rfid }); - } - - return queryBuilder.getMany(); - } - - /** - * Get latest telemetry packet for a specific RFID - */ - async getLatestTelemetry(rfid: string): Promise { - return this.telemetryPacketRepo.findOne({ - order: { Timestamp: "DESC" }, - where: { Rfid: rfid }, - }); - } - - /** - * Batch insert multiple telemetry packets - */ - async insertTelemetryPacketBatch(packets: ITelemetryData[]): Promise { - const flattenedPackets = packets.map((packet) => { - const timestamp = new Date(packet.TimeStamp * 1000); - const Rfid = packet.Pi.Rfid; - - return { - RaceName: packet.Title, - Rfid, - Timestamp: timestamp, - Title: packet.Title, - ...packet.B3, - ...packet.Battery, - ...packet.BatteryFaults.Errors, - ...packet.BatteryFaults.Warnings, - ...packet.Contactor, - ...packet.KeyMotor, - ...packet.MBMS, - ...packet.MPPT, - Motor0ActiveMotor: packet.MotorDetails0.ActiveMotor, - Motor0BemfD: packet.MotorDetails0.BEMF_D, - Motor0BemfQ: packet.MotorDetails0.BEMF_Q, - Motor0BusCurrent: packet.MotorDetails0.BusCurrent, - Motor0BusVoltage: packet.MotorDetails0.BusVoltage, - Motor0DcBusAh: packet.MotorDetails0.DC_Bus_Ah, - Motor0DspBoardTemperature: packet.MotorDetails0.DspBoardTemperature, - Motor0ErrorFlags: packet.MotorDetails0.ErrorFlags, - Motor0HeatsinkTemperature: packet.MotorDetails0.HeatsinkTemperature, - Motor0Id: packet.MotorDetails0.Id, - Motor0Iq: packet.MotorDetails0.Iq, - Motor0LimitFlags: packet.MotorDetails0.LimitFlags, - Motor0MotorTemperature: packet.MotorDetails0.MotorTemperature, - Motor0MotorVelocity: packet.MotorDetails0.MotorVelocity, - Motor0Odometer: packet.MotorDetails0.Odometer, - Motor0PhaseCurrentB: packet.MotorDetails0.PhaseCurrentB, - Motor0PhaseCurrentC: packet.MotorDetails0.PhaseCurrentC, - Motor0RxErrorCount: packet.MotorDetails0.RxErrorCount, - Motor0SerialNumber: packet.MotorDetails0.SerialNumber, - Motor0SlipSpeed: packet.MotorDetails0.SlipSpeed, - Motor0Supply15v: packet.MotorDetails0.Supply15V, - Motor0Supply1v9: packet.MotorDetails0.Supply1V9, - Motor0Supply3v3: packet.MotorDetails0.Supply3V3, - Motor0TritiumId: packet.MotorDetails0.TritiumId, - Motor0TxErrorCount: packet.MotorDetails0.TxErrorCount, - Motor0Vd: packet.MotorDetails0.Vd, - Motor0VehicleVelocity: packet.MotorDetails0.VehicleVelocity, - Motor0Vq: packet.MotorDetails0.Vq, - Motor1ActiveMotor: packet.MotorDetails1.ActiveMotor, - Motor1BemfD: packet.MotorDetails1.BEMF_D, - Motor1BemfQ: packet.MotorDetails1.BEMF_Q, - Motor1BusCurrent: packet.MotorDetails1.BusCurrent, - Motor1BusVoltage: packet.MotorDetails1.BusVoltage, - Motor1DcBusAh: packet.MotorDetails1.DC_Bus_Ah, - Motor1DspBoardTemperature: packet.MotorDetails1.DspBoardTemperature, - Motor1ErrorFlags: packet.MotorDetails1.ErrorFlags, - Motor1HeatsinkTemperature: packet.MotorDetails1.HeatsinkTemperature, - Motor1Id: packet.MotorDetails1.Id, - Motor1Iq: packet.MotorDetails1.Iq, - Motor1LimitFlags: packet.MotorDetails1.LimitFlags, - Motor1MotorTemperature: packet.MotorDetails1.MotorTemperature, - Motor1MotorVelocity: packet.MotorDetails1.MotorVelocity, - Motor1Odometer: packet.MotorDetails1.Odometer, - Motor1PhaseCurrentB: packet.MotorDetails1.PhaseCurrentB, - Motor1PhaseCurrentC: packet.MotorDetails1.PhaseCurrentC, - Motor1RxErrorCount: packet.MotorDetails1.RxErrorCount, - Motor1SerialNumber: packet.MotorDetails1.SerialNumber, - Motor1SlipSpeed: packet.MotorDetails1.SlipSpeed, - Motor1Supply15v: packet.MotorDetails1.Supply15V, - Motor1Supply1v9: packet.MotorDetails1.Supply1V9, - Motor1Supply3v3: packet.MotorDetails1.Supply3V3, - Motor1TritiumId: packet.MotorDetails1.TritiumId, - Motor1TxErrorCount: packet.MotorDetails1.TxErrorCount, - Motor1Vd: packet.MotorDetails1.Vd, - Motor1VehicleVelocity: packet.MotorDetails1.VehicleVelocity, - Motor1Vq: packet.MotorDetails1.Vq, - ...packet.ProximitySensors, - ...packet.Telemetry, - } as Partial; - }); - - await this.telemetryPacketRepo.save(flattenedPackets); - } } diff --git a/packages/server/src/controllers/BackendController/BackendController.ts b/packages/server/src/controllers/BackendController/BackendController.ts index 293de1a7..4f5834cc 100644 --- a/packages/server/src/controllers/BackendController/BackendController.ts +++ b/packages/server/src/controllers/BackendController/BackendController.ts @@ -9,7 +9,6 @@ import { SocketIO } from "@/datasources/SocketIO/SocketIO"; import { SolarMQTTClient } from "@/datasources/SolarMQTTClient/SolarMQTTClient"; import { options } from "@/datasources/SolarMQTTClient/SolarMQTTClient.types"; -import { DatabaseManager } from "@/database/DatabaseManager"; import { logger } from "@/index"; import { type ITelemetryData } from "@shared/helios-types"; @@ -19,9 +18,8 @@ export class BackendController implements BackendControllerTypes { public socketIO: SocketIO; public lapController: LapController; public mqtt: SolarMQTTClient; - public databaseManager: DatabaseManager; - public carLatency: number; public databaseService: DatabaseService; + public carLatency: number; constructor( httpsServer: Server, ) { @@ -29,18 +27,16 @@ export class BackendController implements BackendControllerTypes { this.socketIO = new SocketIO(httpsServer, this); this.mqtt = new SolarMQTTClient(options, this); this.lapController = new LapController(this); - this.databaseManager = DatabaseManager.getInstance(); + this.databaseService = DatabaseService.getInstance(); this.establishCarPinging(); this.carLatency = 0; this.initializeDatabase(); - this.databaseService = DatabaseService.getInstance(); // this.handleCarLatency(); } private async initializeDatabase() { try { - await this.databaseManager.initialize(); - // await this.databaseService.initialize(); + await this.databaseService.initialize(); logger.info("Database connection established successfully"); } catch (error) { logger.error("Failed to initialize database:", error); @@ -67,14 +63,13 @@ export class BackendController implements BackendControllerTypes { // Insert the packet into the database this.dynamoDB.insertPacketData(message); + this.databaseService.insertPacketData(message); + // Broadcast the packet to the frontend this.socketIO.broadcastPacket(message); // Handle the packet in the lap controller await this.lapController.handlePacket(message); - - // insert to timescaledb - await this.databaseService.insertPacketData(message); } public handleCarDisconnect() { @@ -90,8 +85,7 @@ export class BackendController implements BackendControllerTypes { public async cleanup() { try { - await this.databaseManager.close(); - // await this.databaseService.close(); + await this.databaseService.close(); logger.info("Database connection closed successfully"); } catch (error) { logger.error("Error closing database connection:", error); diff --git a/packages/server/src/database/DatabaseManager.ts b/packages/server/src/database/DatabaseManager.ts deleted file mode 100644 index 38e735f2..00000000 --- a/packages/server/src/database/DatabaseManager.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { AppDataSource } from "db"; - -import { createLightweightApplicationLogger } from "@/utils/logger"; - -const logger = createLightweightApplicationLogger("DatabaseManager.ts"); - -export class DatabaseManager { - private static instance: DatabaseManager; - private isConnected = false; - - private constructor() {} - - static getInstance(): DatabaseManager { - if (!DatabaseManager.instance) { - DatabaseManager.instance = new DatabaseManager(); - } - return DatabaseManager.instance; - } - - async initialize() { - if (!this.isConnected) { - await AppDataSource.initialize(); - this.isConnected = true; - logger.info("Database initialized successfully"); - } - } - - async close() { - if (this.isConnected) { - await AppDataSource.destroy(); - this.isConnected = false; - logger.info("Database connection closed"); - } - } -} From 9e80eadc7ceb1b4f4c2ae237257ed973cede8d79 Mon Sep 17 00:00:00 2001 From: sim Date: Sat, 10 Jan 2026 14:57:47 -0700 Subject: [PATCH 11/16] make env for dburl --- packages/db/src/data-source.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db/src/data-source.ts b/packages/db/src/data-source.ts index 29437e80..e1135df2 100644 --- a/packages/db/src/data-source.ts +++ b/packages/db/src/data-source.ts @@ -15,5 +15,5 @@ export const AppDataSource = new DataSource({ migrations: [__dirname + "/migrations/*.{js,ts}"], synchronize: process.env.NODE_ENV === "development", type: "postgres", - url: "postgres://tsdbadmin:l61gksd8ona24d62@l6yyro58r7.wy9edrssy5.tsdb.cloud.timescale.com:38572/tsdb", + url: process.env.DATABASE_URL, }); From e51a7ef8150e6cde6134234b7321777ba4c25c84 Mon Sep 17 00:00:00 2001 From: simar kandola Date: Sat, 10 Jan 2026 15:04:18 -0700 Subject: [PATCH 12/16] Fix typo in ProximitySensors entity property name --- packages/db/src/entities/ProximitySensors.entity.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/db/src/entities/ProximitySensors.entity.ts b/packages/db/src/entities/ProximitySensors.entity.ts index 26b62b4e..8d5078b0 100644 --- a/packages/db/src/entities/ProximitySensors.entity.ts +++ b/packages/db/src/entities/ProximitySensors.entity.ts @@ -31,8 +31,9 @@ export class ProximitySensors { ProximitySensor2!: number; @Column({ type: "float" }) - RroximitySensor3!: number; + ProximitySensor3!: number; @Column({ type: "float" }) ProximitySensor4!: number; } + From 8400abd64691704903e3a6091789aac0d11a0887 Mon Sep 17 00:00:00 2001 From: burtonjong Date: Sat, 10 Jan 2026 15:31:25 -0700 Subject: [PATCH 13/16] a --- packages/server/.env.example | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/.env.example b/packages/server/.env.example index f7066f47..8fd107bd 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -12,4 +12,6 @@ MQTT_USERNAME='' MQTT_PASSWORD='' -GPS_TABLE_NAME='' \ No newline at end of file +GPS_TABLE_NAME='' + +DATABASE_URL='' \ No newline at end of file From 4cc03aa16afe8473e97a3f86bc8baa362058cc22 Mon Sep 17 00:00:00 2001 From: burtonjong Date: Sat, 24 Jan 2026 11:20:19 -0700 Subject: [PATCH 14/16] fix: fix build --- packages/db/src/entities/ProximitySensors.entity.ts | 2 +- packages/db/src/entities/TelemetryPacket.entity.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/db/src/entities/ProximitySensors.entity.ts b/packages/db/src/entities/ProximitySensors.entity.ts index 26b62b4e..3a9c2ce7 100644 --- a/packages/db/src/entities/ProximitySensors.entity.ts +++ b/packages/db/src/entities/ProximitySensors.entity.ts @@ -31,7 +31,7 @@ export class ProximitySensors { ProximitySensor2!: number; @Column({ type: "float" }) - RroximitySensor3!: number; + ProximitySensor3!: number; @Column({ type: "float" }) ProximitySensor4!: number; diff --git a/packages/db/src/entities/TelemetryPacket.entity.ts b/packages/db/src/entities/TelemetryPacket.entity.ts index 481baf2c..5503fa64 100644 --- a/packages/db/src/entities/TelemetryPacket.entity.ts +++ b/packages/db/src/entities/TelemetryPacket.entity.ts @@ -955,7 +955,7 @@ export class TelemetryPacket { ProximitySensor2?: number; @Column({ type: "float", nullable: true }) - RroximitySensor3?: number; + ProximitySensor3?: number; @Column({ type: "float", nullable: true }) ProximitySensor4?: number; From d0ccc49f761afcbf03ed0181e7fb10775ccb99a6 Mon Sep 17 00:00:00 2001 From: burtonjong Date: Sat, 24 Jan 2026 11:21:26 -0700 Subject: [PATCH 15/16] fix: unused import --- packages/db/src/services/DatabaseService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db/src/services/DatabaseService.ts b/packages/db/src/services/DatabaseService.ts index 2e9cf072..e01dc827 100644 --- a/packages/db/src/services/DatabaseService.ts +++ b/packages/db/src/services/DatabaseService.ts @@ -1,4 +1,4 @@ -import { DataSource, Repository } from "typeorm"; +import { Repository } from "typeorm"; import { AppDataSource } from "../data-source"; import { ITelemetryData } from "@shared/helios-types"; import { TelemetryPacket } from "../entities/TelemetryPacket.entity"; From 4a6704debe201f8a49ae49393af81b9e5d13a008 Mon Sep 17 00:00:00 2001 From: burtonjong Date: Sat, 24 Jan 2026 11:22:26 -0700 Subject: [PATCH 16/16] fix: unused import --- .../src/controllers/BackendController/BackendController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/controllers/BackendController/BackendController.ts b/packages/server/src/controllers/BackendController/BackendController.ts index 89544e70..4f5834cc 100644 --- a/packages/server/src/controllers/BackendController/BackendController.ts +++ b/packages/server/src/controllers/BackendController/BackendController.ts @@ -9,7 +9,6 @@ import { SocketIO } from "@/datasources/SocketIO/SocketIO"; import { SolarMQTTClient } from "@/datasources/SolarMQTTClient/SolarMQTTClient"; import { options } from "@/datasources/SolarMQTTClient/SolarMQTTClient.types"; -import { DatabaseManager } from "@/database/DatabaseManager"; import { logger } from "@/index"; import { type ITelemetryData } from "@shared/helios-types";