From 696f2735fff95fa6007427a9103fc27e070cd7bb Mon Sep 17 00:00:00 2001 From: Timothy Yin Date: Sun, 19 Apr 2026 23:20:50 +0800 Subject: [PATCH] feat(firmware): IM1281C driver --- hardware/firmware/lib/IM1281C/src/IM1281C.cpp | 125 ++++++++++++++++++ hardware/firmware/lib/IM1281C/src/IM1281C.h | 65 +++++++++ hardware/firmware/platformio.ini | 1 + hardware/firmware/src/main.cpp | 43 +++++- 4 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 hardware/firmware/lib/IM1281C/src/IM1281C.cpp create mode 100644 hardware/firmware/lib/IM1281C/src/IM1281C.h diff --git a/hardware/firmware/lib/IM1281C/src/IM1281C.cpp b/hardware/firmware/lib/IM1281C/src/IM1281C.cpp new file mode 100644 index 0000000..9f73a88 --- /dev/null +++ b/hardware/firmware/lib/IM1281C/src/IM1281C.cpp @@ -0,0 +1,125 @@ +#include "IM1281C.h" + +IM1281C::IM1281C() + : _serial(nullptr), + _slaveAddress(1), + _lastAResult(0xFF), + _lastBResult(0xFF) +{ +} + +bool IM1281C::begin(HardwareSerial &serial, + int8_t rxPin, + int8_t txPin, + uint8_t slaveAddress, + uint32_t baudRate, + uint32_t serialConfig) +{ + _serial = &serial; + _slaveAddress = slaveAddress; + _a = IM1281CAData{}; + _b = IM1281CBData{}; + _lastAResult = 0xFF; + _lastBResult = 0xFF; + + _serial->begin(baudRate, serialConfig, rxPin, txPin); + _node.begin(_slaveAddress, *_serial); + return true; +} + +bool IM1281C::readA() +{ + return readAll(); +} + +bool IM1281C::readB() +{ + return readAll(); +} + +bool IM1281C::readAll() +{ + if (_serial == nullptr) + { + return false; + } + + // IM1281C datasheet addresses are effectively 32-bit data items. + // Requesting 0x0010 from 0x0048 yields 64 data bytes (0x40), which covers A/B block. + const uint8_t result = _node.readHoldingRegisters(0x0048, 16); + _lastAResult = result; + _lastBResult = result; + + if (result != _node.ku8MBSuccess) + { + _a.valid = false; + _b.valid = false; + return false; + } + + const uint32_t aVoltageRaw = combineWords(_node.getResponseBuffer(0), _node.getResponseBuffer(1)); + const uint32_t aCurrentRaw = combineWords(_node.getResponseBuffer(2), _node.getResponseBuffer(3)); + const uint32_t aPowerRaw = combineWords(_node.getResponseBuffer(4), _node.getResponseBuffer(5)); + const uint32_t aEnergyRaw = combineWords(_node.getResponseBuffer(6), _node.getResponseBuffer(7)); + const uint32_t aPfRaw = combineWords(_node.getResponseBuffer(8), _node.getResponseBuffer(9)); + const uint32_t aCo2Raw = combineWords(_node.getResponseBuffer(10), _node.getResponseBuffer(11)); + const uint32_t aTempRaw = combineWords(_node.getResponseBuffer(12), _node.getResponseBuffer(13)); + const uint32_t aFreqRaw = combineWords(_node.getResponseBuffer(14), _node.getResponseBuffer(15)); + + _a.voltage = scaleValue(aVoltageRaw, 0.0001f); + _a.current = scaleValue(aCurrentRaw, 0.0001f); + _a.power = scaleValue(aPowerRaw, 0.0001f); + _a.energy = scaleValue(aEnergyRaw, 0.0001f); + _a.powerFactor = scaleValue(aPfRaw, 0.001f); + _a.co2 = scaleValue(aCo2Raw, 0.0001f); + _a.temperature = scaleValue(aTempRaw, 0.01f); + _a.frequency = scaleValue(aFreqRaw, 0.01f); + _a.valid = true; + + const uint32_t bVoltageRaw = combineWords(_node.getResponseBuffer(16), _node.getResponseBuffer(17)); + const uint32_t bCurrentRaw = combineWords(_node.getResponseBuffer(18), _node.getResponseBuffer(19)); + const uint32_t bPowerRaw = combineWords(_node.getResponseBuffer(20), _node.getResponseBuffer(21)); + const uint32_t bEnergyRaw = combineWords(_node.getResponseBuffer(22), _node.getResponseBuffer(23)); + const uint32_t bPfRaw = combineWords(_node.getResponseBuffer(24), _node.getResponseBuffer(25)); + const uint32_t bCo2Raw = combineWords(_node.getResponseBuffer(26), _node.getResponseBuffer(27)); + + _b.voltage = scaleValue(bVoltageRaw, 0.0001f); + _b.current = scaleValue(bCurrentRaw, 0.0001f); + _b.power = scaleValue(bPowerRaw, 0.0001f); + _b.energy = scaleValue(bEnergyRaw, 0.0001f); + _b.powerFactor = scaleValue(bPfRaw, 0.001f); + _b.co2 = scaleValue(bCo2Raw, 0.0001f); + _b.valid = true; + + return true; +} + +const IM1281CAData &IM1281C::a() const +{ + return _a; +} + +const IM1281CBData &IM1281C::b() const +{ + return _b; +} + +uint8_t IM1281C::lastAResult() const +{ + return _lastAResult; +} + +uint8_t IM1281C::lastBResult() const +{ + return _lastBResult; +} + +uint32_t IM1281C::combineWords(uint16_t highWord, uint16_t lowWord) +{ + return (static_cast(highWord) << 16) | static_cast(lowWord); +} + +float IM1281C::scaleValue(uint32_t raw, float scale) +{ + return static_cast(raw) * scale; +} diff --git a/hardware/firmware/lib/IM1281C/src/IM1281C.h b/hardware/firmware/lib/IM1281C/src/IM1281C.h new file mode 100644 index 0000000..44e1b2d --- /dev/null +++ b/hardware/firmware/lib/IM1281C/src/IM1281C.h @@ -0,0 +1,65 @@ +#ifndef HELIOS_IM1281C_H +#define HELIOS_IM1281C_H + +#include +#include + +struct IM1281CAData +{ + float voltage = 0.0f; + float current = 0.0f; + float power = 0.0f; + float energy = 0.0f; + float powerFactor = 0.0f; + float co2 = 0.0f; + float temperature = 0.0f; + float frequency = 0.0f; + bool valid = false; +}; + +struct IM1281CBData +{ + float voltage = 0.0f; + float current = 0.0f; + float power = 0.0f; + float energy = 0.0f; + float powerFactor = 0.0f; + float co2 = 0.0f; + bool valid = false; +}; + +class IM1281C +{ +public: + IM1281C(); + + bool begin(HardwareSerial &serial, + int8_t rxPin, + int8_t txPin, + uint8_t slaveAddress = 1, + uint32_t baudRate = 4800, + uint32_t serialConfig = SERIAL_8N1); + + bool readA(); + bool readB(); + bool readAll(); + + const IM1281CAData &a() const; + const IM1281CBData &b() const; + uint8_t lastAResult() const; + uint8_t lastBResult() const; + +private: + static uint32_t combineWords(uint16_t highWord, uint16_t lowWord); + static float scaleValue(uint32_t raw, float scale); + + ModbusMaster _node; + HardwareSerial *_serial; + uint8_t _slaveAddress; + uint8_t _lastAResult; + uint8_t _lastBResult; + IM1281CAData _a; + IM1281CBData _b; +}; + +#endif // HELIOS_IM1281C_H diff --git a/hardware/firmware/platformio.ini b/hardware/firmware/platformio.ini index b76c621..0917285 100644 --- a/hardware/firmware/platformio.ini +++ b/hardware/firmware/platformio.ini @@ -18,5 +18,6 @@ lib_deps = miguelbalboa/MFRC522@^1.4.12 tzapu/WiFiManager@^2.0.17 adafruit/Adafruit SSD1306@^2.5.16 + 4-20ma/ModbusMaster@^2.0.1 build_flags = -DMO_PLATFORM=MO_PLATFORM_ARDUINO -DMO_MG_USE_VERSION=MO_MG_V715 -DMO_NUMCONNECTORS=3 board_build.partitions = partitions.csv diff --git a/hardware/firmware/src/main.cpp b/hardware/firmware/src/main.cpp index 956aea9..5f31402 100644 --- a/hardware/firmware/src/main.cpp +++ b/hardware/firmware/src/main.cpp @@ -15,6 +15,7 @@ #include "esp_system.h" #include "config.h" +#include "IM1281C.h" #include "pins.h" /* LED State Enum */ @@ -95,6 +96,7 @@ Adafruit_SSD1306 display(128, 64, &Wire, -1); SmartLed leds(LED_WS2812B, LED_COUNT, LED_PIN, 0, DoubleBuffer); MFRC522 rfid(PIN_RC_CS, PIN_RC_RST); +IM1281C im1281c; static bool isConnectorPlugged(unsigned int connectorId) { @@ -303,6 +305,42 @@ static void expireAuthWaitIfNeeded() } } +static void pollIm1281c() +{ + static unsigned long s_last_poll_ms = 0; + const unsigned long now = millis(); + if ((now - s_last_poll_ms) < 1000) + { + return; + } + s_last_poll_ms = now; + + if (!im1281c.readAll()) + { + Serial.printf("[IM1281C] read failed: A=%u B=%u\n", im1281c.lastAResult(), im1281c.lastBResult()); + return; + } + + const IM1281CAData &a = im1281c.a(); + const IM1281CBData &b = im1281c.b(); + Serial.printf("[IM1281C] A: U=%.4fV I=%.4fA P=%.4fW E=%.4fkWh PF=%.3f CO2=%.4fkg T=%.2fC F=%.2fHz\n", + a.voltage, + a.current, + a.power, + a.energy, + a.powerFactor, + a.co2, + a.temperature, + a.frequency); + Serial.printf("[IM1281C] B: U=%.4fV I=%.4fA P=%.4fW E=%.4fkWh PF=%.3f CO2=%.4fkg\n", + b.voltage, + b.current, + b.power, + b.energy, + b.powerFactor, + b.co2); +} + /* LED Control Functions */ void updateLED() { @@ -435,8 +473,8 @@ void setup() // Initialize I2C for OLED (from schematic pin map) Wire.begin(PIN_OLED_SDA, PIN_OLED_SCL); - // Initialize UART2 for IM1281C (U2) - Serial2.begin(BAUD_IM1281C, SERIAL_8N1, PIN_U2RXD, PIN_U2TXD); + // Initialize IM1281C over UART2 (default: address 1, 4800bps, 8N1) + im1281c.begin(Serial2, PIN_U2RXD, PIN_U2TXD); // Initialize SPI bus for RC522 SPI.begin(PIN_RC_SCK, PIN_RC_MISO, PIN_RC_MOSI, PIN_RC_CS); @@ -773,6 +811,7 @@ void loop() stopIfUnplugged(); pollRfidCard(); expireAuthWaitIfNeeded(); + pollIm1281c(); mg_mgr_poll(&mgr, 10); mocpp_loop();