Monitor for central heating system (e.g. 2zones+hw) Supports up to 15 temp probes (DS18B20/DS18S20) 3 valve monitors Gas pulse meter recording Use stand-alone or with nodeEnergyServer See http://robdobson.com/2015/09/central-heating-monitor
Dependencies: EthernetInterfacePlusHostname NTPClient Onewire RdWebServer SDFileSystem-RTOS mbed-rtos mbed-src
main.cpp
- Committer:
- Bobty
- Date:
- 2015-10-16
- Revision:
- 23:fd5a5a9f30bc
- Parent:
- 21:ccf053bab795
File content as of revision 23:fd5a5a9f30bc:
// Gas usage monitor // Counts pulses from a gas meter // Monitors temperature sensors // Monitors valve/pump activity // Web interface and UDP broadcast // Rob Dobson 2015 #include "mbed.h" #include "EthernetInterface.h" #include "NTPClient.h" #include "RdWebServer.h" #include "GasUseCounter.h" #include "Thermometers.h" #include "VoltAlerter.h" #include "Watchdog.h" #include "Logger.h" // System name (used for hostname) char systemName[20] = "RdGasUseMonitor"; // Web and UDB ports const int WEBPORT = 80; // Port for web server const int BROADCAST_PORT = 42853; // Arbitrarily chosen port number // Main loop delay between data collection passes const int LOOP_DELAY_IN_MS = 250; // Debugging and status RawSerial pc(USBTX, USBRX); DigitalOut led1(LED1); //ticking (flashes) DigitalOut led2(LED2); //state of the 1st voltage alerter DigitalOut led3(LED3); //socket connecting status DigitalOut led4(LED4); //server status // Web server UDPSocket sendUDPSocket; Endpoint broadcastEndpoint; // Network Time Protocol (NTP) NTPClient ntp; const int NTP_REFRESH_INTERVAL_HOURS = 1; // Mutex for SD card access Mutex sdCardMutex; // File system for SD card SDFileSystem sd(p5, p6, p7, p8, "sd"); // Base folder for web file system char* baseWebFolder = "/sd/"; // Log file names const char* gasPulseFileName1 = "/sd/curPulse.txt"; const char* gasPulseFileName2 = "/sd/curPulse2.txt"; const char* eventLogFileName = "/sd/log.txt"; const char* dataLogFileBase = "/sd/"; // Logger Logger logger(eventLogFileName, dataLogFileBase, sdCardMutex); // Gas use counter DigitalIn gasPulsePin(p21); GasUseCounter gasUseCounter(gasPulseFileName1, gasPulseFileName2, gasPulsePin, logger, sdCardMutex); // Thermometers - DS18B20 OneWire Thermometer connections const PinName tempSensorPins[] = { p22 }; Thermometers thermometers(sizeof(tempSensorPins)/sizeof(PinName), tempSensorPins, LOOP_DELAY_IN_MS, logger); // Voltage Sensors / Alerters const int NUM_VOLT_ALERTERS = 3; VoltAlerter voltAlerter1(p23); VoltAlerter voltAlerter2(p24); VoltAlerter voltAlerter3(p25); // Watchdog Watchdog watchdog; // Broadcast message format // Data format of the broadcast message - senml - https://tools.ietf.org/html/draft-jennings-senml-08 // { // "e": [ // {"n":"gasCount","v":%d}, // {"n":"gasPulseRateMs","v":%d,"u":"ms"}, // {"n":"temp_%s","v":%0.1f,"u":"degC"}, // ... // {"n":"pump_%d","v":%d}, // ... // ], // "bt": %d // } const char broadcastMsgPrefix[] = "{\"e\":["; const char broadcastMsgGasFormat[] = "{\"n\":\"gasCount\",\"v\":%d,\"u\":\"count\"},{\"n\":\"gasPulseRateMs\",\"v\":%d,\"u\":\"ms\"}"; const char broadcastTemperatureFormat[] = "{\"n\":\"temp_%s_%s\",\"v\":%0.1f,\"u\":\"degC\"}"; const char broadcastVoltAlerterFormat[] = "{\"n\":\"pump_%d_%s\",\"bv\":%d}"; const char broadcastMsgSuffix[] = "],\"bt\":%d}"; // Broadcast message length and buffer const int MAX_DEVICE_NAME_LEN = 20; const int broadcastMsgLen = sizeof(broadcastMsgPrefix) + sizeof(broadcastMsgGasFormat) + ((sizeof(broadcastTemperatureFormat)+MAX_DEVICE_NAME_LEN)*Thermometers::MAX_THERMOMETERS) + ((sizeof(broadcastVoltAlerterFormat)+MAX_DEVICE_NAME_LEN)*NUM_VOLT_ALERTERS) + sizeof(broadcastMsgSuffix) + 60; char broadcastMsgBuffer[broadcastMsgLen]; // Handling of SD card file delete and file upload commands const int MAX_FNAME_LENGTH = 127; char fileNameForWrite[MAX_FNAME_LENGTH+1] = ""; int fileRemainingForWrite = 0; // Thermometer and pump names char thermometerAddrs[][MAX_DEVICE_NAME_LEN] = { "28b1b1e0050000d0", "289dd7c705000060", "28b3b0c60500000a" }; char thermometerNames[][MAX_DEVICE_NAME_LEN] = { "HWCylinder", "Inflow2", "Outflow2" }; int numNamedThermometers = 3; char voltAlerterNames[][MAX_DEVICE_NAME_LEN] = { "PumpUpper", "PumpLower", "PumpHW" }; // Get names of thermometers char* getThermometerName(char* thermAddr) { for (int i = 0; i < numNamedThermometers; i++) { if (strcmp(thermometerAddrs[i], thermAddr) == 0) return thermometerNames[i]; } return "Unknown"; } // Get names of volt alerters char* getVoltAlerterName(int idx) { if (idx >= 0 && idx < NUM_VOLT_ALERTERS) { return voltAlerterNames[idx]; } return "Unknown"; } // Format broadcast message void GenBroadcastMessage() { // Get temperature values TemperatureValue tempValues[Thermometers::MAX_THERMOMETERS]; int numTempValues = thermometers.GetTemperatureValues(Thermometers::MAX_THERMOMETERS, tempValues, 100); // for (int tempIdx = 0; tempIdx < numTempValues; tempIdx++) // { // printf("Temp: %.1f, Addr: %s, Time: %d\r\n", tempValues[tempIdx].tempInCentigrade, tempValues[tempIdx].address, tempValues[tempIdx].timeStamp); // } // Format the broadcast message time_t timeNow = time(NULL); strcpy(broadcastMsgBuffer, broadcastMsgPrefix); sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastMsgGasFormat, gasUseCounter.GetCount(), gasUseCounter.GetPulseRateMs()); strcpy(broadcastMsgBuffer+strlen(broadcastMsgBuffer), ","); for (int tempIdx = 0; tempIdx < numTempValues; tempIdx++) { sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastTemperatureFormat, tempValues[tempIdx].address, getThermometerName(tempValues[tempIdx].address), tempValues[tempIdx].tempInCentigrade); strcpy(broadcastMsgBuffer+strlen(broadcastMsgBuffer), ","); } sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastVoltAlerterFormat, 1, getVoltAlerterName(0), voltAlerter1.GetState()); strcpy(broadcastMsgBuffer+strlen(broadcastMsgBuffer), ","); sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastVoltAlerterFormat, 2, getVoltAlerterName(1), voltAlerter2.GetState()); strcpy(broadcastMsgBuffer+strlen(broadcastMsgBuffer), ","); sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastVoltAlerterFormat, 3, getVoltAlerterName(2), voltAlerter3.GetState()); sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastMsgSuffix, timeNow); } // Send broadcast message with current data void SendInfoBroadcast() { led3 = true; // Init the sending socket sendUDPSocket.init(); sendUDPSocket.set_broadcasting(); broadcastEndpoint.set_address("255.255.255.255", BROADCAST_PORT); // Format the message GenBroadcastMessage(); // Send int bytesToSend = strlen(broadcastMsgBuffer); int rslt = sendUDPSocket.sendTo(broadcastEndpoint, broadcastMsgBuffer, bytesToSend); if (rslt == bytesToSend) { logger.LogDebug("Broadcast (len %d) Sent ok %s", bytesToSend, broadcastMsgBuffer); } else if (rslt == -1) { logger.LogDebug("Broadcast Failed to send %s", broadcastMsgBuffer); } else { logger.LogDebug("Broadcast Didn't send all of %s", broadcastMsgBuffer); } // Log the data logger.LogData(broadcastMsgBuffer); led3 = false; } char* getCurDataCallback(int method, char* cmdStr, char* argStr, char* msgBuffer, int msgLen, int contentLen, unsigned char* pPayload, int payloadLen, int splitPayloadPos) { // Format message GenBroadcastMessage(); return broadcastMsgBuffer; } char* setGasUseCallback(int method, char* cmdStr, char* argStr, char* msgBuffer, int msgLen, int contentLen, unsigned char* pPayload, int payloadLen, int splitPayloadPos) { logger.LogDebug("Setting gas use count %s", argStr); int newGasUse = 0; char* eqStr = strchr(argStr, '='); if (eqStr == NULL) return "SetGasValue FAILED"; sscanf(eqStr+1, "%d", &newGasUse); gasUseCounter.SetCount(newGasUse); return "SetGasValue OK"; } // Handle SD Card File Delete char* sdCardDelete(int method, char* cmdStr, char* argStr, char* msgBuffer, int msgLen, int contentLen, unsigned char* pPayload, int payloadLen, int splitPayloadPos) { printf("Delete file %s\r\n", argStr); sdCardMutex.lock(); char* baseWebFolder = "/sd/"; char filename[MAX_FNAME_LENGTH+1]; sprintf(filename, "%s%s", baseWebFolder, argStr); printf("Full filename for delete is %s\r\n", filename); bool delOk = (remove(filename) == 0); sdCardMutex.unlock(); if (delOk) return "Delete OK"; return "Delete Failed"; } // Handle SD Card File Upload char* sdCardUpload(int method, char* cmdStr, char* argStr, char* msgBuffer, int msgLen, int contentLen, unsigned char* pPayload, int payloadLen, int splitPayloadPos) { printf("Upload file %s PayloadLen %d ContentLen %d MsgLen %d\r\n", argStr, payloadLen, contentLen, msgLen); if (payloadLen == 0) { printf("Upload file - payload len == 0 - quitting\r\n"); return "OK"; } bool writeSuccess = false; if (splitPayloadPos == 0) { // Get the filename fileNameForWrite[0] = '\0'; fileRemainingForWrite = 0; char* filenameText = " filename=\""; char* pFilename = strstr((char*)pPayload, filenameText); if ((pFilename != NULL) && (pFilename - ((char*)pPayload) < 200)) { pFilename += strlen(filenameText); char* pEndFileName = strstr(pFilename, "\""); if (pEndFileName != NULL) { int fileNameLen = pEndFileName - pFilename; if (fileNameLen + strlen(baseWebFolder) < MAX_FNAME_LENGTH) { strcpy(fileNameForWrite, baseWebFolder); strncpy(fileNameForWrite+strlen(baseWebFolder), pFilename, fileNameLen); fileNameForWrite[fileNameLen+strlen(baseWebFolder)] = '\0'; printf("Upload file - filename %s\r\n", fileNameForWrite); } else { printf("Upload file - filename too long - quitting\r\n"); } } else { printf("Upload file - end of filename not found - quitting\r\n"); } } else { printf("Upload file - filename not found (or not near start of message) - quitting\r\n"); } // Write first chunk to file if (strlen(fileNameForWrite) > 0) { // Find the chunk start char* chunkStartText = "\r\n\r\n"; char* pChunk = strstr((char*)pPayload, chunkStartText); if ((pChunk != NULL) && (pChunk - ((char*)pPayload) < 300)) { pChunk += strlen(chunkStartText); // Find chunk len int headerLen = pChunk - ((char*)pPayload); int chunkLen = payloadLen - headerLen; fileRemainingForWrite = contentLen - headerLen; int numBytesToWrite = chunkLen; if (fileRemainingForWrite > 0) { // Check for end boundary of data if ((fileRemainingForWrite - chunkLen < 100) && (payloadLen > 50)) { char* pEndForm = strstr(((char*)pPayload) + payloadLen - 50, "\r\n------"); if (pEndForm != NULL) { numBytesToWrite = pEndForm - pChunk; printf("Upload file - payload end found writing %d bytes\r\n", numBytesToWrite); } } // Write chunk to file sdCardMutex.lock(); FILE* fp = fopen(fileNameForWrite, "w+"); if (fp == NULL) { printf("Upload file - Failed to open output file\r\n"); } else { fwrite(pChunk, sizeof(char), numBytesToWrite, fp); fclose(fp); printf("Upload file - written %d bytes\r\n", numBytesToWrite); writeSuccess = true; } sdCardMutex.unlock(); // Reduce remaining bytes fileRemainingForWrite -= chunkLen; } else { printf("Upload file - file remaining for write <= 0 - quitting\r\n"); } } else { printf("Upload file - can't find chunk start - quitting\r\n"); } } else { printf("Upload file - filename blank - quitting\r\n"); } } else { if (strlen(fileNameForWrite) != 0) { if (fileRemainingForWrite > 0) { int numBytesToWrite = payloadLen; // Check for end boundary of data if ((fileRemainingForWrite - payloadLen < 100) && (payloadLen > 50)) { char* pEndForm = strstr(((char*)pPayload) + payloadLen - 50, "\r\n------"); if (pEndForm != NULL) { numBytesToWrite = pEndForm - ((char*)pPayload); printf("Upload file - payload end found writing %d bytes\r\n", numBytesToWrite); } } sdCardMutex.lock(); FILE* fp = fopen(fileNameForWrite, "a"); if (fp == NULL) { printf("Failed to open output file\r\n"); } else { fwrite(pPayload, sizeof(char), numBytesToWrite, fp); fclose(fp); writeSuccess = true; printf("Upload file - written %d bytes\r\n", numBytesToWrite); } sdCardMutex.unlock(); // Reduce remaining bytes fileRemainingForWrite -= payloadLen; } else { printf("Upload file - file remaining for write <= 0 - quitting\r\n"); } } else { printf("Upload file - filename blank - quitting\r\n"); } } // Return results if (writeSuccess) return "Write OK"; return "Write Failed"; } // Create, configure and run the web server void http_thread(void const* arg) { RdWebServer webServer(&sdCardMutex); webServer.addCommand("", RdWebServerCmdDef::CMD_SDORUSBFILE, NULL, "index.htm", false); webServer.addCommand("gear-gr.png", RdWebServerCmdDef::CMD_SDORUSBFILE, NULL, NULL, true); webServer.addCommand("listfiles", RdWebServerCmdDef::CMD_SDORUSBFILE, NULL, "/", false); webServer.addCommand("getcurdata", RdWebServerCmdDef::CMD_CALLBACK, &getCurDataCallback); webServer.addCommand("setgascount", RdWebServerCmdDef::CMD_CALLBACK, &setGasUseCallback); webServer.addCommand("delete", RdWebServerCmdDef::CMD_CALLBACK, &sdCardDelete); webServer.addCommand("upload", RdWebServerCmdDef::CMD_CALLBACK, &sdCardUpload); webServer.init(WEBPORT, &led4, baseWebFolder); webServer.run(); } // Network time protocol (NTP) thread to get time from internet void ntp_thread(void const* arg) { while (1) { logger.LogDebug("Trying to update time..."); if (ntp.setTime("0.pool.ntp.org") == NTP_OK) { osDelay(1000); // This delay is simply to try to improve printf output logger.LogDebug("Set time successfully"); time_t ctTime; ctTime = time(NULL); logger.LogDebug("Time is set to (UTC): %s", ctime(&ctTime)); } else { logger.LogDebug("Cannot set from NTP"); } // Refresh time every K hours for (int k = 0; k < NTP_REFRESH_INTERVAL_HOURS; k++) { // 1 hour for (int i = 0; i < 60; i++) { for (int j = 0; j < 60; j++) { osDelay(1000); } logger.LogDebug("%d mins to next NTP time refresh", (NTP_REFRESH_INTERVAL_HOURS-k-1)*60 + (59-i)); } } } } // #define TEST_WATCHDOG 1 #ifdef TEST_WATCHDOG int watchdogTestLoopCount = 0; #endif // Main int main() { pc.baud(115200); logger.SetDebugDest(true, true); logger.LogDebug("\r\n\r\nGas Monitor V2 - Rob Dobson 2015"); // Initialise thermometers thermometers.Init(); // Get the current count from the SD Card gasUseCounter.Init(); // Setup ethernet interface char macAddr[6]; mbed_mac_address(macAddr); logger.LogDebug("Ethernet MAC address: %02x:%02x:%02x:%02x:%02x:%02x", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]); logger.LogDebug("Connecting to ethernet ..."); // Init ethernet EthernetInterface::init(); // Using code described here https://developer.mbed.org/questions/1602/How-to-set-the-TCPIP-stack-s-hostname-pr/ // to setName on the ethernet interface EthernetInterface::setName(systemName); // Connect ethernet EthernetInterface::connect(); logger.LogDebug("IP Address: %s HostName %s", EthernetInterface::getIPAddress(), EthernetInterface::getName()); // NTP Time setter Thread ntpTimeSetter(&ntp_thread); // Web Server Thread httpServer(&http_thread, NULL, osPriorityNormal, (DEFAULT_STACK_SIZE * 3)); // Store reason for restart bool watchdogCausedRestart = watchdog.WatchdogCausedRestart(); bool restartCauseRecorded = false; // Setup the watchdog for reset watchdog.SetTimeoutSecs(300); // Time of last broadcast time_t timeOfLastBroadcast = time(NULL); const int TIME_BETWEEN_BROADCASTS_IN_SECS = 60; while(true) { // Check if we can record the reason for restart (i.e. if time is now set) if (!restartCauseRecorded) { time_t nowTime = time(NULL); if (nowTime > 1000000000) { // Record the reason for restarting in the log file if (watchdogCausedRestart) { logger.LogEvent("Watchdog Restart"); logger.LogDebug("Watchdog Restart"); } else { logger.LogEvent("Normal Restart"); logger.LogDebug("Normal Restart"); } restartCauseRecorded = true; } } // Loop delay osDelay(LOOP_DELAY_IN_MS); // Feed the watchdog and show the flashing LED led1 = !led1; watchdog.Feed(); // Service gas count if (gasUseCounter.Service()) { SendInfoBroadcast(); timeOfLastBroadcast = time(NULL); } // Service thermometers thermometers.Service(); // Check if ready for a broadcast if ((time(NULL) - timeOfLastBroadcast) >= TIME_BETWEEN_BROADCASTS_IN_SECS) { SendInfoBroadcast(); timeOfLastBroadcast = time(NULL); } // Service volt alerters voltAlerter1.Service(); voltAlerter2.Service(); voltAlerter3.Service(); // Set LED2 to the state of the first volt alerter led2 = voltAlerter1.GetState(); #ifdef TEST_WATCHDOG // After about 20 seconds of operation we'll hang to test the watchdog if (watchdogTestLoopCount++ > 80) { // This should cause watchdog to kick in and reset osDelay(20000); } #endif } }