Add console CAN controller node
This commit is contained in:
1 parent
a130726907
commit
99a1850a21
3 files changed
+1366
-99
No files matched your search
+551
@@ -3,6 +3,8 @@
|
||||
// Automatic seat controller with EEPROM settings and optional serial tools.
|
||||
//
|
||||
#include <EEPROM.h>
|
||||
#include <SPI.h>
|
||||
#include <mcp_can.h>
|
||||
|
||||
#define seatRelayD8 8 // HIGH = OFF, LOW = ON (up relay)
|
||||
#define seatRelayD9 9 // HIGH = OFF, LOW = ON (down relay)
|
||||
@@ -45,11 +47,59 @@ const unsigned long AUTO_CALIBRATION_DOWN_MS = 6000UL;
|
||||
const unsigned long DEBUG_INTERVAL_MS = 1000UL;
|
||||
const long MANUAL_STEP_MS = 500;
|
||||
|
||||
// Chaegeon-CAN heartbeat. MCP2515 module uses an 8MHz crystal.
|
||||
const byte CAN_CS_PIN = 10;
|
||||
const unsigned long CAN_ID_MAIN_HEARTBEAT = 0x100UL;
|
||||
const unsigned long CAN_ID_CONSOLE_COMMAND = 0x101UL;
|
||||
const unsigned long CAN_ID_CONSOLE_STATUS = 0x200UL;
|
||||
const unsigned long CAN_HEARTBEAT_INTERVAL_MS = 250UL;
|
||||
|
||||
// Center-console node protocol. Nano stores memory and sends pulse commands.
|
||||
const unsigned long CONSOLE_CAN_TIMEOUT_MS = 2000UL;
|
||||
const unsigned long CONSOLE_COMMAND_HOLD_MS = 2500UL;
|
||||
const unsigned long CONSOLE_MANUAL_LEARN_SETTLE_MS = 700UL;
|
||||
const unsigned long CONSOLE_STARTUP_DELAY_MS = 3000UL;
|
||||
const unsigned long CONSOLE_MEMORY_REFRESH_INTERVAL_MS = 1170000UL; // 19m 30s
|
||||
const unsigned long CONSOLE_STEERING_RESTART_INTERVAL_MS = 1170000UL;
|
||||
const unsigned int CONSOLE_SHORT_PRESS_MS = 300;
|
||||
const unsigned int CONSOLE_SHORT_PRESS_GAP_MS = 450;
|
||||
const unsigned int CONSOLE_CLIMATE_LONG_OFF_MS = 2100;
|
||||
const unsigned int CONSOLE_CLIMATE_LONG_OFF_GAP_MS = 700;
|
||||
const unsigned int CONSOLE_STEERING_RESTART_GAP_MS = 1200;
|
||||
|
||||
const byte CONSOLE_CHANNEL_COUNT = 7;
|
||||
const byte CONSOLE_CLIMATE_COUNT = 4;
|
||||
const byte CONSOLE_TOGGLE_COUNT = 3;
|
||||
const byte CONSOLE_PULSE_QUEUE_SIZE = 24;
|
||||
|
||||
const byte CH_DRIVER_VENT = 0;
|
||||
const byte CH_DRIVER_HEAT = 1;
|
||||
const byte CH_PASSENGER_VENT = 2;
|
||||
const byte CH_PASSENGER_HEAT = 3;
|
||||
const byte CH_STEERING_HEAT = 4;
|
||||
const byte CH_FRONT_SENSOR = 5;
|
||||
const byte CH_VESS = 6;
|
||||
|
||||
const byte TG_STEERING_HEAT = 0;
|
||||
const byte TG_FRONT_SENSOR = 1;
|
||||
const byte TG_VESS = 2;
|
||||
|
||||
const byte CONSOLE_OPPOSITE_CLIMATE[CONSOLE_CLIMATE_COUNT] = {
|
||||
CH_DRIVER_HEAT,
|
||||
CH_DRIVER_VENT,
|
||||
CH_PASSENGER_HEAT,
|
||||
CH_PASSENGER_VENT
|
||||
};
|
||||
|
||||
// EEPROM map.
|
||||
const int EEPROM_MAGIC_ADDR = 0;
|
||||
const byte EEPROM_MAGIC_VALUE = 0x64;
|
||||
const int EEPROM_USE_COUNT_ADDR = 1;
|
||||
const int EEPROM_SEAT_POSITION_ADDR = 2; // uint16_t, low byte first
|
||||
const int EEPROM_CONSOLE_MAGIC_ADDR = 16;
|
||||
const byte EEPROM_CONSOLE_MAGIC_VALUE = 0xC2;
|
||||
const int EEPROM_CONSOLE_CLIMATE_TARGET_ADDR = 17; // 4 bytes, driver vent/heat/passenger vent/heat
|
||||
const int EEPROM_CONSOLE_TOGGLE_TARGET_ADDR = 21; // 3 bytes, steering/front sensor/VESS
|
||||
|
||||
const byte CALIBRATION_IDLE = 0;
|
||||
const byte CALIBRATION_UP = 1;
|
||||
@@ -92,6 +142,38 @@ bool serialIntroPrinted = false;
|
||||
bool debugEnabled = false;
|
||||
bool manualStepStatusPending = false;
|
||||
|
||||
MCP_CAN chaegeonCan(CAN_CS_PIN);
|
||||
bool chaegeonCanReady = false;
|
||||
byte chaegeonCanSeq = 0;
|
||||
unsigned long lastCanHeartbeatTime = 0;
|
||||
|
||||
bool consoleAlive = false;
|
||||
bool consoleBusy = false;
|
||||
bool consoleStartupApplyDone = false;
|
||||
bool previousConsoleIgnitionOn = false;
|
||||
bool consoleIgnitionApplyPending = false;
|
||||
bool consoleCommandInFlight = false;
|
||||
byte consoleCommandSeq = 0;
|
||||
byte consoleCommandInFlightSeq = 0;
|
||||
byte consoleLastAcceptedSeq = 0;
|
||||
byte consoleActualClimatePack = 0;
|
||||
byte consoleActualToggleBits = 0;
|
||||
byte consoleButtonBits = 0;
|
||||
byte consolePreviousButtonBits = 0;
|
||||
byte consoleClimateTarget[CONSOLE_CLIMATE_COUNT] = {0, 0, 0, 0};
|
||||
bool consoleToggleTarget[CONSOLE_TOGGLE_COUNT] = {false, false, false};
|
||||
bool consoleManualLearnPending[CONSOLE_CHANNEL_COUNT] = {false};
|
||||
unsigned long consoleManualLearnTime[CONSOLE_CHANNEL_COUNT] = {0};
|
||||
unsigned long lastConsoleStatusTime = 0;
|
||||
unsigned long lastConsoleCommandTime = 0;
|
||||
unsigned long lastConsoleClimateRefreshTime = 0;
|
||||
unsigned long lastConsoleSteeringRestartTime = 0;
|
||||
byte consolePulseQueueChannel[CONSOLE_PULSE_QUEUE_SIZE] = {0};
|
||||
unsigned int consolePulseQueueOnMs[CONSOLE_PULSE_QUEUE_SIZE] = {0};
|
||||
unsigned int consolePulseQueueGapMs[CONSOLE_PULSE_QUEUE_SIZE] = {0};
|
||||
byte consolePulseQueueHead = 0;
|
||||
byte consolePulseQueueTail = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(25);
|
||||
@@ -103,6 +185,7 @@ void setup() {
|
||||
|
||||
applySeatAction(0);
|
||||
seat(0);
|
||||
setupChaegeonCan();
|
||||
loadSettings();
|
||||
|
||||
measurement();
|
||||
@@ -137,6 +220,7 @@ void loop() {
|
||||
registerIgnitionOffUse(currentTime);
|
||||
}
|
||||
prevIgnitionState = ignitionState;
|
||||
updateChaegeonCan(currentTime);
|
||||
|
||||
if (autoCalibrationState != CALIBRATION_IDLE) {
|
||||
updateAutoCalibration(currentTime);
|
||||
@@ -538,6 +622,8 @@ void loadSettings() {
|
||||
storedSeatPositionMs = SEAT_HARD_LIMIT_UP_MS;
|
||||
writeUint16(EEPROM_SEAT_POSITION_ADDR, storedSeatPositionMs);
|
||||
}
|
||||
|
||||
loadConsoleSettings();
|
||||
}
|
||||
|
||||
uint16_t readUint16(int addr) {
|
||||
@@ -604,6 +690,7 @@ void printStatus(const __FlashStringHelper *tag) {
|
||||
printNamedBool(F("forceCalibration"), forceCalibration);
|
||||
printNamedInt(F("autoCalibrationState"), autoCalibrationState);
|
||||
printAutoCalibrationDelayStatus();
|
||||
printConsoleStatusFields();
|
||||
}
|
||||
|
||||
void printDebugLine() {
|
||||
@@ -631,6 +718,7 @@ void printDebugLine() {
|
||||
printNamedBool(F("forceCalibration"), forceCalibration);
|
||||
printNamedInt(F("autoCalibrationState"), autoCalibrationState);
|
||||
printAutoCalibrationDelayStatus();
|
||||
printConsoleStatusFields();
|
||||
}
|
||||
|
||||
void printUptime() {
|
||||
@@ -715,6 +803,469 @@ void printAutoCalibrationDelayStatus() {
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
void printConsoleStatusFields() {
|
||||
printNamedBool(F("consoleCanReady"), chaegeonCanReady);
|
||||
printNamedBool(F("consoleAlive"), consoleAlive);
|
||||
printNamedBool(F("consoleBusy"), consoleBusy);
|
||||
printNamedInt(F("consolePulseQueueLength"), consolePulseQueueLength());
|
||||
printNamedInt(F("consoleDriverVentTarget"), consoleClimateTarget[CH_DRIVER_VENT]);
|
||||
printNamedInt(F("consoleDriverHeatTarget"), consoleClimateTarget[CH_DRIVER_HEAT]);
|
||||
printNamedInt(F("consolePassengerVentTarget"), consoleClimateTarget[CH_PASSENGER_VENT]);
|
||||
printNamedInt(F("consolePassengerHeatTarget"), consoleClimateTarget[CH_PASSENGER_HEAT]);
|
||||
printNamedBool(F("consoleSteeringHeatTarget"), consoleToggleTarget[TG_STEERING_HEAT]);
|
||||
printNamedBool(F("consoleFrontSensorTarget"), consoleToggleTarget[TG_FRONT_SENSOR]);
|
||||
printNamedBool(F("consoleVessTarget"), consoleToggleTarget[TG_VESS]);
|
||||
printNamedInt(F("consoleDriverVentActual"), getConsoleClimateActual(CH_DRIVER_VENT));
|
||||
printNamedInt(F("consoleDriverHeatActual"), getConsoleClimateActual(CH_DRIVER_HEAT));
|
||||
printNamedInt(F("consolePassengerVentActual"), getConsoleClimateActual(CH_PASSENGER_VENT));
|
||||
printNamedInt(F("consolePassengerHeatActual"), getConsoleClimateActual(CH_PASSENGER_HEAT));
|
||||
printNamedBool(F("consoleSteeringHeatActual"), getConsoleToggleActual(TG_STEERING_HEAT));
|
||||
printNamedBool(F("consoleFrontSensorActual"), getConsoleToggleActual(TG_FRONT_SENSOR));
|
||||
printNamedBool(F("consoleVessActual"), getConsoleToggleActual(TG_VESS));
|
||||
}
|
||||
|
||||
void updateChaegeonCan(unsigned long currentTime) {
|
||||
updateChaegeonCanReceive(currentTime);
|
||||
updateConsoleAlive(currentTime);
|
||||
updateChaegeonCanHeartbeat(currentTime);
|
||||
updateConsoleManualLearn(currentTime);
|
||||
updateConsoleAutomation(currentTime);
|
||||
updateConsoleCommandSender(currentTime);
|
||||
}
|
||||
|
||||
void updateChaegeonCanReceive(unsigned long currentTime) {
|
||||
if (!chaegeonCanReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (chaegeonCan.checkReceive() == CAN_MSGAVAIL) {
|
||||
unsigned long rxId = 0;
|
||||
byte len = 0;
|
||||
byte payload[8] = {0};
|
||||
if (chaegeonCan.readMsgBuf(&rxId, &len, payload) != CAN_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rxId == CAN_ID_CONSOLE_STATUS && len >= 6 && payload[0] == 1) {
|
||||
consoleBusy = (payload[1] & 0x02) != 0;
|
||||
consoleActualClimatePack = payload[2];
|
||||
consoleActualToggleBits = payload[3];
|
||||
consoleLastAcceptedSeq = payload[5];
|
||||
lastConsoleStatusTime = currentTime;
|
||||
consoleAlive = true;
|
||||
|
||||
if (consoleCommandInFlight &&
|
||||
consoleLastAcceptedSeq == consoleCommandInFlightSeq) {
|
||||
consoleCommandInFlight = false;
|
||||
}
|
||||
|
||||
handleConsoleButtonBits(payload[4], currentTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateConsoleAlive(unsigned long currentTime) {
|
||||
bool aliveNow = chaegeonCanReady &&
|
||||
lastConsoleStatusTime != 0 &&
|
||||
currentTime - lastConsoleStatusTime <= CONSOLE_CAN_TIMEOUT_MS;
|
||||
|
||||
if (!aliveNow && consoleAlive) {
|
||||
consoleStartupApplyDone = false;
|
||||
consoleCommandInFlight = false;
|
||||
clearConsolePulseQueue();
|
||||
}
|
||||
|
||||
consoleAlive = aliveNow;
|
||||
}
|
||||
|
||||
void handleConsoleButtonBits(byte newButtonBits, unsigned long currentTime) {
|
||||
consolePreviousButtonBits = consoleButtonBits;
|
||||
consoleButtonBits = newButtonBits;
|
||||
|
||||
for (byte channel = 0; channel < CONSOLE_CHANNEL_COUNT; channel++) {
|
||||
bool wasPressed = (consolePreviousButtonBits & (1 << channel)) != 0;
|
||||
bool isPressed = (consoleButtonBits & (1 << channel)) != 0;
|
||||
if (wasPressed && !isPressed) {
|
||||
consoleManualLearnPending[channel] = true;
|
||||
consoleManualLearnTime[channel] = currentTime + CONSOLE_MANUAL_LEARN_SETTLE_MS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateConsoleManualLearn(unsigned long currentTime) {
|
||||
for (byte channel = 0; channel < CONSOLE_CHANNEL_COUNT; channel++) {
|
||||
if (!consoleManualLearnPending[channel]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((long)(currentTime - consoleManualLearnTime[channel]) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
consoleManualLearnPending[channel] = false;
|
||||
learnConsoleManualChannel(channel);
|
||||
}
|
||||
}
|
||||
|
||||
void learnConsoleManualChannel(byte channel) {
|
||||
if (channel <= CH_PASSENGER_HEAT) {
|
||||
setConsoleClimateTarget(channel, getConsoleClimateActual(channel));
|
||||
return;
|
||||
}
|
||||
|
||||
byte toggleIndex = channel - CH_STEERING_HEAT;
|
||||
if (toggleIndex < CONSOLE_TOGGLE_COUNT) {
|
||||
setConsoleToggleTarget(toggleIndex, getConsoleToggleActual(toggleIndex));
|
||||
}
|
||||
}
|
||||
|
||||
void updateConsoleAutomation(unsigned long currentTime) {
|
||||
bool ignitionOn = ignitionState == HIGH;
|
||||
if (ignitionOn && !previousConsoleIgnitionOn) {
|
||||
consoleIgnitionApplyPending = true;
|
||||
}
|
||||
previousConsoleIgnitionOn = ignitionOn;
|
||||
|
||||
if (!consoleAlive || !consoleReadyForNewSequence()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!consoleStartupApplyDone && currentTime >= CONSOLE_STARTUP_DELAY_MS) {
|
||||
applyConsoleClimateTargets(true);
|
||||
if (ignitionOn) {
|
||||
applyConsoleIgnitionTargets();
|
||||
consoleIgnitionApplyPending = false;
|
||||
}
|
||||
consoleStartupApplyDone = true;
|
||||
lastConsoleClimateRefreshTime = currentTime;
|
||||
lastConsoleSteeringRestartTime = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignitionOn && consoleIgnitionApplyPending) {
|
||||
applyConsoleIgnitionTargets();
|
||||
consoleIgnitionApplyPending = false;
|
||||
lastConsoleSteeringRestartTime = currentTime;
|
||||
previousConsoleIgnitionOn = ignitionOn;
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTime - lastConsoleClimateRefreshTime >= CONSOLE_MEMORY_REFRESH_INTERVAL_MS) {
|
||||
applyConsoleClimateTargets(true);
|
||||
if (ignitionOn) {
|
||||
applyConsoleIgnitionTargets();
|
||||
consoleIgnitionApplyPending = false;
|
||||
}
|
||||
lastConsoleClimateRefreshTime = currentTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ignitionOn &&
|
||||
consoleToggleTarget[TG_STEERING_HEAT] &&
|
||||
currentTime - lastConsoleSteeringRestartTime >= CONSOLE_STEERING_RESTART_INTERVAL_MS) {
|
||||
restartConsoleSteeringHeat();
|
||||
lastConsoleSteeringRestartTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
bool consoleReadyForNewSequence() {
|
||||
return !consoleBusy &&
|
||||
!consoleCommandInFlight &&
|
||||
consolePulseQueueHead == consolePulseQueueTail;
|
||||
}
|
||||
|
||||
void applyConsoleClimateTargets(bool force) {
|
||||
for (byte climate = 0; climate < CONSOLE_CLIMATE_COUNT; climate++) {
|
||||
ensureConsoleClimateTarget(climate, force);
|
||||
}
|
||||
}
|
||||
|
||||
void applyConsoleIgnitionTargets() {
|
||||
for (byte toggle = 0; toggle < CONSOLE_TOGGLE_COUNT; toggle++) {
|
||||
ensureConsoleToggleTarget(toggle);
|
||||
}
|
||||
}
|
||||
|
||||
void ensureConsoleClimateTarget(byte climate, bool force) {
|
||||
byte target = consoleClimateTarget[climate];
|
||||
byte actual = getConsoleClimateActual(climate);
|
||||
|
||||
if (target == 0) {
|
||||
if (actual != 0) {
|
||||
enqueueConsolePulse(climate, CONSOLE_CLIMATE_LONG_OFF_MS, CONSOLE_CLIMATE_LONG_OFF_GAP_MS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && actual == target) {
|
||||
return;
|
||||
}
|
||||
|
||||
enqueueConsoleClimateSet(climate, target);
|
||||
}
|
||||
|
||||
void ensureConsoleToggleTarget(byte toggle) {
|
||||
if (getConsoleToggleActual(toggle) == consoleToggleTarget[toggle]) {
|
||||
return;
|
||||
}
|
||||
|
||||
enqueueConsolePulse(toggle + CH_STEERING_HEAT,
|
||||
CONSOLE_SHORT_PRESS_MS,
|
||||
CONSOLE_SHORT_PRESS_GAP_MS);
|
||||
}
|
||||
|
||||
void restartConsoleSteeringHeat() {
|
||||
if (getConsoleToggleActual(TG_STEERING_HEAT)) {
|
||||
enqueueConsolePulse(CH_STEERING_HEAT,
|
||||
CONSOLE_SHORT_PRESS_MS,
|
||||
CONSOLE_STEERING_RESTART_GAP_MS);
|
||||
enqueueConsolePulse(CH_STEERING_HEAT,
|
||||
CONSOLE_SHORT_PRESS_MS,
|
||||
CONSOLE_SHORT_PRESS_GAP_MS);
|
||||
} else {
|
||||
enqueueConsolePulse(CH_STEERING_HEAT,
|
||||
CONSOLE_SHORT_PRESS_MS,
|
||||
CONSOLE_SHORT_PRESS_GAP_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void enqueueConsoleClimateSet(byte climate, byte targetLevel) {
|
||||
enqueueConsolePulse(climate, CONSOLE_CLIMATE_LONG_OFF_MS, CONSOLE_CLIMATE_LONG_OFF_GAP_MS);
|
||||
|
||||
byte shortPresses = getConsoleShortPressCount(targetLevel);
|
||||
for (byte i = 0; i < shortPresses; i++) {
|
||||
enqueueConsolePulse(climate, CONSOLE_SHORT_PRESS_MS, CONSOLE_SHORT_PRESS_GAP_MS);
|
||||
}
|
||||
}
|
||||
|
||||
byte getConsoleShortPressCount(byte level) {
|
||||
if (level == 3) {
|
||||
return 1;
|
||||
}
|
||||
if (level == 2) {
|
||||
return 2;
|
||||
}
|
||||
if (level == 1) {
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte getConsoleClimateActual(byte climate) {
|
||||
return (consoleActualClimatePack >> (climate * 2)) & 0x03;
|
||||
}
|
||||
|
||||
bool getConsoleToggleActual(byte toggle) {
|
||||
return (consoleActualToggleBits & (1 << toggle)) != 0;
|
||||
}
|
||||
|
||||
void setConsoleClimateTarget(byte climate, byte level) {
|
||||
if (level > 3) {
|
||||
level = 3;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if (consoleClimateTarget[climate] != level) {
|
||||
consoleClimateTarget[climate] = level;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (level > 0) {
|
||||
byte opposite = CONSOLE_OPPOSITE_CLIMATE[climate];
|
||||
if (consoleClimateTarget[opposite] != 0) {
|
||||
consoleClimateTarget[opposite] = 0;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
saveConsoleSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void setConsoleToggleTarget(byte toggle, bool enabled) {
|
||||
if (consoleToggleTarget[toggle] == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
consoleToggleTarget[toggle] = enabled;
|
||||
saveConsoleSettings();
|
||||
}
|
||||
|
||||
void updateConsoleCommandSender(unsigned long currentTime) {
|
||||
if (!chaegeonCanReady || !consoleAlive || consoleBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (consoleCommandInFlight) {
|
||||
if (currentTime - lastConsoleCommandTime >= CONSOLE_COMMAND_HOLD_MS) {
|
||||
consoleCommandInFlight = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (consolePulseQueueHead == consolePulseQueueTail) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte channel = consolePulseQueueChannel[consolePulseQueueHead];
|
||||
unsigned int onMs = consolePulseQueueOnMs[consolePulseQueueHead];
|
||||
unsigned int gapMs = consolePulseQueueGapMs[consolePulseQueueHead];
|
||||
|
||||
byte seq = nextConsoleCommandSeq();
|
||||
byte payload[8] = {
|
||||
1,
|
||||
1,
|
||||
channel,
|
||||
(byte)(onMs & 0xFF),
|
||||
(byte)(onMs >> 8),
|
||||
(byte)(gapMs & 0xFF),
|
||||
(byte)(gapMs >> 8),
|
||||
seq
|
||||
};
|
||||
|
||||
if (chaegeonCan.sendMsgBuf(CAN_ID_CONSOLE_COMMAND, 0, 8, payload) == CAN_OK) {
|
||||
consolePulseQueueHead = nextConsoleQueueIndex(consolePulseQueueHead);
|
||||
consoleCommandInFlight = true;
|
||||
consoleCommandInFlightSeq = seq;
|
||||
lastConsoleCommandTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
byte nextConsoleCommandSeq() {
|
||||
consoleCommandSeq++;
|
||||
if (consoleCommandSeq == 0) {
|
||||
consoleCommandSeq = 1;
|
||||
}
|
||||
return consoleCommandSeq;
|
||||
}
|
||||
|
||||
void enqueueConsolePulse(byte channel, unsigned int onMs, unsigned int gapMs) {
|
||||
byte nextTail = nextConsoleQueueIndex(consolePulseQueueTail);
|
||||
if (nextTail == consolePulseQueueHead || channel >= CONSOLE_CHANNEL_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
consolePulseQueueChannel[consolePulseQueueTail] = channel;
|
||||
consolePulseQueueOnMs[consolePulseQueueTail] = onMs;
|
||||
consolePulseQueueGapMs[consolePulseQueueTail] = gapMs;
|
||||
consolePulseQueueTail = nextTail;
|
||||
}
|
||||
|
||||
void clearConsolePulseQueue() {
|
||||
consolePulseQueueHead = consolePulseQueueTail;
|
||||
}
|
||||
|
||||
byte consolePulseQueueLength() {
|
||||
if (consolePulseQueueTail >= consolePulseQueueHead) {
|
||||
return consolePulseQueueTail - consolePulseQueueHead;
|
||||
}
|
||||
return CONSOLE_PULSE_QUEUE_SIZE - consolePulseQueueHead + consolePulseQueueTail;
|
||||
}
|
||||
|
||||
byte nextConsoleQueueIndex(byte index) {
|
||||
index++;
|
||||
if (index >= CONSOLE_PULSE_QUEUE_SIZE) {
|
||||
index = 0;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
void loadConsoleSettings() {
|
||||
if (EEPROM.read(EEPROM_CONSOLE_MAGIC_ADDR) != EEPROM_CONSOLE_MAGIC_VALUE) {
|
||||
EEPROM.update(EEPROM_CONSOLE_MAGIC_ADDR, EEPROM_CONSOLE_MAGIC_VALUE);
|
||||
for (byte i = 0; i < CONSOLE_CLIMATE_COUNT; i++) {
|
||||
EEPROM.update(EEPROM_CONSOLE_CLIMATE_TARGET_ADDR + i, 0);
|
||||
}
|
||||
for (byte i = 0; i < CONSOLE_TOGGLE_COUNT; i++) {
|
||||
EEPROM.update(EEPROM_CONSOLE_TOGGLE_TARGET_ADDR + i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (byte i = 0; i < CONSOLE_CLIMATE_COUNT; i++) {
|
||||
byte value = EEPROM.read(EEPROM_CONSOLE_CLIMATE_TARGET_ADDR + i);
|
||||
consoleClimateTarget[i] = value <= 3 ? value : 0;
|
||||
}
|
||||
for (byte i = 0; i < CONSOLE_TOGGLE_COUNT; i++) {
|
||||
consoleToggleTarget[i] = EEPROM.read(EEPROM_CONSOLE_TOGGLE_TARGET_ADDR + i) != 0;
|
||||
}
|
||||
|
||||
if (consoleClimateTarget[CH_DRIVER_VENT] > 0 &&
|
||||
consoleClimateTarget[CH_DRIVER_HEAT] > 0) {
|
||||
consoleClimateTarget[CH_DRIVER_HEAT] = 0;
|
||||
}
|
||||
if (consoleClimateTarget[CH_PASSENGER_VENT] > 0 &&
|
||||
consoleClimateTarget[CH_PASSENGER_HEAT] > 0) {
|
||||
consoleClimateTarget[CH_PASSENGER_HEAT] = 0;
|
||||
}
|
||||
saveConsoleSettings();
|
||||
}
|
||||
|
||||
void saveConsoleSettings() {
|
||||
for (byte i = 0; i < CONSOLE_CLIMATE_COUNT; i++) {
|
||||
EEPROM.update(EEPROM_CONSOLE_CLIMATE_TARGET_ADDR + i, consoleClimateTarget[i]);
|
||||
}
|
||||
for (byte i = 0; i < CONSOLE_TOGGLE_COUNT; i++) {
|
||||
EEPROM.update(EEPROM_CONSOLE_TOGGLE_TARGET_ADDR + i, consoleToggleTarget[i] ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void setupChaegeonCan() {
|
||||
if (chaegeonCan.begin(MCP_ANY, CAN_125KBPS, MCP_8MHZ) == CAN_OK) {
|
||||
chaegeonCan.setMode(MCP_NORMAL);
|
||||
chaegeonCanReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
void updateChaegeonCanHeartbeat(unsigned long currentTime) {
|
||||
if (!chaegeonCanReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentTime - lastCanHeartbeatTime < CAN_HEARTBEAT_INTERVAL_MS) {
|
||||
return;
|
||||
}
|
||||
lastCanHeartbeatTime = currentTime;
|
||||
|
||||
byte flags = 0;
|
||||
if (ignitionState == HIGH) {
|
||||
flags |= 0x01;
|
||||
}
|
||||
if (parkingState == HIGH) {
|
||||
flags |= 0x02;
|
||||
}
|
||||
if (getRawDriveMode() == HIGH) {
|
||||
flags |= 0x04;
|
||||
}
|
||||
if (confirmedDriveMode == HIGH) {
|
||||
flags |= 0x08;
|
||||
}
|
||||
if (isStabilizing) {
|
||||
flags |= 0x10;
|
||||
}
|
||||
if (currentSeatAction != 0 || requestedSeatAction != 0) {
|
||||
flags |= 0x20;
|
||||
}
|
||||
if (autoCalibrationPending || autoCalibrationState != CALIBRATION_IDLE) {
|
||||
flags |= 0x40;
|
||||
}
|
||||
|
||||
uint16_t raw = (uint16_t)parkingRawValue;
|
||||
uint16_t uptimeSeconds = (uint16_t)(currentTime / 1000UL);
|
||||
byte payload[8] = {
|
||||
1,
|
||||
flags,
|
||||
chaegeonCanSeq++,
|
||||
useCount,
|
||||
(byte)(raw & 0xFF),
|
||||
(byte)(raw >> 8),
|
||||
(byte)(uptimeSeconds & 0xFF),
|
||||
(byte)(uptimeSeconds >> 8)
|
||||
};
|
||||
|
||||
chaegeonCan.sendMsgBuf(CAN_ID_MAIN_HEARTBEAT, 0, 8, payload);
|
||||
}
|
||||
|
||||
void setRelay(int relayPin, boolean state) {
|
||||
digitalWrite(relayPin, state ? LOW : HIGH);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user