#include #include //#include #include #define F_CPU 16000000 #include //#include //#include #include #include #include #include #include #include #include //#include #include "PubSubClient.h" #include SoftwareSerial mySerial(13, 14); // RX, TX // JSON Parsing buffer StaticJsonBuffer<200> jsonBuffer; //char mqtt_server[64] = "mqtt.masdelaginebra.com"; char mqtt_server[64] = "192.168.2.16"; char outTopic[64] = "pip4048Monitor/fromesp"; char bootTopic[64] = "REBOOT"; char inTopic[64] = "pip4048Monitor/toesp"; char mqttMsgTopic[64] = "pip4048Monitor/toesp/mqttMsg"; char mqttUser[64] = "pip4048"; char mqttPassword[64] = "provence2"; char mqttID[64] = "pip4048Monitor"; long dely, dely5, dely10, dely60; // some timers int whichPIPCommand = 0, lastPIPCommand = 0; // which PIP values to read int oneHour = 0, oneDay = 24*60*60; int hangUpTime = 60; // 60 seconds timeout float solarVoltage = 0, solarVoltageAverage = 0; // Enter a MAC address and IP address for your controller below. // The IP address will be dependent on your local network: uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xE1 }; IPAddress ip(192, 168, 2, 177); IPAddress dnss(192, 168, 2, 1); IPAddress subnet(255, 255, 255, 0); // Initialize the Ethernet server library // with the IP address and port you want to use // (port 80 is default for HTTP): EthernetServer server(80); EthernetClient eclient; #define ETHERNET_RST 3 //PubSubClient client(mqtt_server, 1883, callback, eclient); PubSubClient client(eclient); static uint8_t pipInputBuf[500]; int pipInputPointer = 0; struct { char gridVoltage[16]; char gridFrequency[16]; char acOutput[16]; char acFrequency[16]; char acApparentPower[16]; char acActivePower[16]; char loadPercent[16]; char busVoltage[16]; char batteryVoltage[16]; char batteryChargeCurrent[16]; char batteryCharge[16]; char inverterTemperature[16]; char PVCurrent[16]; char PVVoltage[16]; } pipVals; struct { uint8_t qpigs[5] = {'Q', 'P', 'I', 'G', 'S'}; uint8_t qpiws[5] = {'Q','P', 'I', 'W', 'S'}; } pipCommands; #define LED 15 // Defines for the current sensor ADC #define SELPIN1 18//Selection Pin 1 #define SELPIN2 19 //Selection Pin 2 #define SELPIN3 20 //Selection Pin 3 #define SELPIN4 21 //Selection Pin 4 //#define DATAOUT 5 // MOSI //#define DATAIN 6 // MISO //#define SPICLOCK 7 // Clock #define ADCMOSI 26 #define ADCMISO 25 #define ADCCLK 24 #define DATAOUT 26 // MOSI #define DATAIN 25 // MISO #define SPICLOCK 24 // Clock #define SOLARVOLTAGE 31 int readvalue; int powerSamples[60][4][8]; // Readings from the ADCs int solarPower[4][8]; // Readings from the ADCs int whichSolarPower = 0; int totalSolarPower = 0; static unsigned long unixTime = 0; static struct tm timeinfo; static time_t theTime; int yDay = 0; EthernetUDP udp; // GPS Receiver TinyGPSPlus gps; char latitude[10], longitude[10]; float dLatitude, dLongitude; // ==================================================================================================================== char * catString (char *s1, char *s2); // Background 1 second interrupt void bTimer(void) { char s[128]; wdt_reset(); // Check to see that we've been getting valid MQTT messages in the last minute, else reset if (hangUpTime == 0) { noInterrupts(); // Disable interrupts for (;;); // Loop until reset } else --hangUpTime; if (unixTime) { ++unixTime; // increment unixTime if it's non-zero } if (oneHour) --oneHour; // One hour timer if (oneDay == 0) { // Request a time update sprintf(s,"{\"id\":\"%s\",\"desc\":\"PIP4048 Monitor\",\"attribute\":\"NONE\",\"fail\":\"0\"}", mqttID); client.publish("esplogon", s); oneDay = 24*60*60; // Rest timer } else --oneDay; digitalWrite(LED, !digitalRead(LED)); } // ==================================================================================================================== void callback(char *topic, unsigned char* payload, unsigned int length) { char s[100]; hangUpTime = 60; // Reset the hangup timer if we get an MQTT message payload[length] = 0; // Terminate payload string if (!strcmp(topic, catString(inTopic,"/power"))) // send the current solar power { sprintf(s,"%d", totalSolarPower); client.publish(catString(outTopic,"/totalSolarPower"), s); } else if (!strcmp(topic, inTopic)) // Set the time if we have an empty inTopic - i.e. just /toesp { JsonObject& root = jsonBuffer.parseObject(payload); if (root.success()) { unixTime = root["time"]; sprintf(s, "====>>>> TIME %lu (%s)", unixTime, payload); mySerial.println(s); } else { sprintf(s, "====>>>> FAIL TIME (%s)", payload); mySerial.println(s); } } else if (!strcmp(topic, catString(inTopic,"/reset"))) // Reset the board { noInterrupts(); // Disable interrupts for (;;); // Loop until reset } } void setup() { char s[64]; wdt_enable(WDTO_8S); // Enable the watchdog timer with an 8 seconds timeout pinMode(SS, OUTPUT); // Set network select pin high (inactive) digitalWrite(SS, 1); pinMode(ETHERNET_RST, OUTPUT); // Ethernet Reset digitalWrite(ETHERNET_RST, 0); pinMode(SELPIN1, OUTPUT); pinMode(SELPIN2, OUTPUT); pinMode(SELPIN3, OUTPUT); pinMode(SELPIN4, OUTPUT); // disable device to start with digitalWrite(SELPIN1, HIGH); digitalWrite(SELPIN2, HIGH); digitalWrite(SELPIN3, HIGH); digitalWrite(SELPIN4, HIGH); pinMode(DATAIN, INPUT_PULLUP); pinMode(DATAOUT, OUTPUT); pinMode(SPICLOCK, OUTPUT); pinMode(ADCCLK, OUTPUT); pinMode(ADCMOSI, OUTPUT); pinMode(ADCMISO, INPUT); pinMode(SOLARVOLTAGE, INPUT); analogReference(INTERNAL2V56); pinMode(LED, OUTPUT); for (int i = 0; i < 5; i++) { delay(200); digitalWrite(LED, 0); delay(200); digitalWrite(LED, 1); wdt_reset(); } digitalWrite(ETHERNET_RST, 1); delay(200); Serial.begin(9600); Serial1.begin(2400); mySerial.begin(9600); mySerial.println("Starting PIP Monitor"); // Initialise the background interrupt FlexiTimer2::set(1000, bTimer); FlexiTimer2::start(); // start the Ethernet connection and the server: mySerial.println("Starting Ethernet"); // Ethernet.begin(mac); Ethernet.begin(mac, ip, dnss, dnss, subnet); mySerial.println("Starting Server"); server.begin(); mySerial.print("server is at "); mySerial.println(Ethernet.localIP()); // Add IP address to mtqqID //sprintf(mqttID, "%s_%d.%d.%d.%d", mqttID, Ethernet.localIP()[0], Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]); client.setServer(mqtt_server, 1883); client.setCallback(callback); if (reconnect()) // Connect to MQTT server mySerial.println("Connected OK"); else mySerial.println("NOT connected OK"); delay(2000); //reconnect(); if (client.subscribe(catString(inTopic, "/#"))) { mySerial.println("Subscribe OK"); client.publish(catString(outTopic, "/login"), "Pip Monitor Logged In"); } else mySerial.println("Subscribe NOT OK"); dely = dely5 = dely10 = dely60 = millis(); // Various timers wdt_reset(); } // QPIGS 0x51 0x50 0x49 0x47 0x53 0xb7 0xa9 0x0d //uint8_t txArray[] = {0x51, 0x50, 0x49, 0x47, 0x53}; void loop() { char s[100]; int i; uint16_t crc; char *val; char pipstatus[40]; float power; // Get chars from the GPS receiver while (Serial.available()) gps.encode(Serial.read()); if (gps.location.isUpdated()) { int t[10]; mySerial.print("\n\n===============================\n"); dLatitude = gps.location.lat(); dLongitude = gps.location.lng(); ftoa(latitude, dLatitude, 4); ftoa(longitude, dLongitude, 4); timeinfo.tm_hour = t[0] = gps.time.hour(); timeinfo.tm_min = t[1] = gps.time.minute(); timeinfo.tm_sec = t[2] = gps.time.second(); timeinfo.tm_mday = t[3] = gps.date.day(); timeinfo.tm_mon = t[4] = gps.date.month(); timeinfo.tm_year = t[5] = gps.date.year(); // sprintf(s, "%d/%d/%d %d:%d:%d", gps.date.day(), gps.date.month(), gps.date.year(), gps.time.hour(), gps.time.minute(), gps.time.second()); // strptime(s, "%d/%m/%Y %H:%M:%s", &timeinfo); theTime = mktime(&timeinfo); // sprintf(s,"\r\n==============\\rnLat=%s, long=%s (%d:%d, %d/%d/%d\r\n===================\n\r", latitude, longitude, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_mday, timeinfo.tm_mon, timeinfo.tm_year); sprintf(s,"\r\n==============\\rnLat=%s, long=%s (%d:%d, %d/%d/%d = %ld)\r\n===================\n\r", latitude, longitude, t[0], t[1], t[3], t[4], t[5], theTime); mySerial.print(s); } if (dely5 + 5000 < millis()) { int i; dely5 = millis(); sprintf(s, "Packet Sent - command %d\n", whichPIPCommand); mySerial.print(s); switch (whichPIPCommand) { case 0: pipSend(pipCommands.qpigs, sizeof(pipCommands.qpigs)); lastPIPCommand = 0; whichPIPCommand = 1; // Next command break; case 1: pipSend(pipCommands.qpiws, sizeof(pipCommands.qpiws)); lastPIPCommand = 1; whichPIPCommand = 0; // Next command break; } // Send the time out to the solar trackers //sprintf(s,"****TIME!%ld", unixTime); //mySerial.println(s); //mySerial.println(unixTime); //Serial.print(s); //Serial.write(13); // //Serial.write(10); // } if (dely + 1000 < millis()) { float il = analogRead(SOLARVOLTAGE); solarVoltage = (((il * 2.56) / 1024) * (330 + 8.2)) / 8.2; solarVoltageAverage += solarVoltage; sprintf(s, "Solar Voltage = %d (%d)", (int) solarVoltage, analogRead(SOLARVOLTAGE)); mySerial.println(s); totalSolarPower = 0; for (int whichADCBoard = 0; whichADCBoard < 4; whichADCBoard++) for (int whichADC = 0; whichADC < 8; whichADC++) { il = read_adc(whichADCBoard, whichADC); if ((il < 20) || (il == 2048) || (il == 2047)) il = 0; // Get rid of signal noise power = (il * 5 / 4096) * 10 * solarVoltage; // VOLTS * AMPS - 1 amp = 100mV totalSolarPower += power; powerSamples[whichSolarPower][whichADCBoard][whichADC] = (int) power; } sprintf(s, "Total Power = %d", totalSolarPower); mySerial.println(s); if (++whichSolarPower == 60) // Taken 60 samples - 1 minute { // Now average all ten samples whichSolarPower = 0; for (int whichADCBoard = 0; whichADCBoard < 4; whichADCBoard++) { for (int whichADC = 0; whichADC < 8; whichADC++) { solarPower[whichADCBoard][whichADC] = 0; for (int whichSample = 0; whichSample < 60; whichSample++) solarPower[whichADCBoard][whichADC] += powerSamples[whichSample][whichADCBoard][whichADC]; solarPower[whichADCBoard][whichADC] = (solarPower[whichADCBoard][whichADC] / 60); } } // Now zero the array ready for the next set of readings for (int whichADCBoard = 0; whichADCBoard < 4; whichADCBoard++) for (int whichADC = 0; whichADC < 8; whichADC++) powerSamples[whichSolarPower][whichADCBoard][whichADC] = 0; // Send the solar current data to the MQTT server in JSON format char current[256]; mySerial.println("Sending Solar Power Data"); sprintf(current, "%d,", (int) (solarVoltageAverage / 60)); solarVoltageAverage = 0; // Clear the running total for (int x = 0; x < 3; x++) { for (int y = 0; y < 8; y++) { sprintf(current + strlen(current), "%d", solarPower[x][y]); if (((x * 8) + y) < 23) sprintf(current + strlen(current), ","); // Comma separator except for last value } } client.publish(catString(outTopic, "/solarPower"), current); } dely = millis(); } // One minute if (dely60 + 60000 < millis()) { dely60 = millis(); if (oneHour == 0) { //// Get the time from an NTP server //unixTime = ntpUnixTime(udp); //mySerial.print("Unixtime = "); //mySerial.println(unixTime); oneHour = 3600; // reset the count } } if (client.connected()) client.loop(); else { mySerial.println("Not Connected"); reconnect(); } // Check any return from the pip4048 i = processPipInput(&crc); if (i > 0) // Got a good packet { mySerial.println("Got good packet"); mySerial.println((char *) pipInputBuf); switch (lastPIPCommand) // Which paccket are we expecting? { case 0: // QPIGS // Now split the packet into the values val = strtok((char *) pipInputBuf, " "); // get the first value strcpy(pipVals.gridVoltage, val + 1); // Skip the initial '(' val = strtok(0, " "); // Get the next value strcpy(pipVals.gridFrequency, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.acOutput, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.acFrequency, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.acApparentPower, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.acActivePower, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.loadPercent, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.busVoltage, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.batteryVoltage, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.batteryChargeCurrent, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.batteryCharge, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.inverterTemperature, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.PVCurrent, val); val = strtok(0, " "); // Get the next value strcpy(pipVals.PVVoltage, val); client.publish(catString(outTopic, "/gridVoltage"), pipVals.gridVoltage); client.publish(catString(outTopic, "/gridFrequency"), pipVals.gridFrequency); client.publish(catString(outTopic, "/acOutput"), pipVals.acOutput); client.publish(catString(outTopic, "/acFrequency"), pipVals.acFrequency); client.publish(catString(outTopic, "/acApparentPower"), pipVals.acApparentPower); client.publish(catString(outTopic, "/acActivePower"), pipVals.acActivePower); client.publish(catString(outTopic, "/loadPercent"), pipVals.loadPercent); client.publish(catString(outTopic, "/busVoltage"), pipVals.busVoltage); client.publish(catString(outTopic, "/batteryVoltage"), pipVals.batteryVoltage); client.publish(catString(outTopic, "/batteryChargeCurrent"), pipVals.batteryChargeCurrent); client.publish(catString(outTopic, "/batteryCharge"), pipVals.batteryCharge); client.publish(catString(outTopic, "/inverterTemperature"), pipVals.inverterTemperature); client.publish(catString(outTopic, "/PVCurrent"), pipVals.PVCurrent); client.publish(catString(outTopic, "/PVVoltage"), pipVals.PVVoltage); // Calculate PV Power int I, V; I = atoi(pipVals.PVCurrent); V = atoi(pipVals.PVVoltage); sprintf(s, "%d", I * V); client.publish(catString(outTopic, "/PVPower"), s); break; case 1: // QPIWS val = strtok((char *) pipInputBuf, " "); // get the first value strcpy(pipstatus, val + 1); // Skip the initial '(' - make a copy of the returned stricg for processing // Now send the various PIP status messages - CONVERT TO JSON FOR USE if (pipstatus[1] == '1') client.publish(catString(outTopic, "/status"), "Inverter Fault"); if (pipstatus[2] == '1') client.publish(catString(outTopic, "/status"), "Bus Over Voltage"); if (pipstatus[3] == '1') client.publish(catString(outTopic, "/status"), "Bus Under Voltage"); if (pipstatus[4] == '1') client.publish(catString(outTopic, "/status"), "Bus Soft Fail"); if (pipstatus[5] == '1') client.publish(catString(outTopic, "/status"), "Line Fail"); if (pipstatus[6] == '1') client.publish(catString(outTopic, "/status"), "OPV Short"); if (pipstatus[7] == '1') client.publish(catString(outTopic, "/status"), "Inverter Voltage Too Low"); if (pipstatus[8] == '1') client.publish(catString(outTopic, "/status"), "Inverter Voltage Too High"); if (pipstatus[9] == '1') client.publish(catString(outTopic, "/status"), "Over Temperature"); if (pipstatus[10] == '1') client.publish(catString(outTopic, "/status"), "Fan Locked"); if (pipstatus[11] == '1') client.publish(catString(outTopic, "/status"), "Battery Voltage Too High"); if (pipstatus[12] == '1') client.publish(catString(outTopic, "/status"), "Battery Low Alarm"); if (pipstatus[14] == '1') client.publish(catString(outTopic, "/status"), "Battery Under Shutdown"); if ((pipstatus[16] == '1') && (pipstatus[1] == '0')) client.publish(catString(outTopic, "/status"), "Overload - Warning"); if ((pipstatus[16] == '1') && (pipstatus[1] == '1')) client.publish(catString(outTopic, "/status"), "Overload - FAULT"); if (pipstatus[17] == '1') client.publish(catString(outTopic, "/status"), "EEPROM Fault"); break; } } if (i == -1) // Got a bad packet { mySerial.println("Got BAD packet"); mySerial.println((char *) pipInputBuf); } Ethernet.maintain(); } // Check for input from serial1, put it into a buffer and then return the buffer length if a has been detected and the packet is valid, // 0 if hasn't yet been detected and -1 if an invalid crc has been sent int processPipInput(uint16_t *retCrc) { uint8_t pipChar; uint16_t newCrc; while (Serial1.available()) // Got any input? { if ((pipChar = Serial1.read()) != 0x0d) // Read the byte { pipInputBuf[pipInputPointer++] = pipChar; // Not a } else { // Got a , calculate the crc newCrc = cal_crc_half(pipInputBuf, pipInputPointer - 2); if (newCrc == ((((pipInputBuf[pipInputPointer - 2]) << 8) & 0xff00) | (pipInputBuf[pipInputPointer - 1] & 0xff))) // Good crc { int i = pipInputPointer - 2; pipInputBuf[i] = 0; // Terminate the string in the input buffer, overwriting the crc - so it can easily be printed out pipInputPointer = 0; // Zero the pointer ready for the next packet *retCrc = newCrc; // Return the buffer CRC return(i); // Return length of buffer } else { pipInputBuf[pipInputPointer + 1] = 0; // Terminate the string for display...keep the crc in place for checking pipInputPointer = 0; return (-1); // Indicate bad crc } } } return (0); // packet not yet finished } // Send a packet to the pip4048 void pipSend(uint8_t txArray[], int length) { int crc = cal_crc_half(txArray, length); Serial1.write(txArray, length); Serial1.write((crc >> 8) & 0xff); Serial1.write(crc & 0xff); Serial1.write(0x0d); } static int mqttConnects = 0; bool reconnect() { // Loop until we're reconnected if (!client.connected()) { mySerial.print("Attempting MQTT connection..."); // Attempt to connect if (client.connect(mqttID, mqttUser, mqttPassword)) { char s[100]; ++mqttConnects; mySerial.println("connected"); // Resubscibe client.subscribe(catString(inTopic, "/#")); // Once connected, publish an announcement... sprintf(s, "PIP4048 Monitor Connected(%d) - IP address%d.%d.%d.%d", mqttConnects, Ethernet.localIP()[0], Ethernet.localIP()[1], Ethernet.localIP()[2], Ethernet.localIP()[3]); client.publish(catString(outTopic, "/login"), s); // Request a time update sprintf(s,"{\"id\":\"%s\",\"desc\":\"PIP4048 Monitor\",\"attribute\":\"NONE\",\"fail\":\"0\"}", mqttID); client.publish("esplogon", s); return (1); // Indicate connected } } return (0); // Indicate not connected } uint16_t cal_crc_half(uint8_t *pin, uint8_t len) { uint16_t crc; uint8_t da; uint8_t *ptr; uint8_t bCRCHign; uint8_t bCRCLow; uint16_t crc_ta[16]= { 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef }; ptr=pin; crc=0; while(len--!=0) { da=((uint8_t)(crc>>8))>>4; crc<<=4; crc^=crc_ta[da^(*ptr>>4)]; da=((uint8_t)(crc>>8))>>4; crc<<=4; crc^=crc_ta[da^(*ptr&0x0f)]; ptr++; } bCRCLow = crc; bCRCHign= (uint8_t)(crc>>8); if(bCRCLow==0x28||bCRCLow==0x0d||bCRCLow==0x0a) { bCRCLow++; } if(bCRCHign==0x28||bCRCHign==0x0d||bCRCHign==0x0a) { bCRCHign++; } crc = ((uint8_t)bCRCHign)<<8; crc += bCRCLow; return(crc); } // Concatenate 2 strings - max result is 256 bytes char * catString (char *s1, char *s2) { static char result[256]; sprintf(result, "%s%s", s1, s2); return (result); } // Read the external ADC - MCP3208 int read_adc(int whichAdc, int channel) { int adcvalue = 0; int b1 = 0, b2 = 0; int sign = 0; int selPin[] = {SELPIN1, SELPIN2, SELPIN3, SELPIN4}; byte commandbits = B11000000; //command bits - start, mode, chn (3), dont care (3) // command bits for MCP3304 // 0000 = diff, ch0 = in+, ch1 = in- // 0010 = diff, ch2 = in+, ch3 = in- // 0100 = diff, ch4 = in+, ch5 = in- digitalWrite (selPin[whichAdc], LOW); // Select adc //send start bit and bit to specify single or differential mode (single mode chosen here) //allow channel selection commandbits|=((channel-1)<<3); // setup bits to be written for (int i=7; i>=3; i--) { digitalWrite(ADCMOSI,commandbits&1<=0; i--) { adcvalue+=digitalRead(ADCMISO)< 115 - pollIntv/8); // Discard the rest of the packet udp.flush(); return time - 2208988800ul; // convert NTP time to Unix time } */ // Float to ASCII conversion char *ftoa(char *a, double f, int precision) { long p[] = {0,10,100,1000,10000,100000,1000000,10000000,100000000}; char *ret = a; long heiltal = (long)f; itoa(heiltal, a, 10); while (*a != '\0') a++; *a++ = '.'; long desimal = abs((long)((f - heiltal) * p[precision])); itoa(desimal, a, 10); return ret; }