751 lines
33 KiB
C++
751 lines
33 KiB
C++
#include "SQLite.h"
|
||
#include <string>
|
||
#include <vector>
|
||
#include <chrono>
|
||
#include <cstdio>
|
||
#include <cstring>
|
||
#include "../logc/loguru.hpp"
|
||
|
||
namespace {
|
||
const int SCHEMA_VERSION = 2;
|
||
}
|
||
|
||
//====================================================================
|
||
// 列绑定宏:字段名 + SQL 类型 + 绑定器(无捕获函数指针,不抛异常、不分配)
|
||
//====================================================================
|
||
#define COL(Struct, member) \
|
||
SQLite::Column{ #member, "INTEGER", [](sqlite3_stmt* s, int& i, const void* o){ \
|
||
sqlite3_bind_int64(s, i++, static_cast<sqlite3_int64>(static_cast<const Struct*>(o)->member)); } }
|
||
|
||
#define COL_AS(Struct, member, colname) \
|
||
SQLite::Column{ colname, "INTEGER", [](sqlite3_stmt* s, int& i, const void* o){ \
|
||
sqlite3_bind_int64(s, i++, static_cast<sqlite3_int64>(static_cast<const Struct*>(o)->member)); } }
|
||
|
||
#define COL_ARR(Struct, arr, index, colname) \
|
||
SQLite::Column{ colname, "INTEGER", [](sqlite3_stmt* s, int& i, const void* o){ \
|
||
sqlite3_bind_int64(s, i++, static_cast<sqlite3_int64>(static_cast<const Struct*>(o)->arr[index])); } }
|
||
|
||
//====================================================================
|
||
// 表描述注册表:DDL(建表) 与 DML(插入) 共用的唯一数据源。
|
||
// 每张协议结构表的数据列只在这里定义一次;id/timestamp 元列由建表逻辑自动追加。
|
||
//====================================================================
|
||
const std::vector<SQLite::TableDesc>& SQLite::allTables()
|
||
{
|
||
static const std::vector<TableDesc> tables = {
|
||
{ "ccuState", {
|
||
COL(ccuState, fc_mode),
|
||
COL(ccuState, fc_status),
|
||
COL(ccuState, fc_fault_level),
|
||
COL(ccuState, fault_level_1),
|
||
COL(ccuState, fault_level_2),
|
||
COL(ccuState, fault_level_3),
|
||
COL(ccuState, fault_level_4),
|
||
COL(ccuState, total_generation_time),
|
||
COL(ccuState, output_power_limit),
|
||
COL(ccuState, total_generation_power),
|
||
COL(ccuState, hydrogen_capacity),
|
||
COL(ccuState, liquid_oxygen_capacity),
|
||
COL(ccuState, fc1_min_cell_voltage),
|
||
COL(ccuState, fc1_min_cell_pos),
|
||
COL(ccuState, fc1_avg_cell_voltage),
|
||
COL(ccuState, fc2_min_cell_voltage),
|
||
COL(ccuState, fc2_min_cell_pos),
|
||
COL(ccuState, fc2_avg_cell_voltage),
|
||
COL(ccuState, palladium_temp),
|
||
COL(ccuState, buffer_tank_pressure),
|
||
COL(ccuState, flue_total_emission),
|
||
COL(ccuState, flue_pressure),
|
||
COL(ccuState, reactor_pressure),
|
||
COL(ccuState, electric_valve_open),
|
||
COL(ccuState, main_pipe_pressure),
|
||
COL(ccuState, aux_pipe_pressure),
|
||
COL(ccuState, dcdc1_in_voltage),
|
||
COL(ccuState, dcdc1_in_current),
|
||
COL(ccuState, dcdc2_in_voltage),
|
||
COL(ccuState, dcdc2_in_current),
|
||
COL(ccuState, dcdc_out_voltage),
|
||
COL(ccuState, dcdc_out_current),
|
||
COL(ccuState, dcdc_control_voltage),
|
||
COL(ccuState, dcdc_aux_out_voltage),
|
||
COL(ccuState, methanol_total_usage),
|
||
COL(ccuState, methanol_solution_feed),
|
||
COL(ccuState, oxygen_water_tank_level),
|
||
COL(ccuState, hydrogen_water_tank_level),
|
||
COL(ccuState, ballast_water_tank_level),
|
||
COL(ccuState, exhaust_in_pressure),
|
||
COL(ccuState, exhaust_out_pressure),
|
||
COL(ccuState, exhaust_run_freq),
|
||
COL(ccuState, exhaust_in_temp),
|
||
COL(ccuState, exhaust_out_temp),
|
||
COL(ccuState, exhaust_in_water_pressure),
|
||
COL(ccuState, exhaust_out_water_pressure),
|
||
COL(ccuState, tank_lo2_pressure),
|
||
COL(ccuState, tank_co2_pressure),
|
||
COL(ccuState, tank_lo2_level),
|
||
COL(ccuState, alloy_h2_flow),
|
||
COL(ccuState, fc_h2_flow),
|
||
COL(ccuState, fc_o2_flow),
|
||
COL(ccuState, emergency_float_depth),
|
||
COL(ccuState, emergency_float_time),
|
||
COL(ccuState, cabin_pressure1),
|
||
COL(ccuState, cabin_pressure2),
|
||
COL(ccuState, cabin_temp1),
|
||
COL(ccuState, cabin_temp2),
|
||
COL(ccuState, cabin_humidity1),
|
||
COL(ccuState, cabin_humidity2),
|
||
COL(ccuState, h2_concentration1),
|
||
COL(ccuState, h2_concentration2),
|
||
COL(ccuState, h2_concentration3),
|
||
COL(ccuState, o2_concentration1),
|
||
COL(ccuState, o2_concentration2),
|
||
COL(ccuState, ch3oh_concentration1),
|
||
COL(ccuState, ch3oh_concentration2),
|
||
COL(ccuState, flame_detector1),
|
||
COL(ccuState, flame_detector2),
|
||
COL_ARR(ccuState, reserved1, 0, "reserved1_1"),
|
||
COL_ARR(ccuState, reserved1, 1, "reserved1_2"),
|
||
COL(ccuState, emergency_battery1_voltage),
|
||
COL(ccuState, emergency_battery1_current),
|
||
COL(ccuState, emergency_battery1_max_temp),
|
||
COL(ccuState, emergency_battery1_fault_word),
|
||
COL(ccuState, emergency_battery2_voltage),
|
||
COL(ccuState, emergency_battery2_current),
|
||
COL(ccuState, emergency_battery2_max_temp),
|
||
COL(ccuState, emergency_battery2_fault_word),
|
||
COL(ccuState, cabin_ox_concentration),
|
||
COL(ccuState, cabin_temperature),
|
||
COL(ccuState, cabin_humidity),
|
||
COL(ccuState, cabin_pressure),
|
||
COL(ccuState, power_cabin_ox_concentration),
|
||
COL(ccuState, power_cabin_temperature),
|
||
COL(ccuState, power_cabin_humidity),
|
||
COL(ccuState, power_cabin_pressure),
|
||
COL_ARR(ccuState, reserved2, 0, "reserved2"),
|
||
COL_ARR(ccuState, dyn_alarm_flag, 0, "dyn_alarm_flag1"),
|
||
COL_ARR(ccuState, dyn_alarm_flag, 1, "dyn_alarm_flag2"),
|
||
COL_ARR(ccuState, dyn_alarm_flag, 2, "dyn_alarm_flag3"),
|
||
COL_ARR(ccuState, dyn_alarm_flag, 3, "dyn_alarm_flag4"),
|
||
COL_ARR(ccuState, dyn_alarm_flag, 4, "dyn_alarm_flag5"),
|
||
COL_ARR(ccuState, dyn_alarm_flag, 5, "dyn_alarm_flag6"),
|
||
COL_ARR(ccuState, ins_alarm_flag, 0, "ins_alarm_flag1"),
|
||
COL_ARR(ccuState, ins_alarm_flag, 1, "ins_alarm_flag2"),
|
||
COL_ARR(ccuState, ins_alarm_flag, 2, "ins_alarm_flag3"),
|
||
COL_ARR(ccuState, ins_alarm_flag, 3, "ins_alarm_flag4"),
|
||
COL_ARR(ccuState, ins_alarm_flag, 4, "ins_alarm_flag5"),
|
||
COL_ARR(ccuState, ins_alarm_flag, 5, "ins_alarm_flag6"),
|
||
COL(ccuState, ins_relay_status1),
|
||
COL(ccuState, ins_relay_status2),
|
||
COL(ccuState, dyn_relay_status1),
|
||
COL(ccuState, dyn_relay_status2),
|
||
COL(ccuState, ins_max_discharge_power),
|
||
COL(ccuState, dyn_max_discharge_power),
|
||
COL(ccuState, ins_soc),
|
||
COL(ccuState, dyn_soc),
|
||
COL(ccuState, ins_total_energy),
|
||
COL(ccuState, dyn_total_energy),
|
||
COL(ccuState, ins_power_input),
|
||
COL(ccuState, dyn_power_input),
|
||
COL(ccuState, ins_charge_status),
|
||
COL(ccuState, dyn_charge_status),
|
||
COL(ccuState, ins_voltage_link),
|
||
COL(ccuState, ins_voltage_pack),
|
||
COL(ccuState, ins_current),
|
||
COL(ccuState, ins_resistance_pos),
|
||
COL(ccuState, ins_resistance_neg),
|
||
COL(ccuState, dyn_voltage_link),
|
||
COL(ccuState, dyn_voltage_pack),
|
||
COL(ccuState, dyn_current),
|
||
COL(ccuState, dyn_resistance_pos),
|
||
COL(ccuState, dyn_resistance_neg),
|
||
COL(ccuState, ins_emergency_status),
|
||
COL(ccuState, dyn_emergency_status),
|
||
COL(ccuState, ins_soc_threshold1),
|
||
COL(ccuState, ins_soc_threshold2),
|
||
COL(ccuState, ins_soc_threshold3),
|
||
COL(ccuState, ins_power_limit1),
|
||
COL(ccuState, ins_power_limit2),
|
||
COL(ccuState, dyn_soc_threshold1),
|
||
COL(ccuState, dyn_soc_threshold2),
|
||
COL(ccuState, dyn_soc_threshold3),
|
||
COL(ccuState, dyn_power_limit1),
|
||
COL(ccuState, dyn_power_limit2),
|
||
COL(ccuState, device_online_flag1),
|
||
COL(ccuState, device_online_flag2),
|
||
COL(ccuState, reserved3),
|
||
COL(ccuState, heartbeat),
|
||
COL(ccuState, emergency_command),
|
||
}},
|
||
{ "disHighVolBusState", {
|
||
COL(disHighVolBusState, fuelCellCircuitBreaker),
|
||
COL(disHighVolBusState, powerLithiumBatteryCircuitBreaker),
|
||
COL(disHighVolBusState, propulsionMotorCircuitBreaker),
|
||
COL(disHighVolBusState, lithiumBatteryGroupInstrumentCircuitBreaker),
|
||
COL(disHighVolBusState, dcDc5ModuleCircuitBreaker),
|
||
COL(disHighVolBusState, bowHighVoltageDistributionBoxCircuitBreaker),
|
||
COL(disHighVolBusState, sternFTDevice45CircuitBreaker),
|
||
COL(disHighVolBusState, sternRudderSwitch1CircuitBreaker),
|
||
COL(disHighVolBusState, sternRudderSwitch2CircuitBreaker),
|
||
COL(disHighVolBusState, fbReservedCircuitBreaker),
|
||
COL(disHighVolBusState, tyzReservedCircuitBreaker),
|
||
COL(disHighVolBusState, reservedCircuitBreaker),
|
||
COL(disHighVolBusState, dcDcFaultWord1),
|
||
COL(disHighVolBusState, dcDcFaultWord2),
|
||
COL(disHighVolBusState, powerBusInsulationStatus),
|
||
COL(disHighVolBusState, aBusInsulationStatus),
|
||
COL(disHighVolBusState, meterPowerLossSignal),
|
||
COL(disHighVolBusState, emergencyPowerLossSignal),
|
||
COL(disHighVolBusState, powerBusVoltage),
|
||
COL(disHighVolBusState, dcDc5ModuleCurrent),
|
||
COL(disHighVolBusState, powerBusCurrent),
|
||
COL(disHighVolBusState, busbarAVoltage),
|
||
COL(disHighVolBusState, propulsionMotorControlBoxCurrent),
|
||
COL(disHighVolBusState, busbarACurrent),
|
||
COL(disHighVolBusState, lithiumBatteryGroupMeterCurrent),
|
||
COL(disHighVolBusState, bowHighVoltageDistributionBoxCurrent),
|
||
COL(disHighVolBusState, sternFTDevice45Current),
|
||
COL(disHighVolBusState, tyzReservedSwitchCurrent),
|
||
COL(disHighVolBusState, sternRudderSwitch1Current),
|
||
COL(disHighVolBusState, reservedCurrent1),
|
||
COL(disHighVolBusState, sternRudderSwitch2Current),
|
||
COL(disHighVolBusState, reservedCurrent2),
|
||
COL(disHighVolBusState, fbReservedSwitchCurrent),
|
||
COL(disHighVolBusState, reservedCurrent3),
|
||
COL(disHighVolBusState, coolWaterPressure),
|
||
}},
|
||
{ "disHighAVolBusState", {
|
||
COL(disHighAVolBusState, bowFTDevice123CircuitBreaker),
|
||
COL(disHighAVolBusState, actuatorCircuitBreaker),
|
||
COL(disHighAVolBusState, mastSteeringGearControlBoxCircuitBreaker),
|
||
COL(disHighAVolBusState, xczCircuitBreaker),
|
||
COL(disHighAVolBusState, bowRudderControlBoxCircuitBreaker),
|
||
COL(disHighAVolBusState, openWaterCoverStartCylinderCircuitBreaker),
|
||
COL(disHighAVolBusState, instrumentPowerFailureSignal),
|
||
COL(disHighAVolBusState, emergencyPowerFailureSignal),
|
||
COL(disHighAVolBusState, waterIngressionAlarm),
|
||
COL(disHighAVolBusState, busbarBVoltage),
|
||
COL(disHighAVolBusState, busbarBCurrent),
|
||
COL(disHighAVolBusState, bowFTDevice123Current),
|
||
COL(disHighAVolBusState, actuatorCurrent),
|
||
COL(disHighAVolBusState, mastSteeringGearControlBoxCurrent),
|
||
COL(disHighAVolBusState, xczCurrent),
|
||
COL(disHighAVolBusState, bowRudderControlBoxCurrent),
|
||
COL(disHighAVolBusState, openWaterCoverStartCylinderCurrent),
|
||
}},
|
||
{ "disLowMainBusState", {
|
||
COL(disLowMainBusState, lithiumBatteryGroupInstrumentCircuitBreaker),
|
||
COL(disLowMainBusState, bowLowVoltageDistributionBoxCircuitBreaker),
|
||
COL(disLowMainBusState, unit4InstrumentDC48VCircuitBreaker),
|
||
COL(disLowMainBusState, dcC1DCDistributionPanelCircuitBreaker),
|
||
COL(disLowMainBusState, reservedCircuitBreaker1),
|
||
COL(disLowMainBusState, reservedCircuitBreaker2),
|
||
COL(disLowMainBusState, emergencyLithiumBatteryGroup2CircuitBreaker),
|
||
COL(disLowMainBusState, instrumentPowerFailureSignal),
|
||
COL(disLowMainBusState, emergencyPowerFailureSignal),
|
||
COL(disLowMainBusState, instrumentBusbarInsulationLow),
|
||
COL(disLowMainBusState, instrumentBusbarVoltage),
|
||
COL(disLowMainBusState, bowLowVoltageDistributionBoxCurrent),
|
||
COL(disLowMainBusState, dcC1InstrumentDC48VCurrent),
|
||
COL(disLowMainBusState, reservedCurrent1),
|
||
COL(disLowMainBusState, emergencyLithiumBatteryGroup2Current),
|
||
COL(disLowMainBusState, lithiumBatteryGroupInstrumentCurrent),
|
||
COL(disLowMainBusState, unit4InstrumentDC48VCurrent),
|
||
COL(disLowMainBusState, reservedCurrent2),
|
||
COL(disLowMainBusState, emergency2BusbarVoltage),
|
||
COL(disLowMainBusState, compositeEnergyManagementSystemEmergencyDC48VCurrent),
|
||
COL(disLowMainBusState, fuelCellSecuritySystemEmergencyDC48VCurrent),
|
||
COL(disLowMainBusState, plcControlPowerCurrent),
|
||
}},
|
||
{ "disLowBusState", {
|
||
COL(disLowBusState, unit1CircuitBreaker),
|
||
COL(disLowBusState, unit2CircuitBreaker),
|
||
COL(disLowBusState, unit3CircuitBreaker),
|
||
COL(disLowBusState, unit5CircuitBreaker),
|
||
COL(disLowBusState, bowPZDeviceCircuitBreaker),
|
||
COL(disLowBusState, reservedCircuitBreaker1),
|
||
COL(disLowBusState, reservedCircuitBreaker2),
|
||
COL(disLowBusState, emergencyLithiumBatteryGroup1CircuitBreaker),
|
||
COL(disLowBusState, powerFailureSignal),
|
||
COL(disLowBusState, emergencyPowerFailureSignal),
|
||
COL(disLowBusState, waterIngressionAlarm),
|
||
COL(disLowBusState, instrumentBusbarVoltage),
|
||
COL(disLowBusState, instrumentBusbarCurrent),
|
||
COL(disLowBusState, unit1Current),
|
||
COL(disLowBusState, unit2Current),
|
||
COL(disLowBusState, unit3Current),
|
||
COL(disLowBusState, unit5Current),
|
||
COL(disLowBusState, bowPZDeviceCurrent),
|
||
COL(disLowBusState, emergencyLithiumBatteryGroup1Current),
|
||
COL(disLowBusState, emergency1BusbarVoltage),
|
||
}},
|
||
{ "ccuSetParmFb", {
|
||
COL(msg_CcuSetParmFbMsg, flag),
|
||
COL(msg_CcuSetParmFbMsg, failureCode),
|
||
}},
|
||
{ "ccuColCmd", {
|
||
COL_AS(ccuColCmd, fcmode, "workCondition"),
|
||
COL(ccuColCmd, fcuCmd),
|
||
COL(ccuColCmd, fcuPowerEfficiency),
|
||
COL(ccuColCmd, pitchData1),
|
||
COL(ccuColCmd, rollData1),
|
||
COL(ccuColCmd, emergencyAllow),
|
||
COL(ccuColCmd, depth),
|
||
COL(ccuColCmd, supplyExhaustCmd),
|
||
COL(ccuColCmd, reservedCmd5),
|
||
COL(ccuColCmd, reservedCmd6),
|
||
COL(ccuColCmd, insBatCmd),
|
||
COL(ccuColCmd, powerBatCmd),
|
||
COL(ccuColCmd, powerBatEfficiency),
|
||
COL(ccuColCmd, heartbeat),
|
||
COL(ccuColCmd, masterState),
|
||
COL(ccuColCmd, paceholder1),
|
||
COL(ccuColCmd, paceholder2),
|
||
}},
|
||
{ "ccuSetParmCmd", {
|
||
COL(ccuSetParmCmd, insSocHold1),
|
||
COL(ccuSetParmCmd, insSocHold2),
|
||
COL(ccuSetParmCmd, insSocHold3),
|
||
COL(ccuSetParmCmd, insOutputPower),
|
||
COL(ccuSetParmCmd, reserved1),
|
||
COL(ccuSetParmCmd, powerSocHold1),
|
||
COL(ccuSetParmCmd, powerSocHold2),
|
||
COL(ccuSetParmCmd, powerSocHold3),
|
||
COL(ccuSetParmCmd, powerOutputPower),
|
||
COL(ccuSetParmCmd, reserved2),
|
||
COL(ccuSetParmCmd, reserved3),
|
||
COL(ccuSetParmCmd, reserved4),
|
||
}},
|
||
{ "disHighVolBusCmd", {
|
||
COL(disHighVolBusCmd, fuelCellCircuitBreaker),
|
||
COL(disHighVolBusCmd, powerLithiumBatteryCircuitBreaker),
|
||
COL(disHighVolBusCmd, propulsionMotorCircuitBreaker),
|
||
COL(disHighVolBusCmd, lithiumBatteryGroupInstrumentCircuitBreaker),
|
||
COL(disHighVolBusCmd, dcDc5ModuleCircuitBreaker),
|
||
COL(disHighVolBusCmd, bowHighVoltageDistributionBoxCircuitBreaker),
|
||
COL(disHighVolBusCmd, sternFTDevice45CircuitBreaker),
|
||
COL(disHighVolBusCmd, sternRudderSwitch1CircuitBreaker),
|
||
COL(disHighVolBusCmd, sternRudderSwitch2CircuitBreaker),
|
||
COL(disHighVolBusCmd, fbReservedCircuitBreaker),
|
||
COL(disHighVolBusCmd, tyzReservedCircuitBreaker),
|
||
COL(disHighVolBusCmd, reservedCircuitBreaker),
|
||
COL(disHighVolBusCmd, dcdc5Module),
|
||
COL(disHighVolBusCmd, coolingSystem),
|
||
}},
|
||
{ "disHighAVolBusCmd", {
|
||
COL(disHighAVolBusCmd, bowFTDevice123CircuitBreaker),
|
||
COL(disHighAVolBusCmd, actuatorCircuitBreaker),
|
||
COL(disHighAVolBusCmd, mastSteeringGearControlBoxCircuitBreaker),
|
||
COL(disHighAVolBusCmd, xczCircuitBreaker),
|
||
COL(disHighAVolBusCmd, bowRudderControlBoxCircuitBreaker),
|
||
COL(disHighAVolBusCmd, openWaterCoverStartCylinderCircuitBreaker),
|
||
}},
|
||
{ "disLowMainBusCmd", {
|
||
COL(disLowMainBusCmd, lithiumBatteryGroupInstrumentCircuitBreaker),
|
||
COL(disLowMainBusCmd, bowLowVoltageDistributionBoxCircuitBreaker),
|
||
COL(disLowMainBusCmd, unit4InstrumentDC48VCircuitBreaker),
|
||
COL(disLowMainBusCmd, dcC1DCDistributionPanelCircuitBreaker),
|
||
COL(disLowMainBusCmd, reservedCircuitBreaker1),
|
||
COL(disLowMainBusCmd, reservedCircuitBreaker2),
|
||
COL(disLowMainBusCmd, emergencyLithiumBatteryGroup2CircuitBreaker),
|
||
}},
|
||
{ "disLowBusCmd", {
|
||
COL(disLowBusCmd, unit1CircuitBreaker),
|
||
COL(disLowBusCmd, unit2CircuitBreaker),
|
||
COL(disLowBusCmd, unit3CircuitBreaker),
|
||
COL(disLowBusCmd, unit5CircuitBreaker),
|
||
COL(disLowBusCmd, bowPZDeviceCircuitBreaker),
|
||
COL(disLowBusCmd, reservedCircuitBreaker1),
|
||
COL(disLowBusCmd, reservedCircuitBreaker2),
|
||
COL(disLowBusCmd, emergencyLithiumBatteryGroup1CircuitBreaker),
|
||
}},
|
||
};
|
||
return tables;
|
||
}
|
||
|
||
#undef COL
|
||
#undef COL_AS
|
||
#undef COL_ARR
|
||
|
||
SQLite::SQLite(const std::string& dbPath)
|
||
: m_db(nullptr), m_status(Status::Disabled), m_dbPath(dbPath)
|
||
{
|
||
int rc = sqlite3_open_v2(dbPath.c_str(), &m_db,
|
||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX,
|
||
nullptr);
|
||
if (rc != SQLITE_OK) {
|
||
m_lastError = m_db ? sqlite3_errmsg(m_db) : "unknown error";
|
||
LOG_F(ERROR, "无法打开数据库 %s: %s", dbPath.c_str(), m_lastError.c_str());
|
||
if (m_db) { sqlite3_close(m_db); m_db = nullptr; }
|
||
m_status = Status::Disabled;
|
||
return;
|
||
}
|
||
|
||
m_status = Status::OK;
|
||
if (!applyPragmas()) {
|
||
m_status = Status::Degraded;
|
||
}
|
||
|
||
if (!migrate()) {
|
||
int err = sqlite3_errcode(m_db);
|
||
if (err == SQLITE_CORRUPT || err == SQLITE_NOTADB) {
|
||
LOG_F(ERROR, "数据库损坏,尝试备份并重建: %s", dbPath.c_str());
|
||
sqlite3_close(m_db);
|
||
m_db = nullptr;
|
||
|
||
std::string backup = dbPath + ".corrupt";
|
||
std::rename(dbPath.c_str(), backup.c_str());
|
||
|
||
if (sqlite3_open_v2(dbPath.c_str(), &m_db,
|
||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX,
|
||
nullptr) == SQLITE_OK) {
|
||
m_status = Status::OK;
|
||
applyPragmas();
|
||
if (!migrate()) {
|
||
if (m_status == Status::OK) m_status = Status::Degraded;
|
||
}
|
||
} else {
|
||
m_status = Status::Disabled;
|
||
}
|
||
} else if (m_status == Status::OK) {
|
||
m_status = Status::Degraded;
|
||
}
|
||
}
|
||
}
|
||
|
||
SQLite::~SQLite()
|
||
{
|
||
if (m_db) sqlite3_close(m_db);
|
||
}
|
||
|
||
std::string SQLite::lastError() const
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_dbMutex);
|
||
return m_lastError;
|
||
}
|
||
|
||
bool SQLite::setError(int rc, const char* op)
|
||
{
|
||
m_lastError = m_db ? sqlite3_errmsg(m_db) : "database not open";
|
||
LOG_F(ERROR, "SQLite %s 失败 (rc=%d): %s", op, rc, m_lastError.c_str());
|
||
|
||
if (rc == SQLITE_CORRUPT || rc == SQLITE_NOTADB) {
|
||
m_status = Status::Disabled;
|
||
} else if (rc == SQLITE_FULL || rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
|
||
m_status = Status::Degraded;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool SQLite::applyPragmas()
|
||
{
|
||
const char* pragmas[] = {
|
||
"PRAGMA journal_mode=WAL;",
|
||
"PRAGMA synchronous=NORMAL;",
|
||
"PRAGMA busy_timeout=5000;",
|
||
"PRAGMA foreign_keys=OFF;"
|
||
};
|
||
for (const char* p : pragmas) {
|
||
if (sqlite3_exec(m_db, p, nullptr, nullptr, nullptr) != SQLITE_OK) {
|
||
setError(sqlite3_errcode(m_db), p);
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool SQLite::migrate()
|
||
{
|
||
int version = 0;
|
||
sqlite3_stmt* stmt = nullptr;
|
||
if (sqlite3_prepare_v2(m_db, "PRAGMA user_version;", -1, &stmt, nullptr) == SQLITE_OK) {
|
||
if (sqlite3_step(stmt) == SQLITE_ROW) version = sqlite3_column_int(stmt, 0);
|
||
sqlite3_finalize(stmt);
|
||
}
|
||
|
||
if (!createAllTablesUnlocked()) return false;
|
||
|
||
// 老库缺列自动补齐(协议演进新增字段后,旧表由 CREATE TABLE IF NOT EXISTS
|
||
// 创建、不会带新列,导致 INSERT prepare 失败、该表数据无法落库)
|
||
if (!migrateColumnsUnlocked()) return false;
|
||
|
||
if (version < SCHEMA_VERSION) {
|
||
std::string sql = "PRAGMA user_version = " + std::to_string(SCHEMA_VERSION) + ";";
|
||
sqlite3_exec(m_db, sql.c_str(), nullptr, nullptr, nullptr);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//====================================================================
|
||
// migrateColumnsUnlocked:按注册表逐一对比表现有列,缺哪列补哪列
|
||
// (ALTER TABLE ADD COLUMN,新增列对旧行取默认值 0/NULL)。
|
||
//====================================================================
|
||
bool SQLite::migrateColumnsUnlocked()
|
||
{
|
||
for (const TableDesc& t : allTables()) {
|
||
// 读取现有列(PRAGMA table_info 第 2 列为列名)
|
||
std::vector<std::string> existing;
|
||
sqlite3_stmt* stmt = nullptr;
|
||
std::string pragma = std::string("PRAGMA table_info(") + t.name + ");";
|
||
if (sqlite3_prepare_v2(m_db, pragma.c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
|
||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||
const unsigned char* name = sqlite3_column_text(stmt, 1);
|
||
if (name) existing.push_back(reinterpret_cast<const char*>(name));
|
||
}
|
||
sqlite3_finalize(stmt);
|
||
}
|
||
if (existing.empty()) continue; // 表不存在(新库),建表时已含全部列
|
||
|
||
for (const Column& c : t.columns) {
|
||
bool found = false;
|
||
for (const std::string& e : existing) {
|
||
if (e == c.name) { found = true; break; }
|
||
}
|
||
if (found) continue;
|
||
|
||
std::string sql = std::string("ALTER TABLE ") + t.name +
|
||
" ADD COLUMN " + c.name + " " + c.sqlType + ";";
|
||
char* errMsg = nullptr;
|
||
int rc = sqlite3_exec(m_db, sql.c_str(), nullptr, nullptr, &errMsg);
|
||
if (rc == SQLITE_OK) {
|
||
LOG_F(INFO, "SQLite 迁移: 表 %s 新增列 %s", t.name, c.name);
|
||
} else {
|
||
// 记录失败但不中断打开:其余列/表继续尝试,
|
||
// 未补齐列的 insert 仍会按原样报 prepare 错误
|
||
m_lastError = errMsg ? errMsg : "alter table failed";
|
||
LOG_F(ERROR, "SQLite 迁移失败: %s (%s)", sql.c_str(), m_lastError.c_str());
|
||
if (errMsg) sqlite3_free(errMsg);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool SQLite::createTableInternal(const std::string& tableName, const std::string& columns)
|
||
{
|
||
std::string sql = "CREATE TABLE IF NOT EXISTS " + tableName + " (" + columns + ");";
|
||
char* errMsg = nullptr;
|
||
int rc = sqlite3_exec(m_db, sql.c_str(), nullptr, nullptr, &errMsg);
|
||
if (rc != SQLITE_OK) {
|
||
m_lastError = errMsg ? errMsg : "create table failed";
|
||
LOG_F(ERROR, "创建表失败: %s (%s)", tableName.c_str(), m_lastError.c_str());
|
||
if (errMsg) sqlite3_free(errMsg);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool SQLite::createTable(const std::string& tableName, const std::string& columns)
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_dbMutex);
|
||
if (!m_db) {
|
||
m_lastError = "database not open";
|
||
return false;
|
||
}
|
||
return createTableInternal(tableName, columns);
|
||
}
|
||
|
||
bool SQLite::createTable()
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_dbMutex);
|
||
if (!m_db) {
|
||
m_lastError = "database not open";
|
||
return false;
|
||
}
|
||
return createAllTablesUnlocked();
|
||
}
|
||
|
||
bool SQLite::createAllTablesUnlocked()
|
||
{
|
||
// 由描述符生成各协议结构表的建表语句(自动追加 id/timestamp 元列)
|
||
for (const TableDesc& t : allTables()) {
|
||
std::string cols = "id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||
"timestamp DATETIME DEFAULT (datetime('now', 'localtime'))";
|
||
for (const Column& c : t.columns) {
|
||
cols += ", ";
|
||
cols += c.name;
|
||
cols += " ";
|
||
cols += c.sqlType;
|
||
}
|
||
if (!createTableInternal(t.name, cols)) return false;
|
||
}
|
||
|
||
// msg_log(原始收发帧日志,列类型多样,保持手写)
|
||
std::string msgLogColumns = R"(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
ts REAL NOT NULL,
|
||
ts_local TEXT DEFAULT (datetime('now', 'localtime')),
|
||
direction INTEGER NOT NULL,
|
||
msg_id INTEGER NOT NULL,
|
||
msg_name TEXT,
|
||
length INTEGER,
|
||
payload_hex TEXT,
|
||
checksum_ok INTEGER,
|
||
note TEXT
|
||
)";
|
||
if (!createTableInternal("msg_log", msgLogColumns)) return false;
|
||
|
||
// 索引
|
||
sqlite3_exec(m_db, "CREATE INDEX IF NOT EXISTS idx_msg_log_ts ON msg_log(ts);", nullptr, nullptr, nullptr);
|
||
sqlite3_exec(m_db, "CREATE INDEX IF NOT EXISTS idx_msg_log_id ON msg_log(msg_id);", nullptr, nullptr, nullptr);
|
||
|
||
return true;
|
||
}
|
||
|
||
//====================================================================
|
||
// 泛化插入:按表名在注册表中查找描述并绑定,所有类型共用这一条路径
|
||
//====================================================================
|
||
bool SQLite::insertGeneric(const char* tableName, const void* obj)
|
||
{
|
||
const TableDesc* table = nullptr;
|
||
for (const TableDesc& t : allTables()) {
|
||
if (std::strcmp(t.name, tableName) == 0) { table = &t; break; }
|
||
}
|
||
if (!table) return false;
|
||
|
||
try {
|
||
std::lock_guard<std::mutex> lock(m_dbMutex);
|
||
if (!m_db) { setError(SQLITE_MISUSE, "insert (db not open)"); return false; }
|
||
if (m_status == Status::Disabled) return false;
|
||
if (table->columns.empty()) return false;
|
||
|
||
std::string sql = "INSERT INTO ";
|
||
sql += table->name;
|
||
sql += " (";
|
||
for (size_t i = 0; i < table->columns.size(); ++i) {
|
||
if (i) sql += ",";
|
||
sql += table->columns[i].name;
|
||
}
|
||
sql += ") VALUES (";
|
||
for (size_t i = 0; i < table->columns.size(); ++i) {
|
||
if (i) sql += ",";
|
||
sql += "?";
|
||
}
|
||
sql += ")";
|
||
|
||
sqlite3_stmt* stmt = nullptr;
|
||
int rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr);
|
||
if (rc != SQLITE_OK) { setError(rc, "prepare"); return false; }
|
||
|
||
int idx = 1;
|
||
for (const Column& c : table->columns) {
|
||
c.bind(stmt, idx, obj);
|
||
}
|
||
|
||
rc = sqlite3_step(stmt);
|
||
sqlite3_finalize(stmt);
|
||
if (rc != SQLITE_DONE) { setError(rc, "step"); return false; }
|
||
if (m_status == Status::Degraded) m_status = Status::OK;
|
||
return true;
|
||
} catch (const std::exception& e) {
|
||
LOG_F(ERROR, "SQLite insert 异常: %s", e.what());
|
||
return false;
|
||
} catch (...) {
|
||
LOG_F(ERROR, "SQLite insert 未知异常");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//====================================================================
|
||
// 状态反馈数据
|
||
//====================================================================
|
||
bool SQLite::insertData(ccuState data) { return insertGeneric("ccuState", &data); }
|
||
bool SQLite::insertData(disHighVolBusState data) { return insertGeneric("disHighVolBusState", &data); }
|
||
bool SQLite::insertData(disHighAVolBusState data) { return insertGeneric("disHighAVolBusState", &data); }
|
||
bool SQLite::insertData(disLowMainBusState data) { return insertGeneric("disLowMainBusState", &data); }
|
||
bool SQLite::insertData(disLowBusState data) { return insertGeneric("disLowBusState", &data); }
|
||
bool SQLite::insertData(const msg_CcuSetParmFbMsg& fb) { return insertGeneric("ccuSetParmFb", &fb); }
|
||
|
||
//====================================================================
|
||
// 下发指令数据
|
||
//====================================================================
|
||
bool SQLite::insertData(ccuColCmd cmd) { return insertGeneric("ccuColCmd", &cmd); }
|
||
bool SQLite::insertData(ccuSetParmCmd cmd) { return insertGeneric("ccuSetParmCmd", &cmd); }
|
||
bool SQLite::insertData(disHighVolBusCmd cmd) { return insertGeneric("disHighVolBusCmd", &cmd); }
|
||
bool SQLite::insertData(disHighAVolBusCmd cmd) { return insertGeneric("disHighAVolBusCmd", &cmd); }
|
||
bool SQLite::insertData(disLowMainBusCmd cmd) { return insertGeneric("disLowMainBusCmd", &cmd); }
|
||
bool SQLite::insertData(disLowBusCmd cmd) { return insertGeneric("disLowBusCmd", &cmd); }
|
||
|
||
//====================================================================
|
||
// 原始收发帧落库
|
||
//====================================================================
|
||
void SQLite::onFrame(int direction, unsigned short msgId, const char* name,
|
||
const unsigned char* data, unsigned int len, bool checksumOk)
|
||
{
|
||
try {
|
||
std::lock_guard<std::mutex> lock(m_dbMutex);
|
||
if (!m_db || m_status == Status::Disabled) return;
|
||
|
||
static const char* hexDigits = "0123456789ABCDEF";
|
||
std::string hex;
|
||
hex.reserve(len * 2);
|
||
for (unsigned int i = 0; i < len; ++i) {
|
||
hex.push_back(hexDigits[data[i] >> 4]);
|
||
hex.push_back(hexDigits[data[i] & 0x0F]);
|
||
}
|
||
|
||
double ts = std::chrono::duration<double>(
|
||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||
|
||
const char* sql =
|
||
"INSERT INTO msg_log "
|
||
"(ts, direction, msg_id, msg_name, length, payload_hex, checksum_ok) "
|
||
"VALUES (?,?,?,?,?,?,?)";
|
||
sqlite3_stmt* stmt = nullptr;
|
||
int rc = sqlite3_prepare_v2(m_db, sql, -1, &stmt, nullptr);
|
||
if (rc != SQLITE_OK) { setError(rc, "prepare msg_log"); return; }
|
||
|
||
sqlite3_bind_double(stmt, 1, ts);
|
||
sqlite3_bind_int(stmt, 2, direction);
|
||
sqlite3_bind_int(stmt, 3, static_cast<int>(msgId));
|
||
sqlite3_bind_text(stmt, 4, name ? name : "", -1, SQLITE_TRANSIENT);
|
||
sqlite3_bind_int(stmt, 5, static_cast<int>(len));
|
||
sqlite3_bind_text(stmt, 6, hex.c_str(), static_cast<int>(hex.size()), SQLITE_TRANSIENT);
|
||
sqlite3_bind_int(stmt, 7, checksumOk ? 1 : 0);
|
||
|
||
rc = sqlite3_step(stmt);
|
||
sqlite3_finalize(stmt);
|
||
if (rc != SQLITE_DONE) { setError(rc, "step msg_log"); return; }
|
||
if (m_status == Status::Degraded) m_status = Status::OK;
|
||
} catch (const std::exception& e) {
|
||
LOG_F(ERROR, "SQLite onFrame 异常: %s", e.what());
|
||
} catch (...) {
|
||
LOG_F(ERROR, "SQLite onFrame 未知异常");
|
||
}
|
||
}
|
||
|
||
//====================================================================
|
||
// 查询接口
|
||
//====================================================================
|
||
bool SQLite::queryInt(const std::string& sql, long long& out)
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_dbMutex);
|
||
if (!m_db) return false;
|
||
|
||
sqlite3_stmt* stmt = nullptr;
|
||
int rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr);
|
||
if (rc != SQLITE_OK) { setError(rc, "query"); return false; }
|
||
|
||
bool ok = false;
|
||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||
out = sqlite3_column_int64(stmt, 0);
|
||
ok = true;
|
||
}
|
||
sqlite3_finalize(stmt);
|
||
return ok;
|
||
}
|
||
|
||
long long SQLite::count(const std::string& table)
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_dbMutex);
|
||
if (!m_db) return 0;
|
||
|
||
std::string sql = "SELECT COUNT(*) FROM " + table;
|
||
sqlite3_stmt* stmt = nullptr;
|
||
int rc = sqlite3_prepare_v2(m_db, sql.c_str(), -1, &stmt, nullptr);
|
||
if (rc != SQLITE_OK) { setError(rc, "count"); return 0; }
|
||
|
||
long long n = 0;
|
||
if (sqlite3_step(stmt) == SQLITE_ROW) n = sqlite3_column_int64(stmt, 0);
|
||
sqlite3_finalize(stmt);
|
||
return n;
|
||
}
|