Epever solar chargers seem to be popular and they provide an open API that allows interfacing conveniently without needing to hack into the controller itself. This guide will present a PCB design involving an ESP8266 micro-controller and a MAX485 TTL converter that will be used to periodically poll an MPPT Epever solar charger and then publish the gathered data to an MQTT broker.
For this project the ESP and MAX485 TTL will be entirely powered by the Epever output via the RJ-45 cable that connects to the Epever controller.
The correct wiring between the RJ-45 Ethernet cable and the Epever controller is as follows:
MAX485 TTL | RJ-45 Ethernet Cable Color | Comment |
---|---|---|
Vcc () | orange | It has been noted by some that the Epever controller provides but for the Epever 40A the power provided via the EJ-45 jack seems to be which is perfect for powering the ESP8266 as well as the MAX485 TTL module. |
GND | brown | |
A | green | Used for the Modbus protocol over RS485. |
B | blue |
The wiring between the MAX485 TTL and the ESP is as follows:
ESP | MAX485 TTL | Comments |
---|---|---|
Vcc () | Vcc () | Vcc and GND are actually connected to the other side of the module that connects to the Epever controller via the RJ-45 Ethernet cable and they do not correspond to any MAX485 specific markings on the converter such as DI , DE , RE , RO that have different meanings and are connected to other pins on the ESP. |
GND | GND |
|
DI | TX | DI and RO on the MAX485 converter are connected to the serial pins on the ESP. |
RO | RX |
|
RE | D1 (GPIO) | These two GPIO pins are used as part of the Modbus protocol and are held low or high respectively depending on whether the ESP is transmitting or receiving. |
DE | D2 (GPIO) |
Here is some optional wiring that is not specific to this project but will enable some features that are accounted for in the Arduino code:
D0
to RST
via a resistor in order to enable the ESP to go into deep sleep; since we are going to be polling the Epever solar controller, realistically speaking a data resolution of 1 minute should be sufficient such that between reading the Epever solar controller, the ESP can go into deep sleep thereby sparing even more energy.Note that in this image, the WeMoS D1 Mini Pro is removed from its socket.
The circuit is not that difficult to make but we were mindful with some things:
D0
to RST
of the ESP in order to allow deep sleepingThe code represents a rework of the original by Don M/glitterkitty but with some changes marked in the header of the sketch.
Within the configuration section marked in the code, some changes have to be made in order to set:
the project aims to have all other parameters (including the sleep time) configurable dynamically via MQTT.
The code is designed to perform the following actions, in order:
MQTT_TOPIC_SUB
in the code),Originally, toggling the load of the Epever solar controller did not work too well, as observed within some of the comments of the original author and this has been traced to two problems:
That being said, the following logic is approachable when wanting to update the ESP monitor settings, in order:
MQTT_SUBSCRIBE_WAIT
during the sketch upload that will define the amount of time that the ESP will wait to receive messages from the MQTT broker,waiting
payload,MQTT_TOPIC_SUB
topic such that the ESP can update its settings /*************************************************************************/ /* Copyright (C) 2019 Don M/glitterkitty - License: GNU GPLv3 */ /* Copyright (C) 2023 Wizardry and Steamworks - License: GNU GPLv3 */ /*************************************************************************/ // v1.0 // // Reworked from: https://github.com/glitterkitty/EpEverSolarMonitor // // // // Current documentation @ // // https://grimore.org/hardware/epever/generic_controller_monitor // // // // Differences & Highlights: // // * added OTA update capabilities, // // * settings are made via MQTT instead of hardware (debug), // // * used a JSON objects intead of MQTT topics, // // * made deep sleep optional, // // * ensured re-entrancy when resuming out of suspend, // // * made time to process MQTT messages configurable, // // * added some compatiblity with ESP32, // // * made multiple parameters configurable, // // * small retouches and aesthetics // // // // About: // // This is an Arduino sketch for retrieving metrics off a solar panel // // controller made by EpEver and should work for most MPPT models. // // // // The original documentation by glitterkitty mentioned that the EPEVER // // outputs +7.5V on the orange-white RJ45 connector but at least for the // // EPEVER 40A the voltage between the orange and the brown wire of the // // RJ45 connector has been measured to be +5.11V which is great because // // no stepdown is needed and the EPEVER can power both the MAX485 TTL // // as well as the ESP. In any case, the voltage should be measured // // again before implementing the circuit diagram just to make sure. // // // // Essential wiring diagram: // // // // +---------+ // // | | // // orange +<--------+--- 5V --+------------------------> + VCC 5V // // | + DI <-------------------> + TX // // green +<--------+ A + DE <-------------------> + D2 // // EPEVER | | ESP // // RJ45 | MAX485 | 8266 / 32 // // blue +<--------+ B + RE <-------------------> + D1 // // | + RO <-------------------> + RX // // brown +<--------+-- GND --+------------------------> + GND // // | | // // +---------+ // // // // Optional wiring: // // * connect the ESP D0 GPIO to RST via <= 1kOhm resistor and then set // // the variable USE_DEEP_SLEEP in this sketch to true in order to // // allow the ESP to go into deep sleep mode and consume less power. // // // // Software usage: // // The sketch will generate JSON payloads and send them to the following // // MQTT topics: // // // // MQTT_TOPIC_PUB / panel | battery | load | energy | extra | monitor // // // // where panel, battery, load, energy, extra and monitor are various // // subtopics of the configurable MQTT_TOPIC_PUB topic. For example to // // just listen for battery notifications, subscribe to the following // // MQTT topic: MQTT_TOPIC_PUB/battery // // // // The the sketch will subscribe to the topic defined by MQTT_TOPIC_SUB // // and listen to the following JSON grammar: // // { // // "action" : "settings" | "switch" // // "sleep" (when "action" is "settings") : time in seconds // // "switch" (when "action" is "switch") : "on" | "off" // // } // // // // For example, to set the sleep time between metric updates to about // // 5 minutes, send the following JSON payload on the MQTT_TOPIC_SUB // // topic: // // { // // "action" : "settings" // // "sleep" : 120 // // } // // // // Note that for the switch to work, the Epever device has to be set to // // "manual mode". From the manual, that seems to be setting the load // // setting for the first timer to 117 and the second should display 2, n // // and then the load can be toggled using this sketch. // /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // configuration // /////////////////////////////////////////////////////////////////////////// // Whether to send data over the serial port. #define SERIAL_DATA 1 // RS-485 module pin DE maps to D2 GPIO on ESP. #define MAX485_DE D2 // RS-485 module pin RE maps to D1 GPIO on ESP. #define MAX485_RE D1 #define STA_SSID "" #define STA_PASSWORD "" #define MQTT_SERVER "" #define MQTT_PORT 1883 #define MQTT_CLIENT_ID "EpEver Solar Monitor" #define MQTT_TOPIC_PUB "epever-40a/talk" #define MQTT_TOPIC_SUB "epever-40a/hear" // Seconds to wait for MQTT message to be delivered and processed. #define MQTT_SUBSCRIBE_WAIT 10 // The OTA hostname. //#define OTA_HOSTNAME // The OTA port on which updates take place. //#define OTA_PORT 8266 // The authentication password to use for OTA updates. // This should be set to the unsalted MD5 hash of the plaintext password. //#define OTA_PASSWORD_HASH // Whether to use deep sleep or not (requires hardware modifications). //#define USE_DEEP_SLEEP 1 // the minimal amount that the ESP should sleep for. #define MIN_SLEEP_SECONDS 60 /////////////////////////////////////////////////////////////////////////// // general variable declarations and libraries // /////////////////////////////////////////////////////////////////////////// #include <ModbusMaster.h> #if defined(ARDUINO_ARCH_ESP8266) #include <ESP8266WiFi.h> #include <ESP8266mDNS.h> #elif defined(ARDUINO_ARCH_ESP32) #include <WiFi.h> #include <ESPmDNS.h> #endif #include <PubSubClient.h> #include <ArduinoJson.h> #include <ArduinoOTA.h> #if defined(ARDUINO_ARCH_ESP8266) #include <ESP_EEPROM.h> #elif defined(ARDUINO_ARCH_ESP32) #include <EEPROM.h> #endif ModbusMaster node; WiFiClient wifi_client; PubSubClient mqtt_client(wifi_client); bool loadState = true; int sleepSeconds; const int JSON_DOCUMENT_SIZE = 512; StaticJsonDocument<JSON_DOCUMENT_SIZE> controllerStatusPayload; StaticJsonDocument<JSON_DOCUMENT_SIZE> epeverMetricsPayload; StaticJsonDocument<JSON_DOCUMENT_SIZE> epeverControlPayload; char tmpJsonPayloadBuffer[JSON_DOCUMENT_SIZE]; /////////////////////////////////////////////////////////////////////////// // modbus data definitions // /////////////////////////////////////////////////////////////////////////// // ModBus Register Locations // Start of live-data #define LIVE_DATA 0x3100 // 16 registers #define LIVE_DATA_CNT 16 // just for reference, not used in code #define PANEL_VOLTS 0x00 #define PANEL_AMPS 0x01 #define PANEL_POWER_L 0x02 #define PANEL_POWER_H 0x03 #define BATT_VOLTS 0x04 #define BATT_AMPS 0x05 #define BATT_POWER_L 0x06 #define BATT_POWER_H 0x07 // dummy * 4 #define LOAD_VOLTS 0x0C #define LOAD_AMPS 0x0D #define LOAD_POWER_L 0x0E #define LOAD_POWER_H 0x0F // D7-0 Sec, D15-8 Min : D7-0 Hour, D15-8 Day : D7-0 Month, D15-8 Year #define RTC_CLOCK 0x9013 // 3 regs #define RTC_CLOCK_CNT 3 // State of Charge in percent, 1 reg #define BATTERY_SOC 0x311A // Battery current L #define BATTERY_CURRENT_L 0x331B // Battery current H #define BATTERY_CURRENT_H 0x331C // start of statistical data #define STATISTICS 0x3300 // 22 registers #define STATISTICS_CNT 22 // just for reference, not used in code // Maximum input volt (PV) today #define PV_MAX 0x00 // Minimum input volt (PV) today #define PV_MIN 0x01 // Maximum battery volt today #define BATT_MAX 0x02 // Minimum battery volt today #define BATT_MIN 0x03 // Consumed energy today L #define CONS_ENERGY_DAY_L 0x04 // Consumed energy today H #define CONS_ENGERY_DAY_H 0x05 // Consumed energy this month L #define CONS_ENGERY_MON_L 0x06 // Consumed energy this month H #define CONS_ENGERY_MON_H 0x07 // Consumed energy this year L #define CONS_ENGERY_YEAR_L 0x08 // Consumed energy this year H #define CONS_ENGERY_YEAR_H 0x09 // Total consumed energy L #define CONS_ENGERY_TOT_L 0x0A // Total consumed energy H #define CONS_ENGERY_TOT_H 0x0B // Generated energy today L #define GEN_ENERGY_DAY_L 0x0C // Generated energy today H #define GEN_ENERGY_DAY_H 0x0D // Generated energy this month L #define GEN_ENERGY_MON_L 0x0E // Generated energy this month H #define GEN_ENERGY_MON_H 0x0F // Generated energy this year L #define GEN_ENERGY_YEAR_L 0x10 // Generated energy this year H #define GEN_ENERGY_YEAR_H 0x11 // Total generated energy L #define GEN_ENERGY_TOT_L 0x12 // Total Generated energy H #define GEN_ENERGY_TOT_H 0x13 // Carbon dioxide reduction L #define CO2_REDUCTION_L 0x14 // Carbon dioxide reduction H #define CO2_REDUCTION_H 0x15 // r/w load switch state #define LOAD_STATE 0x02 #define STATUS_FLAGS 0x3200 // Battery status register #define STATUS_BATTERY 0x00 // Charging equipment status register #define STATUS_CHARGER 0x01 /////////////////////////////////////////////////////////////////////////// // datastructures, also for buffer to values conversion // /////////////////////////////////////////////////////////////////////////// // clock union { struct { uint8_t s; uint8_t m; uint8_t h; uint8_t d; uint8_t M; uint8_t y; } r; uint16_t buf[3]; } rtc; // live data union { struct { int16_t pV; int16_t pI; int32_t pP; int16_t bV; int16_t bI; int32_t bP; uint16_t dummy[4]; int16_t lV; int16_t lI; int32_t lP; } l; uint16_t buf[16]; } live; // statistics union { struct { // 4*1 = 4 uint16_t pVmax; uint16_t pVmin; uint16_t bVmax; uint16_t bVmin; // 4*2 = 8 uint32_t consEnerDay; uint32_t consEnerMon; uint32_t consEnerYear; uint32_t consEnerTotal; // 4*2 = 8 uint32_t genEnerDay; uint32_t genEnerMon; uint32_t genEnerYear; uint32_t genEnerTotal; // 1*2 = 2 uint32_t c02Reduction; } s; uint16_t buf[22]; } stats; // these are too far away for the union conversion trick uint16_t batterySOC = 0; int32_t batteryCurrent = 0; // battery status struct { uint8_t volt; // d3-d0 Voltage: 00H Normal, 01H Overvolt, 02H UnderVolt, 03H Low Volt Disconnect, 04H Fault uint8_t temp; // d7-d4 Temperature: 00H Normal, 01H Over warning settings, 02H Lower than the warning settings uint8_t resistance; // d8 abnormal 1, normal 0 uint8_t rated_volt; // d15 1-Wrong identification for rated voltage } status_batt; char batt_volt_status[][20] = { "Normal", "Overvolt", "Low Volt Disconnect", "Fault" }; char batt_temp_status[][16] = { "Normal", "Over WarnTemp", "Below WarnTemp" }; // charging equipment status (not fully impl. yet) //uint8_t charger_operation = 0; //uint8_t charger_state = 0; //uint8_t charger_input = 0; uint8_t charger_mode = 0; //char charger_input_status[][20] = { // "Normal", // "No power connected", // "Higher volt input", // "Input volt error" //}; char charger_charging_status[][12] = { "Off", "Float", "Boost", "Equlization" }; /////////////////////////////////////////////////////////////////////////// // ModBus helper functions // /////////////////////////////////////////////////////////////////////////// void preTransmission() { digitalWrite(MAX485_RE, 1); digitalWrite(MAX485_DE, 1); digitalWrite(LED_BUILTIN, LOW); } void postTransmission() { digitalWrite(MAX485_RE, 0); digitalWrite(MAX485_DE, 0); digitalWrite(LED_BUILTIN, HIGH); } /////////////////////////////////////////////////////////////////////////// // MQTT event handling // /////////////////////////////////////////////////////////////////////////// void mqtt_reconnect() { // Loop until we're reconnected Serial.println("Checking MQT connection..."); while (!mqtt_client.connected()) { Serial.print("MQTT Reconnecting: "); // Attempt to connect if (mqtt_client.connect(MQTT_CLIENT_ID)) { Serial.println("success"); Serial.print("Subscribing MQTT: "); mqtt_client.subscribe(MQTT_TOPIC_SUB); Serial.println(MQTT_TOPIC_SUB); } else { Serial.print("failure, rc="); Serial.print(mqtt_client.state()); Serial.println(" try again in 1 second"); delay(1000); } } } // control load on / off here, setting sleep duration // void mqtt_callback(char* topic, byte* payload, unsigned int length) { uint8_t i, result; Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); for (i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println(); payload[length] = '\0'; // ignore messages not sent on the subscribed topic if (strncmp(topic, MQTT_TOPIC_SUB, strlen(MQTT_TOPIC_SUB)) != 0) { return; } // Parse the payload sent to the MQTT topic as a JSON document. Serial.print("Deserializing message: "); DeserializationError error = deserializeJson(epeverControlPayload, payload); if (error) { Serial.print("failed, error="); Serial.println(error.c_str()); return; } else { Serial.println("success"); } if (!epeverControlPayload.containsKey("action")) { epeverControlPayload.clear(); return; } if (epeverControlPayload["action"] == "switch") { // Switch - but i can't seem to switch a coil directly here ?!? if (epeverControlPayload["status"] == "on") { loadState = true; } if (epeverControlPayload["status"] == "off") { loadState = false; } Serial.print("Setting load state:"); node.clearResponseBuffer(); node.writeSingleCoil(0x0001, 1); result = node.writeSingleCoil(0x0002, loadState); if (result == node.ku8MBSuccess) { Serial.println("success"); } else { Serial.println("failure"); Serial.print("Miss write loadState, ret val: "); Serial.println(result, HEX); } } if (epeverControlPayload["action"] == "settings") { if (epeverControlPayload.containsKey("sleep")) { // input sanitization int seconds = (unsigned int)epeverControlPayload["sleep"]; if (seconds == sleepSeconds) { Serial.println("no change"); return; } if (seconds < MIN_SLEEP_SECONDS) { sleepSeconds = MIN_SLEEP_SECONDS; } else { sleepSeconds = seconds; } EEPROM.put(0, sleepSeconds); if (!EEPROM.commit()) { Serial.println("Failure setting sleep seconds."); return; } Serial.print("Set sleep seconds to: "); Serial.println(sleepSeconds); } epeverControlPayload.clear(); return; } } /////////////////////////////////////////////////////////////////////////// // Arduino functions // /////////////////////////////////////////////////////////////////////////// void setup() { Serial.begin(115200); // DO NOT CHANGE! while (!Serial) ; Serial.println(); Serial.println("Hello World! I'm an EpEver Solar Monitor!"); // init modbus in receive mode pinMode(MAX485_RE, OUTPUT); pinMode(MAX485_DE, OUTPUT); digitalWrite(MAX485_RE, 0); digitalWrite(MAX485_DE, 0); // modbus callbacks node.preTransmission(preTransmission); node.postTransmission(postTransmission); // EPEver Device ID 1 node.begin(1, Serial); // Connect D0 to RST to wake up #ifdef USE_DEEP_SLEEP pinMode(D0, WAKEUP_PULLUP); #endif // variable handling EEPROM.begin(16); EEPROM.get(0, sleepSeconds); if (sleepSeconds < MIN_SLEEP_SECONDS) { sleepSeconds = 60; EEPROM.put(0, sleepSeconds); if (!EEPROM.commit()) { Serial.println("Unable to set default sleep."); } } // Initialize the LED_BUILTIN pin as an output, low active pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); } void loop() { uint8_t i, result; // Turn on LED digitalWrite(LED_BUILTIN, HIGH); // Clear old data memset(rtc.buf, 0, sizeof(rtc.buf)); memset(live.buf, 0, sizeof(live.buf)); memset(stats.buf, 0, sizeof(stats.buf)); // Read registers for clock node.clearResponseBuffer(); result = node.readHoldingRegisters(RTC_CLOCK, RTC_CLOCK_CNT); if (result == node.ku8MBSuccess) { rtc.buf[0] = node.getResponseBuffer(0); rtc.buf[1] = node.getResponseBuffer(1); rtc.buf[2] = node.getResponseBuffer(2); } else { Serial.print("Miss read rtc-data, ret val:"); Serial.println(result, HEX); } // Read LIVE-Data node.clearResponseBuffer(); result = node.readInputRegisters(LIVE_DATA, LIVE_DATA_CNT); if (result == node.ku8MBSuccess) { for (i = 0; i < LIVE_DATA_CNT; i++) live.buf[i] = node.getResponseBuffer(i); } else { Serial.print("Miss read liva-data, ret val:"); Serial.println(result, HEX); } // Statistical Data node.clearResponseBuffer(); result = node.readInputRegisters(STATISTICS, STATISTICS_CNT); if (result == node.ku8MBSuccess) { for (i = 0; i < STATISTICS_CNT; i++) { stats.buf[i] = node.getResponseBuffer(i); } } else { Serial.print("Miss read statistics, ret val:"); Serial.println(result, HEX); } // Battery SOC node.clearResponseBuffer(); result = node.readInputRegisters(BATTERY_SOC, 1); if (result == node.ku8MBSuccess) { batterySOC = node.getResponseBuffer(0); } else { Serial.print("Miss read batterySOC, ret val:"); Serial.println(result, HEX); } // Battery Net Current = Icharge - Iload node.clearResponseBuffer(); result = node.readInputRegisters(BATTERY_CURRENT_L, 2); if (result == node.ku8MBSuccess) { batteryCurrent = node.getResponseBuffer(0); batteryCurrent |= node.getResponseBuffer(1) << 16; } else { Serial.print("Miss read batteryCurrent, ret val:"); Serial.println(result, HEX); } // State of the Load Switch node.clearResponseBuffer(); result = node.readCoils(LOAD_STATE, 1); if (result == node.ku8MBSuccess) { loadState = node.getResponseBuffer(0); } else { Serial.print("Miss read loadState, ret val:"); Serial.println(result, HEX); } // Read Status Flags node.clearResponseBuffer(); result = node.readInputRegisters(0x3200, 2); if (result == node.ku8MBSuccess) { uint16_t temp = node.getResponseBuffer(0); Serial.print("Batt Flags : "); Serial.println(temp); status_batt.volt = temp & 0b1111; status_batt.temp = (temp >> 4) & 0b1111; status_batt.resistance = (temp >> 8) & 0b1; status_batt.rated_volt = (temp >> 15) & 0b1; temp = node.getResponseBuffer(1); Serial.print("Chrg Flags : "); Serial.println(temp, HEX); //for(i=0; i<16; i++) Serial.print( (temp >> (15-i) ) & 1 ); //Serial.println(); //charger_input = ( temp & 0b0000000000000000 ) >> 15 ; charger_mode = (temp & 0b0000000000001100) >> 2; //charger_input = ( temp & 0b0000000000000000 ) >> 12 ; //charger_operation = ( temp & 0b0000000000000000 ) >> 0 ; //Serial.print( "charger_input : "); Serial.println( charger_input ); Serial.print("charger_mode : "); Serial.println(charger_mode); //Serial.print( "charger_oper : "); Serial.println( charger_operation ); //Serial.print( "charger_state : "); Serial.println( charger_state ); } else { Serial.print("Miss read ChargeState, ret val:"); Serial.println(result, HEX); } // Print out to serial #ifdef SERIAL_DATA Serial.printf("\n\nTime: 20%02d-%02d-%02d %02d:%02d:%02d \n", rtc.r.y, rtc.r.M, rtc.r.d, rtc.r.h, rtc.r.m, rtc.r.s); Serial.print("\nLive-Data: Volt Amp Watt "); Serial.printf("\n Panel: %7.3f %7.3f %7.3f ", live.l.pV / 100.f, live.l.pI / 100.f, live.l.pP / 100.0f); Serial.printf("\n Batt: %7.3f %7.3f %7.3f ", live.l.bV / 100.f, live.l.bI / 100.f, live.l.bP / 100.0f); Serial.printf("\n Load: %7.3f %7.3f %7.3f ", live.l.lV / 100.f, live.l.lI / 100.f, live.l.lP / 100.0f); Serial.println(); Serial.printf("\n Battery Current: %7.3f A ", batteryCurrent / 100.f); Serial.printf("\n Battery SOC: %7.0f %% ", batterySOC / 1.0f); Serial.printf("\n Load Switch: %s ", (loadState == 1 ? " On" : "Off")); Serial.print("\n\nStatistics: "); Serial.printf("\n Panel: min: %7.3f max: %7.3f V", stats.s.pVmin / 100.f, stats.s.pVmax / 100.f); Serial.printf("\n Battery: min: %7.3f max: %7.3f V", stats.s.bVmin / 100.f, stats.s.bVmax / 100.f); Serial.println(); Serial.printf("\n Consumed: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh", stats.s.consEnerDay / 100.f, stats.s.consEnerMon / 100.f, stats.s.consEnerYear / 100.f, stats.s.consEnerTotal / 100.f); Serial.printf("\n Generated: day: %7.3f mon: %7.3f year: %7.3f total: %7.3f kWh", stats.s.genEnerDay / 100.f, stats.s.genEnerMon / 100.f, stats.s.genEnerYear / 100.f, stats.s.genEnerTotal / 100.f); Serial.printf("\n CO2-Reduction: %7.3f t ", stats.s.c02Reduction / 100.f); Serial.println(); Serial.print("\nStatus:"); Serial.printf("\n batt.volt: %s ", batt_volt_status[status_batt.volt]); Serial.printf("\n batt.temp: %s ", batt_temp_status[status_batt.temp]); Serial.printf("\n charger.charging: %s ", charger_charging_status[charger_mode]); Serial.println(); Serial.println(); #endif // Start WiFi connection. digitalWrite(LED_BUILTIN, LOW); WiFi.mode(WIFI_STA); WiFi.begin(STA_SSID, STA_PASSWORD); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("Connection Failed! Rebooting..."); delay(5000); ESP.restart(); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); // Port defaults to 8266 #ifdef OTA_PORT ArduinoOTA.setPort(OTA_PORT); #endif // Hostname defaults to esp8266-[ChipID] #ifdef OTA_HOSTNAME if (strlen(OTA_HOSTNAME) != 0) { ArduinoOTA.setHostname(OTA_HOSTNAME); } #endif // No authentication by default #ifdef OTA_PASSWORD_HASH if (strlen(OTA_PASSWORD_HASH) != 0) { ArduinoOTA.setPasswordHash(OTA_PASSWORD_HASH); } #endif ArduinoOTA.onStart([]() { String type; if (ArduinoOTA.getCommand() == U_FLASH) { type = "sketch"; } else { // U_FS type = "filesystem"; } // NOTE: if updating FS this would be the place to unmount FS using FS.end() Serial.println("Start updating " + type); }); ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); }); ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); }); ArduinoOTA.onError([](ota_error_t error) { Serial.printf("Error[%u]: ", error); if (error == OTA_AUTH_ERROR) { Serial.println("Auth Failed"); } else if (error == OTA_BEGIN_ERROR) { Serial.println("Begin Failed"); } else if (error == OTA_CONNECT_ERROR) { Serial.println("Connect Failed"); } else if (error == OTA_RECEIVE_ERROR) { Serial.println("Receive Failed"); } else if (error == OTA_END_ERROR) { Serial.println("End Failed"); } }); ArduinoOTA.begin(); // Establish/keep mqtt connection mqtt_client.setServer(MQTT_SERVER, MQTT_PORT); mqtt_client.setCallback(mqtt_callback); mqtt_reconnect(); digitalWrite(LED_BUILTIN, HIGH); // Once connected, publish an announcement. controllerStatusPayload["solar"]["monitor"]["status"] = "online"; serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer); controllerStatusPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer); controllerStatusPayload["solar"]["monitor"]["status"] = "waiting"; serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer); controllerStatusPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer); // Wait for MQTT subscription processing Serial.println("Waiting for MQTT and OTA events."); unsigned int now = millis(); while (millis() - now < MQTT_SUBSCRIBE_WAIT * 1000) { // Loop for MQTT. if (!mqtt_client.loop() || WiFi.status() != WL_CONNECTED) { break; } // Loop for OTA. ArduinoOTA.handle(); delay(100); } Serial.println("Done waiting for MQTT and OTA events."); // Publish to MQTT Serial.print("Publishing to MQTT: "); // Panel epeverMetricsPayload["solar"]["panel"]["V"] = String(live.l.pV / 100.f, 2); epeverMetricsPayload["solar"]["panel"]["I"] = String(live.l.pI / 100.f, 2); epeverMetricsPayload["solar"]["panel"]["P"] = String(live.l.pP / 100.f, 2); epeverMetricsPayload["solar"]["panel"]["minV"] = String(stats.s.pVmin / 100.f, 3); epeverMetricsPayload["solar"]["panel"]["maxV"] = String(stats.s.pVmax / 100.f, 3); serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer); epeverMetricsPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "panel").c_str(), tmpJsonPayloadBuffer); // Battery epeverMetricsPayload["solar"]["battery"]["V"] = String(live.l.bV / 100.f, 2); epeverMetricsPayload["solar"]["battery"]["I"] = String(live.l.bI / 100.f, 2); epeverMetricsPayload["solar"]["battery"]["P"] = String(live.l.bP / 100.f, 2); epeverMetricsPayload["solar"]["battery"]["minV"] = String(stats.s.bVmin / 100.f, 2); epeverMetricsPayload["solar"]["battery"]["maxV"] = String(stats.s.bVmax / 100.f, 2); epeverMetricsPayload["solar"]["battery"]["SOC"] = String(batterySOC / 1.0f, 2); epeverMetricsPayload["solar"]["battery"]["netI"] = String(batteryCurrent / 100.0f, 2); epeverMetricsPayload["solar"]["battery"]["status"]["voltage"].set(batt_volt_status[status_batt.volt]); epeverMetricsPayload["solar"]["battery"]["status"]["temperature"].set(batt_temp_status[status_batt.temp]); serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer); epeverMetricsPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "battery").c_str(), tmpJsonPayloadBuffer); // Load epeverMetricsPayload["solar"]["load"]["V"] = String(live.l.lV / 100.f, 2); epeverMetricsPayload["solar"]["load"]["I"] = String(live.l.lI / 100.f, 2); epeverMetricsPayload["solar"]["load"]["P"] = String(live.l.lP / 100.f, 2); // pimatic state topic does not work with integers or floats ?!? switch (loadState) { case 1: epeverMetricsPayload["solar"]["load"]["state"].set("on"); break; default: epeverMetricsPayload["solar"]["load"]["state"].set("off"); break; } serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer); epeverMetricsPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "load").c_str(), tmpJsonPayloadBuffer); // Energy epeverMetricsPayload["solar"]["energy"]["consumed_day"] = String(stats.s.consEnerDay / 100.f, 3); epeverMetricsPayload["solar"]["energy"]["consumed_all"] = String(stats.s.consEnerTotal / 100.f, 3); epeverMetricsPayload["solar"]["energy"]["generated_day"] = String(stats.s.genEnerDay / 100.f, 3); epeverMetricsPayload["solar"]["energy"]["generated_all"] = String(stats.s.genEnerTotal / 100.f, 3); serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer); epeverMetricsPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "energy").c_str(), tmpJsonPayloadBuffer); // Extra epeverMetricsPayload["solar"]["extra"]["CO2"]["t"] = String(stats.s.c02Reduction / 100.f, 2); //epever_serialize_s( "solar/status/charger_input", charger_input_status[ charger_input ] epeverMetricsPayload["solar"]["extra"]["charger_mode"] = charger_charging_status[charger_mode]; char buf[21]; sprintf(buf, "20%02d-%02d-%02d %02d:%02d:%02d", rtc.r.y, rtc.r.M, rtc.r.d, rtc.r.h, rtc.r.m, rtc.r.s); epeverMetricsPayload["solar"]["extra"]["time"] = buf; serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer); epeverMetricsPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "extra").c_str(), tmpJsonPayloadBuffer); // Settings epeverMetricsPayload["solar"]["monitor"]["settings"]["sleep"].set(sleepSeconds); serializeJson(epeverMetricsPayload, tmpJsonPayloadBuffer); epeverMetricsPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "settings").c_str(), tmpJsonPayloadBuffer); Serial.println("done"); controllerStatusPayload["solar"]["monitor"]["status"] = "offline"; serializeJson(controllerStatusPayload, tmpJsonPayloadBuffer); controllerStatusPayload.clear(); mqtt_client.publish((String(MQTT_TOPIC_PUB) + "/" + "status").c_str(), tmpJsonPayloadBuffer); // Ensure all messages are sent mqtt_client.unsubscribe(MQTT_TOPIC_SUB); mqtt_client.disconnect(); while (mqtt_client.state() != -1) { delay(100); } // disconnect wifi WiFi.disconnect(true); // power down MAX485_DE // low active digitalWrite(MAX485_RE, 0); digitalWrite(MAX485_DE, 0); // Sleep Serial.print("\nSleep for "); Serial.print(sleepSeconds); Serial.println(" Seconds"); digitalWrite(LED_BUILTIN, LOW); #ifdef USE_DEEP_SLEEP if (USE_DEEP_SLEEP) { ESP.deepSleep(sleepSeconds * 1000000); } else { delay(sleepSeconds * 1000); } #else delay(sleepSeconds * 1000); #endif }
With the code uploaded to the ESP the WeMoS can be plugged back into the socket and when the Ethernet cable is connected the ESP should start transmitting the data to an MQTT broker. Note that it is entirely possible to have both the monitor that was designed connected to the Epever solar controller via Ethernet and concomitantly connect to the WeMoS board via USB to a computer in order to observe the data on the serial port or re-program the ESP.
Note that when flashing the sketch to the WeMoS, more than likely you need to set the board type to:
LOLIN(WEMOS) D1 Mini (clone)
or the sketch will be unable to memorize settings and errors might be printed out on the serial console mentioning that the EEPROM could not be written.
The following section enumerates the data sent by the code. The data is serialized to JSON with two decimals for floating point values yet converted to strings to work around Arduino limitations. All the data is transmitted on various MQTT sub-topics of MQTT_TOPIC_PUB
:
panel
- the solar panel metrics,battery
- the battery metrics,load
- the current load,energy
- some energy related data,extra
- additional data,monitor
- data pertaining to the monitor itself (this device described in this document)
For instance, just for testing, to subscribe to all sub-topics and assuming that MQTT_TOPIC_PUB
is the default epever-40a/talk
, then mosquitto_sub
can be issued as follows:
mosquitto_sub -t 'epever-40a/talk/+' -h localhost
The following sections show some example data transmitted on the topics as section titles.
{ "solar":{ "panel":{ "V":"0.15", "I":"0.00", "P":"0.00", "minV":"0.120", "maxV":"21.390" } } }
{ "solar":{ "battery":{ "V":"13.12", "I":"0.00", "P":"0.00", "minV":"11.20", "maxV":"14.30", "SOC":"73.00", "netI":"-0.67", "status":{ "voltage":"Normal", "temperature":"Normal" } } } }
{ "solar":{ "load":{ "V":"13.12", "I":"0.59", "P":"7.74", "state":"on" } } }
{ "solar":{ "energy":{ "consumed_day":"0.220", "consumed_all":"0.730", "generated_day":"0.210", "generated_all":"1.070" } } }
{ "solar":{ "extra":{ "CO2":{ "t":"0.00" }, "charger_mode":"Off", "time":"2013-01-06 17:13:57" } } }
{ "solar":{ "monitor":{ "settings":{ "sleep":60 } } } }
Here is a possible dashboard that processed the data sent by the monitor:
The dashboard shows the battery and panel base metrics such as voltage, current and wattage, most of these being pulled directly from the JSON objects that the monitor published to the MQTT broker.
Perhaps the most interesting to see here are the metrics displayed in the Efficiency
section. One such metric is the Input vs Output (A)
that is the representation of the data pulled from the battery
MQTT subtopic and of the sub-property netI
. netI
of the JSON object that can be described as a measure of amperes flowing through the Epever solar controller and can quickly show whether the solar array setup is consuming, respectively generating energy depending on whether the netI
value is negative, respectively positive. In the image above, the value seems to be negative, namely meaning that the whole setup is, in fact, consuming half an Ampere which seems to be correct given that the image was taken during night time when the consumers rely on the battery to sustain themselves.
Another interesting metric is the Generated Power (W) vs. Sun Elevation (deg)
because it can be used to debug the solar array setup conveniently.
The chart plots two series, the current power output of the solar panels (in green) and the current elevation of the sun above or below the horizon (orange).
Looking at the chart it is interesting to devise why the most power being generated takes place at around 4pm and it is also somewhat possible to determine the orientation of the solar panels themselves. It is interesting, but the data does seem to match perfectly with the solar panel orientation relative to the sun during the day. We used static solar panels, not motorized ones, and given their static orientation, as well as the trajectory of the sun, it seems obvious that the most power would be generated during the afternoon. In any case, it must be said that power generation and storage is cumulative such that one would need to integrate the area under the Watt curve in order to determine the total amount of power generated over the whole day.
The Node-Red flow in the following section includes some exotic nodes such as:
node-red-contrib-redis
; we have found Redis to be an indispensable storage medium that is highly compatible with JavaScript due to its key-value design as well as providing a less bloated storage option compared to a database,[ { "id": "0c71d7c156291f81", "type": "tab", "label": "Solar", "disabled": false, "info": "", "env": [] }, { "id": "43316a9d83a2d1c5", "type": "group", "z": "0c71d7c156291f81", "name": "MQTT Connectivity", "style": { "label": true }, "nodes": [ "31ca1bd362c9920a", "218e7afa7eb97cfa", "af8ae684feca6f96", "32a15dec7972474c", "3c16b5cc86a7573a" ], "x": 54, "y": 39, "w": 362, "h": 222 }, { "id": "188827854f36f6f9", "type": "group", "z": "0c71d7c156291f81", "name": "Settings", "style": { "label": true }, "nodes": [ "1c058b1f8278dd24", "3082948c1eb929bb", "398337a8d08d5f50", "45d0c9fdf8dfecc0", "281b7fd3a6a5e952", "44b5b1d6bf1afc9a", "24a9ee0682777cec", "7ed8396faf65527e", "5478e53386979ee2", "68f6ab3a62d4cb6d", "81dd9a5a0c11ea14", "27ba80a1376a5657", "d43257e89b9c665d", "a21701f9a23ef74e", "fd38855634aadc66", "a97c99dc848b1cd9", "d2ebc4a6205d1a4a", "fd8ab747371d8127", "3b088c8c7757c298", "9957edec9bbf0f74", "0b1cee2e609ad711", "368064192294170f", "b6bf5fdadf2f16d6", "07e3be91304ca577", "fde241b8790dff11" ], "x": 54, "y": 479, "w": 932, "h": 722 }, { "id": "41e0ca36ab088b06", "type": "group", "z": "0c71d7c156291f81", "name": "Controller Load Switch", "style": { "label": true }, "nodes": [ "636fe34fd10e61fd", "53202812f5b7e8b9", "959b6470f91360a8", "92bf127bf074c713", "192208e68d9a2f71", "4e78e565c335317b", "5db19d2fe731de9c", "d978f144c26b9951", "f5b989e1ddc46713", "53db38d1a30af8e3", "7f744125b78bd5f6", "bba8c0155dd56001", "ebc601c4127fd4b0", "7dc8cba55f4ea2a6", "792e8351c67337e8", "f4d7de6e35cc6689", "1dc82ed439f5e633", "e0de4137375fbe1c", "25514aa57a06bbd8", "468feb011ff38f89", "3d0feb3c85f758cd", "956b7f9e4ed8e254", "0dfccc5fdfc84e44", "4c07e0cc9184dcc3", "3dfb7cc559902c0d", "6425e623d113f38c", "32ba210ab2a02102", "e56cdda4d749deb5", "8efc7748f3d5fed1", "c9279b012026601d", "5cd229f70b7338e4" ], "x": 54, "y": 1239, "w": 1032, "h": 882 }, { "id": "0368dad51c195288", "type": "group", "z": "0c71d7c156291f81", "name": "Charts", "style": { "label": true }, "nodes": [ "98c62b275c81bb7e", "e57766d53a9f53e9", "0d5632e4d7efacee", "aea8a9041645a3a0", "3474a625ad2bc385", "b5c412a0d99fb957", "d1acc889618287d5", "0e51d432c6ad04e9", "592aa764042cc054", "e72e66e0f6902494", "548aed35d5b92d3d", "6b0d431da58807f7", "9cf2a62852ee5b41", "ae0439d4c6d4ccfd", "d93c44240d8a293f", "111ac78c519436d3", "4ab1b149d413d1e7", "1def045df1f1b403", "05e80ca5efb3a704", "d61ea73077afddc2", "414ba84b4e8e7717", "bdc38b66f677cd45", "1a43cb7bb9cc7306", "88587a282917d5e0", "138582e5336ca242", "2d8e41bafc41e64f", "2d7139ac4dbc4d48", "fdc8a434f2a5814b", "cf1724a9366a7d4a", "2a976d1e050c1464" ], "x": 1054, "y": 79, "w": 1232, "h": 882 }, { "id": "f0a7056e4ff89ec9", "type": "group", "z": "0c71d7c156291f81", "name": "Solar Monitor Waiting Settings", "style": { "label": true }, "nodes": [ "2d9b1cea9886c2da", "c6358b504e71c7b6", "defcaaf74bdd754a", "68387caaab696257", "d8a9da5ca489a451", "817044b97ed1806f" ], "x": 54, "y": 299, "w": 592, "h": 142 }, { "id": "31ca1bd362c9920a", "type": "mqtt in", "z": "0c71d7c156291f81", "g": "43316a9d83a2d1c5", "name": "", "topic": "epever-40a/talk/+", "qos": "0", "datatype": "auto-detect", "broker": "725ed69c.6d76a8", "nl": false, "rap": true, "rh": 0, "inputs": 0, "x": 180, "y": 160, "wires": [ [ "af8ae684feca6f96", "3c16b5cc86a7573a" ] ] }, { "id": "98c62b275c81bb7e", "type": "debug", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "debug 4", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1220, "y": 280, "wires": [] }, { "id": "e57766d53a9f53e9", "type": "json", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "property": "payload", "action": "obj", "pretty": false, "x": 1190, "y": 360, "wires": [ [ "4ab1b149d413d1e7", "98c62b275c81bb7e" ] ] }, { "id": "218e7afa7eb97cfa", "type": "mqtt out", "z": "0c71d7c156291f81", "g": "43316a9d83a2d1c5", "name": "", "topic": "epever-40a/hear", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "725ed69c.6d76a8", "x": 240, "y": 220, "wires": [] }, { "id": "0d5632e4d7efacee", "type": "ui_chart", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "0e0468a7bcaa4a7a", "order": 0, "width": 0, "height": 0, "label": "Panel Voltage (V)", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#80ff80", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 1790, "y": 120, "wires": [ [] ] }, { "id": "aea8a9041645a3a0", "type": "ui_chart", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "0e0468a7bcaa4a7a", "order": 0, "width": 0, "height": 0, "label": "Panel Current (A)", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#80ff80", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 1790, "y": 180, "wires": [ [ "cf1724a9366a7d4a" ] ] }, { "id": "3474a625ad2bc385", "type": "ui_chart", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "0e0468a7bcaa4a7a", "order": 0, "width": 0, "height": 0, "label": "Panel Power (W)", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#80ff80", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 1790, "y": 240, "wires": [ [] ] }, { "id": "b5c412a0d99fb957", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.solar.panel.V", "tot": "msg", "dc": true } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1580, "y": 120, "wires": [ [ "0d5632e4d7efacee" ] ] }, { "id": "d1acc889618287d5", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.solar.panel.I", "tot": "msg", "dc": true } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1580, "y": 180, "wires": [ [ "aea8a9041645a3a0" ] ] }, { "id": "0e51d432c6ad04e9", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.solar.panel.P", "tot": "msg", "dc": true } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1580, "y": 240, "wires": [ [ "3474a625ad2bc385", "fdc8a434f2a5814b" ] ] }, { "id": "1c058b1f8278dd24", "type": "ui_slider", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "", "label": "Update Frequency (s)", "tooltip": "", "group": "9e0ac0fcf4e571a8", "order": 4, "width": 0, "height": 0, "passthru": false, "outs": "end", "topic": "topic", "topicType": "msg", "min": "60", "max": "300", "step": 1, "x": 200, "y": 640, "wires": [ [ "45d0c9fdf8dfecc0" ] ] }, { "id": "af8ae684feca6f96", "type": "link out", "z": "0c71d7c156291f81", "g": "43316a9d83a2d1c5", "name": "link out 1", "mode": "link", "links": [ "592aa764042cc054", "2d9b1cea9886c2da", "f5b989e1ddc46713", "d43257e89b9c665d" ], "x": 295, "y": 160, "wires": [] }, { "id": "592aa764042cc054", "type": "link in", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "link in 1", "links": [ "af8ae684feca6f96" ], "x": 1095, "y": 360, "wires": [ [ "e57766d53a9f53e9" ] ] }, { "id": "32a15dec7972474c", "type": "link in", "z": "0c71d7c156291f81", "g": "43316a9d83a2d1c5", "name": "link in 3", "links": [ "3082948c1eb929bb", "fb6e1142d67d70b6", "956b7f9e4ed8e254" ], "x": 95, "y": 220, "wires": [ [ "218e7afa7eb97cfa" ] ] }, { "id": "3082948c1eb929bb", "type": "link out", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "link out 2", "mode": "link", "links": [ "32a15dec7972474c" ], "x": 585, "y": 1040, "wires": [] }, { "id": "398337a8d08d5f50", "type": "debug", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "debug 5", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 680, "y": 1100, "wires": [] }, { "id": "45d0c9fdf8dfecc0", "type": "function", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "function 2", "func": "let value = JSON.stringify(\n {\n \"action\" : \"settings\",\n \"sleep\" : msg.payload\n }\n)\nmsg = {}\nmsg.payload = [\"solar/settings\", value ]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 400, "y": 640, "wires": [ [ "281b7fd3a6a5e952" ] ] }, { "id": "281b7fd3a6a5e952", "type": "redis-command", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "server": "a37a41f9.a98da8", "command": "SET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 560, "y": 640, "wires": [ [ "07e3be91304ca577" ] ] }, { "id": "44b5b1d6bf1afc9a", "type": "redis-command", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "server": "a37a41f9.a98da8", "command": "GET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 220, "y": 1040, "wires": [ [ "5478e53386979ee2", "fd8ab747371d8127" ] ] }, { "id": "24a9ee0682777cec", "type": "inject", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "init", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "[ \"solar/settings\" ]", "payloadType": "json", "x": 150, "y": 520, "wires": [ [ "7ed8396faf65527e" ] ] }, { "id": "7ed8396faf65527e", "type": "redis-command", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "server": "a37a41f9.a98da8", "command": "GET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 300, "y": 520, "wires": [ [ "68f6ab3a62d4cb6d" ] ] }, { "id": "5478e53386979ee2", "type": "json", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "", "property": "payload", "action": "str", "pretty": false, "x": 490, "y": 1040, "wires": [ [ "3082948c1eb929bb" ] ] }, { "id": "68f6ab3a62d4cb6d", "type": "json", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "", "property": "payload", "action": "obj", "pretty": false, "x": 450, "y": 520, "wires": [ [ "81dd9a5a0c11ea14", "d2ebc4a6205d1a4a" ] ] }, { "id": "81dd9a5a0c11ea14", "type": "function", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "function 4", "func": "msg.payload = msg.payload.sleep;\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 600, "y": 520, "wires": [ [ "1c058b1f8278dd24" ] ] }, { "id": "e72e66e0f6902494", "type": "ui_chart", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "6ef616c0de00894f", "order": 0, "width": 0, "height": 0, "label": "Battery Voltage (V)", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 1810, "y": 500, "wires": [ [] ] }, { "id": "548aed35d5b92d3d", "type": "ui_chart", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "6ef616c0de00894f", "order": 0, "width": 0, "height": 0, "label": "Battery Current (A)", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 1810, "y": 560, "wires": [ [] ] }, { "id": "6b0d431da58807f7", "type": "ui_chart", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "6ef616c0de00894f", "order": 0, "width": 0, "height": 0, "label": "Battery Power (W)", "chartType": "line", "legend": "false", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#1f77b4", "#aec7e8", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 1810, "y": 620, "wires": [ [] ] }, { "id": "9cf2a62852ee5b41", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.solar.battery.V", "tot": "msg", "dc": true } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1600, "y": 500, "wires": [ [ "e72e66e0f6902494", "111ac78c519436d3" ] ] }, { "id": "ae0439d4c6d4ccfd", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.solar.battery.I", "tot": "msg", "dc": true } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1600, "y": 560, "wires": [ [ "548aed35d5b92d3d" ] ] }, { "id": "d93c44240d8a293f", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.solar.battery.P", "tot": "msg", "dc": true } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1600, "y": 620, "wires": [ [ "6b0d431da58807f7" ] ] }, { "id": "111ac78c519436d3", "type": "ui_gauge", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "6ef616c0de00894f", "order": 3, "width": 0, "height": 0, "gtype": "gage", "title": "Battery Voltage (V)", "label": "V", "format": "{{value}}", "min": "11", "max": "14", "colors": [ "#ff0000", "#e6e600", "#80ff00" ], "seg1": "", "seg2": "", "x": 1810, "y": 680, "wires": [] }, { "id": "4ab1b149d413d1e7", "type": "switch", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "hask", "v": "solar", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 1330, "y": 360, "wires": [ [ "1def045df1f1b403", "05e80ca5efb3a704" ], [] ] }, { "id": "3c16b5cc86a7573a", "type": "debug", "z": "0c71d7c156291f81", "g": "43316a9d83a2d1c5", "name": "debug 8", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 310, "y": 80, "wires": [] }, { "id": "1def045df1f1b403", "type": "switch", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "property": "payload.solar", "propertyType": "msg", "rules": [ { "t": "hask", "v": "battery", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 1430, "y": 560, "wires": [ [ "9cf2a62852ee5b41", "ae0439d4c6d4ccfd", "d93c44240d8a293f", "414ba84b4e8e7717" ], [] ] }, { "id": "05e80ca5efb3a704", "type": "switch", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "property": "payload.solar", "propertyType": "msg", "rules": [ { "t": "hask", "v": "panel", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 1410, "y": 180, "wires": [ [ "b5c412a0d99fb957", "d1acc889618287d5", "0e51d432c6ad04e9" ], [] ] }, { "id": "d61ea73077afddc2", "type": "ui_gauge", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "56ef03ec967ddd2d", "order": 4, "width": 0, "height": 0, "gtype": "gage", "title": "Input vs. Output (A)", "label": "A", "format": "{{value}}", "min": "-10", "max": 10, "colors": [ "#ff0000", "#e6e600", "#80ff00" ], "seg1": "", "seg2": "", "x": 1810, "y": 740, "wires": [] }, { "id": "414ba84b4e8e7717", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.solar.battery.netI", "tot": "msg", "dc": true } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1600, "y": 740, "wires": [ [ "d61ea73077afddc2" ] ] }, { "id": "bdc38b66f677cd45", "type": "sun-position", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "positionConfig": "9fa8b179879daba5", "rules": [], "onlyOnChange": "true", "topic": "", "outputs": 1, "start": "", "startType": "none", "startOffset": 0, "startOffsetType": "none", "startOffsetMultiplier": 60000, "end": "", "endType": "none", "endOffset": 0, "endOffsetType": "none", "endOffsetMultiplier": 60000, "x": 1450, "y": 860, "wires": [ [ "2d8e41bafc41e64f" ] ] }, { "id": "1a43cb7bb9cc7306", "type": "ui_chart", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "group": "56ef03ec967ddd2d", "order": 5, "width": 0, "height": 0, "label": "Generated Power (W) vs. Sun Elevation (deg)", "chartType": "line", "legend": "true", "xformat": "HH:mm:ss", "interpolate": "linear", "nodata": "", "dot": false, "ymin": "", "ymax": "", "removeOlder": 1, "removeOlderPoints": "", "removeOlderUnit": "86400", "cutout": 0, "useOneColor": false, "useUTC": false, "colors": [ "#11ff00", "#f59a19", "#ff7f0e", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5" ], "outputs": 1, "useDifferentColor": false, "x": 2080, "y": 860, "wires": [ [] ] }, { "id": "88587a282917d5e0", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "watt", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1790, "y": 920, "wires": [ [ "1a43cb7bb9cc7306" ] ] }, { "id": "138582e5336ca242", "type": "change", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "elevation", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1790, "y": 860, "wires": [ [ "1a43cb7bb9cc7306" ] ] }, { "id": "2d8e41bafc41e64f", "type": "function", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "function 5", "func": "msg.payload = msg.payload.altitude\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1620, "y": 860, "wires": [ [ "138582e5336ca242" ] ] }, { "id": "2d7139ac4dbc4d48", "type": "link in", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "link in 4", "links": [ "fdc8a434f2a5814b" ], "x": 1335, "y": 860, "wires": [ [ "bdc38b66f677cd45", "88587a282917d5e0" ] ] }, { "id": "fdc8a434f2a5814b", "type": "link out", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "link out 3", "mode": "link", "links": [ "2d7139ac4dbc4d48" ], "x": 1725, "y": 300, "wires": [] }, { "id": "cf1724a9366a7d4a", "type": "debug", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "debug 9", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 2020, "y": 180, "wires": [] }, { "id": "2a976d1e050c1464", "type": "inject", "z": "0c71d7c156291f81", "g": "0368dad51c195288", "name": "", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[]", "payloadType": "jsonata", "x": 1990, "y": 800, "wires": [ [ "1a43cb7bb9cc7306" ] ] }, { "id": "636fe34fd10e61fd", "type": "debug", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "debug 10", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 800, "y": 1400, "wires": [] }, { "id": "53202812f5b7e8b9", "type": "ui_switch", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "label": "Controller Load", "tooltip": "", "group": "9e0ac0fcf4e571a8", "order": 4, "width": 0, "height": 0, "passthru": false, "decouple": "true", "topic": "topic", "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", "onicon": "", "oncolor": "", "offvalue": "false", "offvalueType": "bool", "officon": "", "offcolor": "", "animate": false, "x": 160, "y": 1480, "wires": [ [ "959b6470f91360a8" ] ] }, { "id": "959b6470f91360a8", "type": "switch", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "true" }, { "t": "false" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 3, "x": 330, "y": 1480, "wires": [ [ "bba8c0155dd56001" ], [ "92bf127bf074c713" ], [] ] }, { "id": "92bf127bf074c713", "type": "function", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "Off", "func": "let value = JSON.stringify(\n {\n \"action\": \"switch\",\n \"state\": \"off\"\n }\n)\nmsg = {}\nmsg.payload = [\"solar/load\", value]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 490, "y": 1520, "wires": [ [ "f4d7de6e35cc6689" ] ] }, { "id": "192208e68d9a2f71", "type": "inject", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 240, "y": 1600, "wires": [ [ "92bf127bf074c713" ] ] }, { "id": "4e78e565c335317b", "type": "inject", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 240, "y": 1380, "wires": [ [] ] }, { "id": "5db19d2fe731de9c", "type": "switch", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "property": "payload.solar.load", "propertyType": "msg", "rules": [ { "t": "hask", "v": "state", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 350, "y": 1680, "wires": [ [ "53db38d1a30af8e3", "7f744125b78bd5f6" ], [] ] }, { "id": "d978f144c26b9951", "type": "json", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "property": "payload", "action": "obj", "pretty": false, "x": 210, "y": 1680, "wires": [ [ "5db19d2fe731de9c" ] ] }, { "id": "f5b989e1ddc46713", "type": "link in", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "link in 5", "links": [ "af8ae684feca6f96" ], "x": 105, "y": 1680, "wires": [ [ "d978f144c26b9951" ] ] }, { "id": "53db38d1a30af8e3", "type": "debug", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "debug 12", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 500, "y": 1740, "wires": [] }, { "id": "7f744125b78bd5f6", "type": "function", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "function 6", "func": "let state = msg.payload.solar.load.state\nmsg = {}\nswitch (state) {\n case \"on\":\n msg.payload = true;\n break;\n case \"off\":\n msg.payload = false;\n break;\n}\n\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 500, "y": 1680, "wires": [ [ "53202812f5b7e8b9" ] ] }, { "id": "bba8c0155dd56001", "type": "function", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "On", "func": "let value = JSON.stringify(\n {\n \"action\": \"switch\",\n \"state\": \"on\"\n }\n)\nmsg = {}\nmsg.payload = [\"solar/load\", value]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 490, "y": 1440, "wires": [ [ "7dc8cba55f4ea2a6", "f4d7de6e35cc6689" ] ] }, { "id": "2d9b1cea9886c2da", "type": "link in", "z": "0c71d7c156291f81", "g": "f0a7056e4ff89ec9", "name": "link in 2", "links": [ "af8ae684feca6f96" ], "x": 95, "y": 400, "wires": [ [ "817044b97ed1806f" ] ] }, { "id": "c6358b504e71c7b6", "type": "switch", "z": "0c71d7c156291f81", "g": "f0a7056e4ff89ec9", "name": "", "property": "payload.solar.monitor.status", "propertyType": "msg", "rules": [ { "t": "eq", "v": "waiting", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 340, "y": 400, "wires": [ [ "68387caaab696257", "defcaaf74bdd754a" ], [] ] }, { "id": "defcaaf74bdd754a", "type": "function", "z": "0c71d7c156291f81", "g": "f0a7056e4ff89ec9", "name": "function 3", "func": "msg = {}\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 490, "y": 400, "wires": [ [ "d8a9da5ca489a451" ] ] }, { "id": "68387caaab696257", "type": "debug", "z": "0c71d7c156291f81", "g": "f0a7056e4ff89ec9", "name": "debug 11", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 470, "y": 340, "wires": [] }, { "id": "d8a9da5ca489a451", "type": "link out", "z": "0c71d7c156291f81", "g": "f0a7056e4ff89ec9", "name": "link out 5", "mode": "link", "links": [ "27ba80a1376a5657", "82e4ed4aaf01deb4", "25514aa57a06bbd8" ], "x": 605, "y": 400, "wires": [] }, { "id": "27ba80a1376a5657", "type": "link in", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "link in 6", "links": [ "d8a9da5ca489a451" ], "x": 115, "y": 980, "wires": [ [ "3b088c8c7757c298" ] ] }, { "id": "817044b97ed1806f", "type": "json", "z": "0c71d7c156291f81", "g": "f0a7056e4ff89ec9", "name": "", "property": "payload", "action": "obj", "pretty": false, "x": 200, "y": 400, "wires": [ [ "c6358b504e71c7b6" ] ] }, { "id": "ebc601c4127fd4b0", "type": "redis-command", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "server": "a37a41f9.a98da8", "command": "GET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 340, "y": 1280, "wires": [ [ "1dc82ed439f5e633" ] ] }, { "id": "d43257e89b9c665d", "type": "link in", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "link in 8", "links": [ "af8ae684feca6f96" ], "x": 115, "y": 780, "wires": [ [ "a21701f9a23ef74e" ] ] }, { "id": "a21701f9a23ef74e", "type": "json", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "", "property": "payload", "action": "obj", "pretty": false, "x": 210, "y": 780, "wires": [ [ "fd38855634aadc66" ] ] }, { "id": "fd38855634aadc66", "type": "switch", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "", "property": "payload.solar.monitor.settings", "propertyType": "msg", "rules": [ { "t": "hask", "v": "sleep", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 350, "y": 780, "wires": [ [ "a97c99dc848b1cd9" ], [] ] }, { "id": "a97c99dc848b1cd9", "type": "function", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "function 8", "func": "msg.payload = msg.payload.solar.monitor.settings.sleep\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 500, "y": 780, "wires": [ [ "1c058b1f8278dd24" ] ] }, { "id": "7dc8cba55f4ea2a6", "type": "debug", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "debug 13", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 820, "y": 1700, "wires": [] }, { "id": "792e8351c67337e8", "type": "inject", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "init", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "[ \"solar/load\" ]", "payloadType": "json", "x": 190, "y": 1280, "wires": [ [ "ebc601c4127fd4b0" ] ] }, { "id": "d2ebc4a6205d1a4a", "type": "debug", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "debug 14", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 720, "y": 760, "wires": [] }, { "id": "f4d7de6e35cc6689", "type": "redis-command", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "server": "a37a41f9.a98da8", "command": "SET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 660, "y": 1480, "wires": [ [ "8efc7748f3d5fed1" ] ] }, { "id": "1dc82ed439f5e633", "type": "json", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "property": "payload", "action": "obj", "pretty": false, "x": 490, "y": 1280, "wires": [ [ "e0de4137375fbe1c" ] ] }, { "id": "e0de4137375fbe1c", "type": "function", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "function 9", "func": "switch (msg.payload.state) {\n case \"on\":\n msg.payload = true;\n break;\n case \"off\":\n msg.payload = false;\n break;\n}\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 640, "y": 1280, "wires": [ [ "636fe34fd10e61fd", "53202812f5b7e8b9" ] ] }, { "id": "25514aa57a06bbd8", "type": "link in", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "link in 9", "links": [ "d8a9da5ca489a451" ], "x": 125, "y": 1900, "wires": [ [ "e56cdda4d749deb5" ] ] }, { "id": "468feb011ff38f89", "type": "redis-command", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "server": "a37a41f9.a98da8", "command": "GET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 240, "y": 1960, "wires": [ [ "3d0feb3c85f758cd", "6425e623d113f38c" ] ] }, { "id": "3d0feb3c85f758cd", "type": "json", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "property": "payload", "action": "str", "pretty": false, "x": 490, "y": 1960, "wires": [ [ "956b7f9e4ed8e254" ] ] }, { "id": "956b7f9e4ed8e254", "type": "link out", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "link out 6", "mode": "link", "links": [ "32a15dec7972474c" ], "x": 585, "y": 1960, "wires": [] }, { "id": "0dfccc5fdfc84e44", "type": "debug", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "debug 15", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 560, "y": 2020, "wires": [] }, { "id": "fd8ab747371d8127", "type": "function", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "function 10", "func": "msg = {}\nmsg.payload = [\"solar/settings/sent\", \"yes\"]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 510, "y": 1160, "wires": [ [ "398337a8d08d5f50", "0b1cee2e609ad711" ] ] }, { "id": "3b088c8c7757c298", "type": "redis-command", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "server": "a37a41f9.a98da8", "command": "GET", "name": "", "topic": "solar/settings/sent", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 280, "y": 980, "wires": [ [ "368064192294170f" ] ] }, { "id": "9957edec9bbf0f74", "type": "debug", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "debug 16", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 640, "y": 920, "wires": [] }, { "id": "0b1cee2e609ad711", "type": "redis-command", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "server": "a37a41f9.a98da8", "command": "SET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 680, "y": 1160, "wires": [ [] ] }, { "id": "368064192294170f", "type": "switch", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "eq", "v": "yes", "vt": "str" }, { "t": "eq", "v": "no", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 3, "x": 490, "y": 980, "wires": [ [], [ "9957edec9bbf0f74", "fde241b8790dff11" ], [] ] }, { "id": "4c07e0cc9184dcc3", "type": "switch", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "eq", "v": "yes", "vt": "str" }, { "t": "eq", "v": "no", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 3, "x": 490, "y": 1900, "wires": [ [], [ "3dfb7cc559902c0d", "5cd229f70b7338e4" ], [] ] }, { "id": "3dfb7cc559902c0d", "type": "debug", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "debug 17", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 640, "y": 1820, "wires": [] }, { "id": "6425e623d113f38c", "type": "function", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "function 11", "func": "msg = {}\nmsg.payload = [\"solar/load/sent\", \"yes\"]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 490, "y": 2080, "wires": [ [ "32ba210ab2a02102", "0dfccc5fdfc84e44" ] ] }, { "id": "32ba210ab2a02102", "type": "redis-command", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "server": "a37a41f9.a98da8", "command": "SET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 660, "y": 2080, "wires": [ [] ] }, { "id": "e56cdda4d749deb5", "type": "redis-command", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "server": "a37a41f9.a98da8", "command": "GET", "name": "", "topic": "solar/load/sent", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 290, "y": 1900, "wires": [ [ "4c07e0cc9184dcc3" ] ] }, { "id": "b6bf5fdadf2f16d6", "type": "redis-command", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "server": "a37a41f9.a98da8", "command": "SET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 900, "y": 640, "wires": [ [] ] }, { "id": "07e3be91304ca577", "type": "function", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "function 12", "func": "msg = {}\nmsg.payload = [\"solar/settings/sent\", \"no\"]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 730, "y": 640, "wires": [ [ "b6bf5fdadf2f16d6" ] ] }, { "id": "8efc7748f3d5fed1", "type": "function", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "function 13", "func": "msg = {}\nmsg.payload = [\"solar/settings/sent\", \"no\"]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 830, "y": 1480, "wires": [ [ "c9279b012026601d" ] ] }, { "id": "c9279b012026601d", "type": "redis-command", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "server": "a37a41f9.a98da8", "command": "SET", "name": "", "topic": "", "params": "[]", "paramsType": "json", "payloadType": "json", "block": false, "x": 1000, "y": 1480, "wires": [ [] ] }, { "id": "fde241b8790dff11", "type": "function", "z": "0c71d7c156291f81", "g": "188827854f36f6f9", "name": "function 14", "func": "msg = {}\nmsg.payload = [ \"solar/settings\" ]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 690, "y": 980, "wires": [ [ "44b5b1d6bf1afc9a" ] ] }, { "id": "5cd229f70b7338e4", "type": "function", "z": "0c71d7c156291f81", "g": "41e0ca36ab088b06", "name": "function 15", "func": "msg = {}\nmsg.payload = [ \"solar/load\" ]\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 690, "y": 1900, "wires": [ [ "468feb011ff38f89" ] ] }, { "id": "725ed69c.6d76a8", "type": "mqtt-broker", "name": "", "broker": "", "port": "1883", "clientid": "", "usetls": false, "compatmode": false, "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }, { "id": "0e0468a7bcaa4a7a", "type": "ui_group", "name": "Panel", "tab": "8c460da79935c16f", "order": 4, "disp": true, "width": "6", "collapse": false }, { "id": "9e0ac0fcf4e571a8", "type": "ui_group", "name": "Control", "tab": "8c460da79935c16f", "order": 2, "disp": true, "width": "6", "collapse": false }, { "id": "a37a41f9.a98da8", "type": "redis-config", "name": "Local", "options": "{}", "cluster": false, "optionsType": "json" }, { "id": "6ef616c0de00894f", "type": "ui_group", "name": "Battery", "tab": "8c460da79935c16f", "order": 3, "disp": true, "width": "6", "collapse": false }, { "id": "56ef03ec967ddd2d", "type": "ui_group", "name": "Efficiency", "tab": "8c460da79935c16f", "order": 4, "disp": true, "width": "6", "collapse": false }, { "id": "9fa8b179879daba5", "type": "position-config", "name": "", "isValide": "true", "angleType": "deg", "timeZoneOffset": "99", "timeZoneDST": "0", "stateTimeFormat": "3", "stateDateFormat": "12", "contextStore": "" }, { "id": "8c460da79935c16f", "type": "ui_tab", "name": "Solar", "icon": "dashboard", "disabled": false, "hidden": false } ]