Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: EthernetInterfacePlusHostname NTPClient Onewire RdWebServer SDFileSystem-RTOS mbed-rtos mbed-src
main.cpp
- Committer:
- Bobty
- Date:
- 2015-10-16
- Revision:
- 22:14b4060dd027
- Parent:
- 21:ccf053bab795
File content as of revision 22:14b4060dd027:
// 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
}
}