From 3bd9cf9626475e17d63bb7280cb846dda2afcc41 Mon Sep 17 00:00:00 2001 From: Ishan Date: Mon, 25 May 2026 14:11:30 -0400 Subject: [PATCH] bump --- src/main.cpp | 546 +++++++++++++++++++++++++++------------------------ 1 file changed, 291 insertions(+), 255 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 01c3030..640b39d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,3 @@ - #define RXD_PIN 5 #define TXD_PIN 6 #define CONTROL_PIN 4 @@ -17,33 +16,23 @@ #include constexpr uint8_t LCD_I2C_ADDRESS = 0x27; -hd44780_I2Cexp lcd(LCD_I2C_ADDRESS, 20, 4); +constexpr uint8_t LCD_COLS = 20; +constexpr uint8_t LCD_ROWS = 4; +constexpr uint8_t WATER_ROW = LCD_ROWS - 1; + +hd44780_I2Cexp lcd(LCD_I2C_ADDRESS, LCD_COLS, LCD_ROWS); constexpr uint8_t RELAY_CONTROL_ADDRESS = 0x21; constexpr uint8_t BUTTONS_CONTROL_ADDRESS = 0x20; +constexpr unsigned long COMMAND_INTERVAL_MS = 1000; +constexpr uint16_t MODBUS_RESPONSE_TIMEOUT_MS = 200; + uint8_t modbusReadCmd[] = {0x01, 0x03, 0x00, 0x04, 0x00, 0x01}; -unsigned long lastCommandTime = 0; -const unsigned long COMMAND_INTERVAL = 1000; + volatile bool g_buttonInterruptFired = false; int g_waterLevelCm = -1; -void addWaterLevel(); -// String *descr[] = {"NA", "Pump", "Barrel -> Back Left", "Back Left -> Barrel", "Barrel to Hose", "CW to Hose", "NA", "NA"}; - -void printBin(byte aByte) -{ - for (int8_t aBit = 7; aBit >= 0; aBit--) - { - // Serial.print(aBit); - // Serial.write(":"); - Serial.write(bitRead(aByte, aBit) ? '1' : '0'); - // Serial.write(" "); - } - Serial.println(); -} - -// NA, Pump, Barrel -> Back Left, Back Left -> Barrel, Barrel to Hose, CW to Hose, NA, NA struct Action { const char *name; @@ -70,130 +59,36 @@ inline Action g_actions[] = { {"NotAssigned", 0b00000000, 0b00000000, false}, }; +uint8_t current_stat = 0b00000000; +uint8_t current_mask = 0b00000000; + +// Forward declarations +void updateLCDDisplay(); +void addWaterLevel(); +void applyRelayStateIfChanged(); +void generateMasksByActive(); +void updateStateByLastAction(ActionId lastAction); +void readAndPrintButtons(); +void pollSerial2WaterLevel(); +uint16_t calculateCRC16(const uint8_t *data, uint8_t length); +void sendCommand(const uint8_t *cmd, size_t len); +void sendModbusCommand(); +void scanI2CDevices(); +void buildTrimmedWaterLevelText(char *outBuffer, size_t outBufferSize); +void buildRightJustifiedWaterRow(char *rowBuffer, size_t rowBufferSize); + inline Action &getAction(ActionId id) { return g_actions[static_cast(id)]; } -uint8_t current_stat = 0b00000000; -uint8_t current_mask = 0b00000000; - -void updateLCDDisplay() +void printBin(byte aByte) { - lcd.clear(); - uint8_t c_row = 0; - for (size_t i = 0; i < static_cast(ActionId::Count); ++i) + for (int8_t aBit = 7; aBit >= 0; --aBit) { - if (g_actions[i].active) - { - lcd.setCursor(0, c_row); - lcd.print(g_actions[i].name); - c_row++; - } - } - addWaterLevel(); -} - -void generateMasksByActive() -{ - current_stat = 0b00000000; - current_mask = 0b00000000; - for (size_t i = 0; i < static_cast(ActionId::Count); ++i) - { - Action &c_action = g_actions[i]; - if (c_action.active) - { - current_mask = current_mask | c_action.mask; - current_stat = (current_stat & ~c_action.mask) | (c_action.stat & c_action.mask); - } - } -} - -void updateStateByLastAction(ActionId LastAction) -{ - Action &c_action = getAction(LastAction); - - if (c_action.active) - { - c_action.active = false; - generateMasksByActive(); - updateLCDDisplay(); - return; - } - uint8_t change_stat = c_action.stat; - uint8_t change_mask = c_action.mask; - - uint8_t old_stat = current_stat; - uint8_t old_mask = current_mask; - - uint8_t potential_new_stat = (current_stat & ~change_mask) | (change_stat & change_mask); - - uint8_t potential_old_change = (potential_new_stat ^ old_stat) & old_mask; - - if (potential_old_change > 0) - { - Serial.print(c_action.name); - Serial.print(" collides with existing. Setting new mask to this one"); - Serial.println(); - - current_stat = change_stat; - current_mask = change_mask; - for (size_t i = 0; i < static_cast(ActionId::Count); ++i) - { - g_actions[i].active = false; - } - c_action.active = true; - } - else - { - Serial.print(c_action.name); - Serial.print(" has no collisions with existing. Setting updated mask"); - Serial.println(); - current_stat = potential_new_stat; - current_mask = old_mask | change_mask; - c_action.active = true; - } - Serial.print("Stat: "); - printBin(current_stat); - Serial.print("Mask: "); - printBin(current_mask); - updateLCDDisplay(); -} - -void applyRelayStateIfChanged() -{ - static uint8_t lastRelayValue = 0xFF; - // Serial.println(current_stat, BIN); - // Serial.println(current_mask, BIN); - // uint8_t relayValue = static_cast(~((current_stat & current_mask) | static_cast(~current_mask))); - - uint8_t relayValue = ~current_stat; - for (int8_t bit = 7; bit >= 0; --bit) - { - Serial.print((relayValue >> bit) & 0x01); + Serial.write(bitRead(aByte, aBit) ? '1' : '0'); } Serial.println(); - Wire.beginTransmission(RELAY_CONTROL_ADDRESS); - Wire.write(relayValue); - uint8_t error = Wire.endTransmission(); - - if (error == 0) - { - lastRelayValue = relayValue; - Serial.printf( - "Relay state updated: value=0x%02X stat=0x%02X mask=0x%02X\n", - relayValue, - current_stat, - current_mask); - } - else - { - Serial.printf( - "Relay write failed: addr=0x%02X error=%u value=0x%02X\n", - RELAY_CONTROL_ADDRESS, - error, - relayValue); - } } void buildTrimmedWaterLevelText(char *outBuffer, size_t outBufferSize) @@ -228,28 +123,157 @@ void buildTrimmedWaterLevelText(char *outBuffer, size_t outBufferSize) outBuffer[len] = '\0'; } -void addWaterLevel() +void buildRightJustifiedWaterRow(char *rowBuffer, size_t rowBufferSize) { - char text[24]; - buildTrimmedWaterLevelText(text, sizeof(text)); // e.g. "123 cm" - - size_t len = strlen(text); - if (len > 20) + if (rowBuffer == nullptr || rowBufferSize < (LCD_COLS + 1)) { - len = 20; - text[20] = '\0'; + return; } - const uint8_t row = 3; // last row on 20x4 - const uint8_t startCol = static_cast(20 - len); // right-justified + memset(rowBuffer, ' ', LCD_COLS); + rowBuffer[LCD_COLS] = '\0'; - // Clear row first so old characters do not remain - // lcd.setCursor(0, row); - // lcd.print(" "); // 20 spaces + char text[24]; + buildTrimmedWaterLevelText(text, sizeof(text)); - // Print at exact right-justified cursor position - lcd.setCursor(startCol, row); - lcd.print(text); + size_t len = strlen(text); + if (len > LCD_COLS) + { + len = LCD_COLS; + } + + const size_t startCol = LCD_COLS - len; + memcpy(rowBuffer + startCol, text, len); +} + +void addWaterLevel() +{ + char row[LCD_COLS + 1]; + buildRightJustifiedWaterRow(row, sizeof(row)); + lcd.setCursor(0, WATER_ROW); + lcd.print(row); +} + +void updateLCDDisplay() +{ + lcd.clear(); + + uint8_t c_row = 0; + for (size_t i = 0; i < static_cast(ActionId::Count); ++i) + { + if (g_actions[i].active) + { + if (c_row >= WATER_ROW) // keep last row for water level + { + break; + } + lcd.setCursor(0, c_row); + lcd.print(g_actions[i].name); + ++c_row; + } + } + + addWaterLevel(); +} + +void generateMasksByActive() +{ + current_stat = 0; + current_mask = 0; + + for (size_t i = 0; i < static_cast(ActionId::Count); ++i) + { + Action &c_action = g_actions[i]; + if (c_action.active) + { + current_mask |= c_action.mask; + current_stat = (current_stat & ~c_action.mask) | (c_action.stat & c_action.mask); + } + } +} + +void updateStateByLastAction(ActionId lastAction) +{ + Action &c_action = getAction(lastAction); + + if (c_action.active) + { + c_action.active = false; + generateMasksByActive(); + applyRelayStateIfChanged(); + updateLCDDisplay(); + return; + } + + const uint8_t change_stat = c_action.stat; + const uint8_t change_mask = c_action.mask; + + const uint8_t old_stat = current_stat; + const uint8_t old_mask = current_mask; + + const uint8_t potential_new_stat = (current_stat & ~change_mask) | (change_stat & change_mask); + const uint8_t potential_old_change = (potential_new_stat ^ old_stat) & old_mask; + + if (potential_old_change != 0) + { + Serial.print(c_action.name); + Serial.println(" collides with existing. Setting only this action."); + + current_stat = change_stat; + current_mask = change_mask; + + for (size_t i = 0; i < static_cast(ActionId::Count); ++i) + { + g_actions[i].active = false; + } + c_action.active = true; + } + else + { + Serial.print(c_action.name); + Serial.println(" has no collisions. Merging action."); + + current_stat = potential_new_stat; + current_mask = old_mask | change_mask; + c_action.active = true; + } + + Serial.print("Stat: "); + printBin(current_stat); + Serial.print("Mask: "); + printBin(current_mask); + + applyRelayStateIfChanged(); + updateLCDDisplay(); +} + +void applyRelayStateIfChanged() +{ + static uint8_t lastRelayValue = 0xFF; + const uint8_t relayValue = static_cast(~current_stat); + + if (relayValue == lastRelayValue) + { + return; + } + + Wire.beginTransmission(RELAY_CONTROL_ADDRESS); + Wire.write(relayValue); + uint8_t error = Wire.endTransmission(); + + if (error == 0) + { + lastRelayValue = relayValue; + Serial.printf( + "Relay state updated: value=0x%02X stat=0x%02X mask=0x%02X\n", + relayValue, current_stat, current_mask); + } + else + { + Serial.printf( + "Relay write failed: addr=0x%02X error=%u value=0x%02X\n", + RELAY_CONTROL_ADDRESS, error, relayValue); + } } void IRAM_ATTR onButtonInterrupt() @@ -257,155 +281,174 @@ void IRAM_ATTR onButtonInterrupt() g_buttonInterruptFired = true; } -uint16_t calculateCRC16(uint8_t *data, uint8_t length) +uint16_t calculateCRC16(const uint8_t *data, uint8_t length) { uint16_t crc = 0xFFFF; - for (int i = 0; i < length; i++) + for (uint8_t i = 0; i < length; ++i) { crc ^= data[i]; - for (int j = 0; j < 8; j++) + for (uint8_t j = 0; j < 8; ++j) { - crc = (crc & 0x0001) ? (crc >> 1) ^ 0xA001 : crc >> 1; + crc = (crc & 0x0001) ? (crc >> 1) ^ 0xA001 : (crc >> 1); } } return crc; } -void sendCommand(uint8_t *cmd, size_t len) +void sendCommand(const uint8_t *cmd, size_t len) { - uint16_t crc = calculateCRC16(cmd, len); - uint8_t crcL = crc & 0xFF; - uint8_t crcH = (crc >> 8) & 0xFF; + uint16_t crc = calculateCRC16(cmd, static_cast(len)); + uint8_t crcL = static_cast(crc & 0xFF); + uint8_t crcH = static_cast((crc >> 8) & 0xFF); Serial2.write(cmd, len); Serial2.write(crcL); Serial2.write(crcH); } -void sendModbusCommand() { sendCommand(modbusReadCmd, sizeof(modbusReadCmd)); } +void sendModbusCommand() +{ + sendCommand(modbusReadCmd, sizeof(modbusReadCmd)); +} void scanI2CDevices() { uint8_t found = 0; - for (uint8_t address = 1; address < 127; address++) + for (uint8_t address = 1; address < 127; ++address) { Wire.beginTransmission(address); uint8_t error = Wire.endTransmission(); if (error == 0) { Serial.printf("I2C device found at 0x%02X\n", address); - found++; + ++found; } } -} - -bool i2cAddressResponds(uint8_t address) -{ - Wire.beginTransmission(address); - return (Wire.endTransmission() == 0); + if (found == 0) + { + Serial.println("No I2C devices found."); + } } void readAndPrintButtons() { - Wire.beginTransmission(BUTTONS_CONTROL_ADDRESS); - Wire.write(0b11111110); // P0 LOW, all other pins released HIGH. + Wire.write(0b11111110); // P0 LOW, others HIGH Wire.endTransmission(); - Wire.requestFrom(BUTTONS_CONTROL_ADDRESS, (uint8_t)1); - if (Wire.available()) + Wire.requestFrom(BUTTONS_CONTROL_ADDRESS, static_cast(1)); + if (!Wire.available()) { - uint8_t portValue = Wire.read(); - bool buttonP3Pressed = (portValue & (1 << 3)) == 0; - bool buttonP4Pressed = (portValue & (1 << 4)) == 0; - bool buttonP5Pressed = (portValue & (1 << 5)) == 0; - bool buttonP6Pressed = (portValue & (1 << 6)) == 0; - bool buttonP7Pressed = (portValue & (1 << 7)) == 0; + return; + } - ActionId LastAction = ActionId::Count; + uint8_t portValue = Wire.read(); + bool buttonP3Pressed = (portValue & (1 << 3)) == 0; + bool buttonP4Pressed = (portValue & (1 << 4)) == 0; + bool buttonP5Pressed = (portValue & (1 << 5)) == 0; + bool buttonP6Pressed = (portValue & (1 << 6)) == 0; + bool buttonP7Pressed = (portValue & (1 << 7)) == 0; - if (buttonP3Pressed) - { - LastAction = ActionId::CityWaterToHose; - } - else if (buttonP4Pressed) - { - LastAction = ActionId::BarrelToHose; - } - else if (buttonP5Pressed) - { - LastAction = ActionId::BackLeftToBarrel; - } - else if (buttonP6Pressed) - { - LastAction = ActionId::BarrelToBackLeft; - } - else if (buttonP7Pressed) - { - // LastAction = ActionId::NotAssigned; - } + ActionId lastAction = ActionId::Count; - Serial.printf( - "INT Buttons P3=%d P4=%d P5=%d P6=%d P7=%d (raw=0x%02X)\n", - buttonP3Pressed, - buttonP4Pressed, - buttonP5Pressed, - buttonP6Pressed, - buttonP7Pressed, - portValue); + if (buttonP3Pressed) + { + lastAction = ActionId::CityWaterToHose; + } + else if (buttonP4Pressed) + { + lastAction = ActionId::BarrelToHose; + } + else if (buttonP5Pressed) + { + lastAction = ActionId::BackLeftToBarrel; + } + else if (buttonP6Pressed) + { + lastAction = ActionId::BarrelToBackLeft; + } + else if (buttonP7Pressed) + { + // reserved + } - if (LastAction != ActionId::Count) - { - updateStateByLastAction(LastAction); - } + Serial.printf( + "INT Buttons P3=%d P4=%d P5=%d P6=%d P7=%d (raw=0x%02X)\n", + buttonP3Pressed, buttonP4Pressed, buttonP5Pressed, buttonP6Pressed, buttonP7Pressed, portValue); + + if (lastAction != ActionId::Count) + { + updateStateByLastAction(lastAction); } } void pollSerial2WaterLevel() { - static uint8_t responseBuffer[256]; - static int responseIndex = 0; + static uint8_t responseBuffer[7]; + static uint8_t responseIndex = 0; static int lastPrintedWaterLevelCm = -1; + static unsigned long lastByteTimeMs = 0; + static unsigned long lastCommandTimeMs = 0; - sendModbusCommand(); + unsigned long now = millis(); + + // Periodic request + if ((now - lastCommandTimeMs) >= COMMAND_INTERVAL_MS) + { + lastCommandTimeMs = now; + sendModbusCommand(); + } + + // Reset partial frame on timeout + if (responseIndex > 0 && (now - lastByteTimeMs) > MODBUS_RESPONSE_TIMEOUT_MS) + { + responseIndex = 0; + } while (Serial2.available()) { - responseBuffer[responseIndex++] = Serial2.read(); - if (responseIndex >= 256) - { - responseIndex = 0; - } - } + uint8_t b = static_cast(Serial2.read()); + lastByteTimeMs = millis(); - if (responseIndex == 7) - { - uint16_t data = ((uint16_t)responseBuffer[3] << 8) | responseBuffer[4]; - uint16_t receivedCRC = ((uint16_t)responseBuffer[6] << 8) | responseBuffer[5]; - uint16_t calculatedCRC = calculateCRC16(responseBuffer, 5); - - if (receivedCRC == calculatedCRC) + if (responseIndex < sizeof(responseBuffer)) { - int waterLevelCm = data / 10; - if (waterLevelCm != lastPrintedWaterLevelCm) - { - lastPrintedWaterLevelCm = waterLevelCm; - g_waterLevelCm = waterLevelCm; - addWaterLevel(); - Serial.printf("Water level: %d cm (%d mm)\n", waterLevelCm, data); - } + responseBuffer[responseIndex++] = b; } else { - Serial.printf("CRC mismatch: received=0x%04X calculated=0x%04X\n", receivedCRC, calculatedCRC); + responseIndex = 0; + continue; } - responseIndex = 0; - } - else if (responseIndex != 0) - { - Serial.printf("Unexpected response length: %d (expected 7)\n", responseIndex); - responseIndex = 0; + if (responseIndex == 7) + { + uint16_t receivedCRC = static_cast(responseBuffer[5]) | + (static_cast(responseBuffer[6]) << 8); + uint16_t calculatedCRC = calculateCRC16(responseBuffer, 5); + + if (responseBuffer[0] == 0x01 && + responseBuffer[1] == 0x03 && + responseBuffer[2] == 0x02 && + receivedCRC == calculatedCRC) + { + uint16_t dataMm = (static_cast(responseBuffer[3]) << 8) | responseBuffer[4]; + int waterLevelCm = static_cast(dataMm / 10); + + if (waterLevelCm != lastPrintedWaterLevelCm) + { + lastPrintedWaterLevelCm = waterLevelCm; + g_waterLevelCm = waterLevelCm; + addWaterLevel(); + Serial.printf("Water level: %d cm (%u mm)\n", waterLevelCm, dataMm); + } + } + else + { + Serial.println("Invalid Modbus frame or CRC mismatch."); + } + + responseIndex = 0; + } } } @@ -427,9 +470,8 @@ void setup() scanI2CDevices(); - int lcdStatus = lcd.begin(20, 4); - lcd.clear(); - // updateLcdStatesIfChanged(); + lcd.begin(LCD_COLS, LCD_ROWS); + updateLCDDisplay(); Serial2.begin(MODBUS_BAUD, SERIAL_8N1, RXD_PIN, TXD_PIN); start = millis(); @@ -437,24 +479,18 @@ void setup() { delay(10); } + + applyRelayStateIfChanged(); } void loop() { - // NA, Pump, Barrel -> Back Left, Back Left -> Barrel, Barrel to Hose, CW to Hose, NA, NA - static unsigned long lastSerial2PollTime = 0; - if (g_buttonInterruptFired) { g_buttonInterruptFired = false; readAndPrintButtons(); } - if (millis() - lastSerial2PollTime >= COMMAND_INTERVAL) - { - lastSerial2PollTime = millis(); - pollSerial2WaterLevel(); - } - // updateLcdStatesIfChanged(); + pollSerial2WaterLevel(); delay(10); -} +} \ No newline at end of file