RMIS-1 Example Code Solar Wind Battery Monitor
The below RMIS-1 test code example has been provided as a means to learn, develop and evolve. The purpose of this test code is to cycle through the various main functions of the board as part of bring up testing.
This example code has been developed for a test Solar/Wind/Battery/UPS project – which is very much work in progress. It allows monitoring the voltage and charge current from Solar and Wind, and also monitoring the charge/discharge of the batteries.
- Include library headers. Configure GPIO and Ports.
- Setup
- Initialise Comms, Wi-fi and ThingSpeak connection.
- Loop
- Each of the ADC outputs are multi-sampled, then averaged [AverageSamples], in order to remove/reduce jitter.
- A small level of Hysteresis [HysteresisValue], is then applied to help reduce duplicate postings.
- The combined Averaging and Hysteresis provide for a more stable updating of each channel, as they are looped, so reducing duplicate updates to ThingSpeak.
- When a value changes on each channel a ‘+’ is displayed at the end of the line and a batch update to ThingSpeak is made.
- Should an error be encountered when posting an update, the ESP will automatically reboot to re-initialise the connection.
- Read data is compared and then only updated if changed. Example output: https://thingspeak.com/channels/1697689
- LED flashed as a heat-beat.
This test code contains a number of useful elements. It is OPEN SOURCE and although is not intended for real world use, it may be freely used, or modified as needed.
Note: Whilst the RMIS-1 test code is focused on updating a ThingSpeak channel, it has been written in such a way to easily be modified for other MQTT brokers and applications.
ThingSpeak Example of the Test Data Output can be found https://thingspeak.com/channels/1697689
The bread-board setup below includes a basic wind generator (3phase), to DC output. A simple off-the-shelf battery charge monitor and the RMIS-1-60 (as we are using 48V DC).
Two diodes were included, just to isolate the Wind and Solar input supply, so I could sample the raw input voltages, separate to the battery.
When running the example code, it will output this example to the console/serial out. The below code monitors the outputs from the current and voltage of each channel. Have tried to annotate as much as possible, in order to provide information.
Attempting to connect to xxxx v1.220418 Firmware - Initialized DitroniX RMIS ESP6288 SDK PCA v2204-106 G8PUO Solar, Wind Generator and Battery Monitor Attempting to connect to xxxx WiFi SSID xxxx WiFi IP xx.xx.xx.x WiFi GW xx.xx.xx.x WiFi MASK 255.255.255.0 WiFi MAC xx:xx:xx:xx:BB:0A WiFi Hostname ESP-RMIS-BB0A WiFi RSSI -57 Initialised ThingSpeak Scanning I2C Found Devices: Devices: (0x18) Found 1 Device(s). Connected to Digital Temperature Sensor MCP9808 Test PCB Temp: 30.94°C 0.00 Current A -/+ (510) LastWindCurrent + 0.78 Voltage V (6) LastWindVoltage + 0.33 Current A -/+ (515) LastSolarCurrent + 14.27 Voltage V (124) LastSolarVoltage + 0.13 Current A -/+ (512) LastBatteryCurrent + 51.48 Voltage V (447) LastBatteryVoltage + 13.00 Voltage V (869) LastPCBVoltage + 30.9 Temperature °C (30) LastTemperature + Voltage 12.97 System Uptime 0 days, 0 hours, 0 minutes, 6 seconds Channel 1697689 update successful. -0.00 Current A -/+ (509) LastWindCurrent 0.77 Voltage V (6) LastWindVoltage 0.32 Current A -/+ (515) LastSolarCurrent 14.27 Voltage V (124) LastSolarVoltage 0.13 Current A -/+ (512) LastBatteryCurrent 51.46 Voltage V (447) LastBatteryVoltage 13.00 Voltage V (869) LastPCBVoltage 30.9 Temperature °C (30) LastTemperature No Update
/* Dave Williams, G8PUO, DitroniX 2019-2022 (ditronix.net) RMIS ESP6288 DC Remote Monitor IoT System ESP-12S SDK (Wifi Enabled, ThingSpeak Enabled) PCA v2204-106 - Test Code Firmware 1.220418 - 18th April 2022 This example code has been developed for a test Solar/Wind/Battery/UPS project - which is work in progress. Monitoring the voltage and charge current from Solar and Wind, and also monitoring the charge/discharge of the batteries. Each of the ADC outputs are multi-sampled, then averaged [AverageSamples], in order to remove/reduce jitter A small level of Hysteresis [HysteresisValue], is then applied to help reduce duplicate postings. These provide more stable updates of each channel, as they are looped, so reducing duplicate updates to ThingSpeak. When a value changes on each channel a + is displayed at the end of the line and a batch update to ThingSpeak is made. Should an error be encountered when posting an update, the ESP will automatically reboot to re-initialise the connection. The purpose of this test code is to cycle through the various main functions of the board as part of bring up testing. Read data is compared and then only updated if changed. Example output: https://thingspeak.com/channels/1697689 This test code is OPEN SOURCE and although is not intended for real world use, it may be freely used, or modified as needed. WIKI. Please see https://ditronix.net/wiki/ for further information */ /* Examaple Output 0.00 Current A -/+ (510) LastWindCurrent 0.52 Voltage V (4) LastWindVoltage 0.06 Current A -/+ (510) LastSolarCurrent 13.44 Voltage V (116) LastSolarVoltage 0.17 Current A -/+ (512) LastBatteryCurrent 55.32 Voltage V (480) LastBatteryVoltage 12.39 Voltage V (828) LastPCBVoltage + 30.6 Temperature °C (30) LastTemperature Voltage 12.39 System Uptime 0 days, 13 hours, 41 minutes, 1 seconds Channel xxxx update successful. Input Current and Sensitivity (measurement scale) -/+ 5A – 185 mV/A -/+ 20A – 100 mV/A -/+ 30 A – 66 mv/A */ #include "Arduino.h" #include <ESP8266WiFi.h> #include "Wire.h" #include "Adafruit_MCP9808.h" // Temperature Sensor #include <Adafruit_MCP3008.h> // 8 Channel ADC #include "ThingSpeak.h" // ThingSPeak IoT #include "uptime_formatter.h" //https://github.com/YiannisBourkelis/Uptime-Library // ######### OBJECTS ######### Adafruit_MCP9808 TempSensor = Adafruit_MCP9808(); Adafruit_MCP3008 adc; // ######### VARIABLES / DEFINES / STATIC ######### // App String AppVersion = "v1.220418"; String AppBuild = "DitroniX RMIS ESP6288 SDK PCA v2204-106"; String AppName = "G8PUO Solar, Wind Generator and Battery Monitor"; // Variables char SensorResult[10]; float SensorRAW; float SensorValue; float LastWindCurrent; float LastWindVoltage; float LastSolarCurrent; float LastSolarVoltage; float LastBatteryCurrent; float LastBatteryVoltage; float LastDumpCurrent; float LastDumpVoltage; float LastTemperature; float LastPCBVoltage; boolean UpdateFlag; // Constants const int HysteresisValue = 1; // RAW Value +/- Threshold (Software Window Comparator) const int AverageSamples = 100; // Average Multi-Samples on each Channel. // MCP9808 Temp Sensor int MCP9808 = 0x18; // PCB Temperature Sensor Address // WiFi const char* ssid = "xxxx"; // network SSID (name) const char* password = "xxxx"; // network password String Hostname = "ESP-RMIS-"; // Hostname Prefix WiFiClient client; // Initialize the client library // ThingSpeak unsigned long myChannelNumber = 0; // Put your ThingSpeak Channel ID here const char * myWriteAPIKey = "xxxx"; // Put your WriteAPIKey here // **************** IO **************** // Define I2C (Expansion Port) #define I2C_SDA 4 #define I2C_SCL 5 // Define SPI (Expansion Port) #define SPI_MISO 12 #define SPI_MOSI 13 #define SPI_SCK 14 #define SPI_SS 15 // Define GPIO 16 for General IO or 1-Wire Use. #define GP16 16 // **************** OUTPUTS **************** #define LED_Status 2 // Define ESP Output Port LED // **************** INPUTS **************** #define ADC A0 // Define ADC (0 DC - 15V DC) // ######### FUNCTIONS ######### // Scan I2C for Devices void scan_i2c_devices() { Serial.print("Scanning I2C\t"); byte count = 0; Serial.print("Found Devices: "); Serial.print(" Devices: "); for (byte i = 8; i < 120; i++) { Wire.beginTransmission(i); if (Wire.endTransmission() == 0) { Serial.print(" (0x"); Serial.print(i, HEX); Serial.print(")\t"); count++; delay(1); } } Serial.print("Found "); Serial.print(count, HEX); Serial.println(" Device(s)."); } // Initialise WiFi and ThingSpeak void InitialiseWiFi() { // Connect or reconnect to WiFi if (WiFi.status() != WL_CONNECTED) { Serial.println("Attempting to connect to " + String(ssid)); WiFi.begin(ssid, password); WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); WiFi.persistent(true); // Stabalise for slow Access Points delay(5000); // Force Hostname Hostname = Hostname + WiFi.macAddress().substring(WiFi.macAddress().length() - 5, WiFi.macAddress().length()); Hostname.replace(":", ""); WiFi.setHostname(Hostname.c_str()); // Wifi Information Serial.println("WiFi SSID \t " + String(ssid)) + "(Wifi Station Mode)"; Serial.printf("WiFi IP \t %s\n", WiFi.localIP().toString().c_str()); Serial.printf("WiFi GW \t %s\n", WiFi.gatewayIP().toString().c_str()); Serial.printf("WiFi MASK \t %s\n", WiFi.subnetMask().toString().c_str()); Serial.println("WiFi MAC \t " + WiFi.macAddress()); Serial.printf("WiFi Hostname \t %s\n", WiFi.hostname().c_str()); Serial.println("WiFi RSSI \t " + String(WiFi.RSSI())); Serial.println(""); // Initialise ThingSpeak Connection ThingSpeak.begin(client); // Initialize ThingSpeak Serial.println("Initialised ThingSpeak"); } } // Initialise Temperature Sensor void InitialiseTemperatureSensor() { // PCB Temperature Sensor if (!TempSensor.begin()) { // Sensor or I2C Issue Serial.println("Couldn't find MCP9808! Sensor or I2C Issue. Check the PCB !!"); } else { Serial.print("Connected to Digital Temperature Sensor MCP9808\t"); // Set Resolution 0.0625°C 250 ms and Wake Up TempSensor.setResolution(3); // Read PCB_Temp Sensor and Output //TempSensor.wake(); // Wake Up SensorRAW = TempSensor.readTempC(); Serial.print("Test PCB Temp: "); Serial.print(SensorRAW); Serial.println("°C"); //TempSensor.shutdown_wake(1); // Sleep Optional } } // Calculate Average Value and Reduce Jitter float CalculateAverage (int SensorChannel) { float AverageRAW = 0; for (int i = 0; i < AverageSamples; i++) { AverageRAW = AverageRAW + adc.readADC(SensorChannel); delay(1); } AverageRAW = AverageRAW / AverageSamples; return AverageRAW; } // ######### SETUP ######### void setup() { // Stabalise delay(500); // Initialize UART: Serial.begin(115200, SERIAL_8N1); //115200 while (!Serial); Serial.println(""); Serial.println(AppVersion + " Firmware - Initialized"); Serial.println(AppBuild); Serial.println(AppName); Serial.println(""); // LED pinMode(LED_Status, OUTPUT); // WiFi InitialiseWiFi(); // Initialise I2C Wire.begin(I2C_SDA, I2C_SCL); scan_i2c_devices(); InitialiseTemperatureSensor(); // Initialise ADC adc.begin(); Serial.println(""); } // ######### LOOP ######### void loop() { // Example of RMIS monitoring port pin allocation. Channels arranged for test purposes. // Channel 1 Wind Current (Pins 1+ and 2) // Channel 1 Wind Voltage (Pin 1+) // Channel 2 Solar Current (Pins 3+ and 4) // Channel 2 Solar Voltage (Pin 3+) // Channel 3 Battery Current (Pins 5+ and 6) // Channel 3 Battery Voltage (Pin 5+) // Channel 4 not used // Wind Current Channel 1 // Read Channel 1 Current 510 = 0A Using 30A Sensor (Calibrated) SensorRAW = CalculateAverage(0); SensorValue = ( SensorRAW - 510 ) / 16.00; dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Current A -/+ (" + String(int(SensorRAW)) + ")\t LastWindCurrent" ); if (SensorRAW < LastWindCurrent - HysteresisValue || SensorRAW > LastWindCurrent + HysteresisValue) { LastWindCurrent = SensorRAW; ThingSpeak.setField(1, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // Wind Voltage Channel 1 // Read Channel 1 Voltage 0-5V 0-1023 (Calibrated) SensorRAW = CalculateAverage(1); SensorValue = ( SensorRAW * 117.87 ) / 1024; dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Voltage V (" + String(int(SensorRAW)) + ")\t\t LastWindVoltage" ); if (SensorRAW < LastWindVoltage - HysteresisValue || SensorRAW > LastWindVoltage + HysteresisValue) { LastWindVoltage = SensorRAW; ThingSpeak.setField(2, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // Solar Current Channel 2 // Read Channel 2 Current 510 = 0A Using 30A Sensor (Calibrated) SensorRAW = CalculateAverage(2); SensorValue = ( SensorRAW - 510 ) / 16.00; dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Current A -/+ (" + String(int(SensorRAW)) + ")\t LastSolarCurrent" ); if (SensorRAW < LastSolarCurrent - HysteresisValue || SensorRAW > LastSolarCurrent + HysteresisValue) { LastSolarCurrent = SensorRAW; ThingSpeak.setField(3, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // Solar Voltage Channel 2 // Read Channel 2 Voltage 0-5V 0-1023 (Calibrated) SensorRAW = CalculateAverage(3); SensorValue = ( SensorRAW * 117.87 ) / 1024; dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Voltage V (" + String(int(SensorRAW)) + ")\t LastSolarVoltage" ); if (SensorRAW < LastSolarVoltage - HysteresisValue || SensorRAW > LastSolarVoltage + HysteresisValue) { LastSolarVoltage = SensorRAW; ThingSpeak.setField(4, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // Battery Current Channel 3 // Read Channel 3 Current 510 = 0A Using 30A Sensor (Calibrated) SensorRAW = CalculateAverage(4); SensorValue = ( SensorRAW - 510 ) / 16.00; dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Current A -/+ (" + String(int(SensorRAW)) + ")\t LastBatteryCurrent" ); if (SensorRAW < LastBatteryCurrent - HysteresisValue || SensorRAW > LastBatteryCurrent + HysteresisValue) { LastBatteryCurrent = SensorRAW; ThingSpeak.setField(5, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // Battery Voltage Channel 3 // Read Channel 3 Voltage 0-5V 0-1023 (Calibrated) SensorRAW = CalculateAverage(5); SensorValue = ( SensorRAW * 117.87 ) / 1024; dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Voltage V (" + String(int(SensorRAW)) + ")\t LastBatteryVoltage" ); if (SensorRAW < LastBatteryVoltage - HysteresisValue || SensorRAW > LastBatteryVoltage + HysteresisValue) { LastBatteryVoltage = SensorRAW; ThingSpeak.setField(6, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // Dump Current Channel 4 // Reserved. Channels 7 & 8 are used for the below during development. // PCB Voltage SensorRAW = analogRead(ADC); SensorValue = (SensorRAW * 15.32 ) / 1024; // Divider value may need tweaking dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Voltage V (" + String(int(SensorRAW)) + ")\t LastPCBVoltage" ); if (SensorRAW < LastPCBVoltage - HysteresisValue || SensorRAW > LastPCBVoltage + HysteresisValue) { LastPCBVoltage = SensorRAW; ThingSpeak.setField(7, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // PCB Temperature // Read PCB_Temp Sensor Temperature ºC and round to 1 decimal place SensorRAW = TempSensor.readTempC(); dtostrf (SensorRAW, 6, 1, SensorResult); sprintf (SensorResult, "%s", SensorResult); Serial.print(String (SensorResult) + "\t Temperature °C (" + String(int(SensorRAW)) + ")\t LastTemperature" ); if (SensorRAW < LastTemperature - HysteresisValue || SensorRAW > LastTemperature + HysteresisValue) { LastTemperature = SensorRAW; ThingSpeak.setField(8, SensorResult ); // Update ThingSpeak Field UpdateFlag = true; Serial.println(" +"); } else { Serial.println(""); } // Update ThingSpeak (Only on change) if (UpdateFlag == true) { UpdateFlag = false; SensorRAW = analogRead(ADC); SensorValue = (SensorRAW * 15.32 ) / 1024; // Divider value may need tweaking dtostrf (SensorValue, 6, 2, SensorResult); sprintf (SensorResult, "%s", SensorResult); ThingSpeak.setStatus("DEV TEST ONLY " + AppVersion + " - " + SensorResult + "V. Last: " + uptime_formatter::getUptime()); Serial.print("Voltage " + String(SensorResult) ); Serial.println(" System Uptime " + uptime_formatter::getUptime()); // Write up to 8 channels to ThingSpeak. Check Update Error # int UpdateErrorCode = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey); // Check for any Update Errors if (UpdateErrorCode == 200) { Serial.print ("Channel "); Serial.println(String(myChannelNumber) + " update successful.\n"); } else { Serial.println("Problem updating channel. HTTP error code " + String(UpdateErrorCode)); Serial.println("Automatically Restarting ESP .... "); ESP.restart(); } } else { Serial.println(" No Update\n"); } // Hearbeat LED and Loop Delay digitalWrite(LED_Status, LOW); // turn the LED ON by making the voltage HIGH delay(150); // wait for a second digitalWrite(LED_Status, HIGH); // turn the LED OFF by making the voltage LOW }