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-02-22
Revision:
10:72eb217def1f
Parent:
9:0e103c2f869a
Child:
11:30182b9aa833

File content as of revision 10:72eb217def1f:

#include "mbed.h"
#include "EthernetInterface.h"
#include "NTPClient.h"
#include "RdWebServer.h"
#include "GasUseCounter.h"
#include "Thermometers.h"
#include "VoltAlerter.h"
#include <stdarg.h>

// Web and UDB ports 
const int WEBPORT = 80; // Port for web server
const int BROADCAST_PORT = 42853; // Arbitrarily chosen port number

// Ticker collects data
//Ticker ticker;
//const int TICK_MS = 50;
const int LOOP_DELAY_IN_MS = 250;

// Debugging and status
RawSerial pc(USBTX, USBRX);
DigitalOut led1(LED1); //ticking (flashes)
DigitalOut led2(LED2); //
DigitalOut led3(LED3); //socket connecting status
DigitalOut led4(LED4); //server status

// Web server
EthernetInterface eth;
UDPSocket sendUDPSocket;
Endpoint broadcastEndpoint;

// Network Time Protocol (NTP)
NTPClient ntp;
const int NTP_REFRESH_INTERVAL_HOURS = 1;

// File system for SD card
SDFileSystem sd(p5, p6, p7, p8, "sd");

// Gas use counter
DigitalIn gasPulsePin(p21);
const char* gasPulseFileName = "/sd/curPulse.txt";
const char* logFilename = "/sd/log.txt";
GasUseCounter gasUseCounter(gasPulseFileName, gasPulsePin, pc);

// Thermometers - DS18B20 OneWire Thermometer connections
const PinName tempSensorPins[] = { p22 };
Thermometers thermometers(sizeof(tempSensorPins)/sizeof(PinName), tempSensorPins, LOOP_DELAY_IN_MS);

// Voltage Sensors / Alerters
const int NUM_VOLT_ALERTERS = 3;
VoltAlerter voltAlerter1(p23);
VoltAlerter voltAlerter2(p24);
VoltAlerter voltAlerter3(p25);

// Broadcast message
const char broadcastMsgPrefix[] = "{\"e\":[";
const char broadcastMsgGasFormat[] = "{\"n\":\"gasCount\",\"v\":%d},{\"n\":\"gasPulseRateMs\",\"v\":%d,\"u\":\"ms\"}";
const char broadcastTemperatureFormat[] = "{\"n\":\"temp_%s\",\"v\":%0.1f,\"u\":\"degC\"}";
const char broadcastVoltAlerterFormat[] = "{\"n\":\"pump_%d\",\"v\":%d}";
const char broadcastMsgSuffix[] = "],\"bt\":%d}";

// Format message
const int broadcastMsgLen = sizeof(broadcastMsgPrefix) + 
            sizeof(broadcastMsgGasFormat) + 
            (sizeof(broadcastTemperatureFormat)*Thermometers::MAX_THERMOMETERS) + 
            (sizeof(broadcastVoltAlerterFormat)*NUM_VOLT_ALERTERS) + 
            sizeof(broadcastMsgSuffix) + 
            60;
char broadcastMsgBuffer[broadcastMsgLen];
    
// Utility function to log data
void LogData(const char* format, ...)
{
    FILE* fp = fopen(logFilename, "a");
    if (fp == NULL)
    {
        pc.printf ("Log ... Filename %s not found\r\n", logFilename);
    }
    else
    {
        va_list argptr;
        va_start(argptr, format);
        vfprintf(fp, format, argptr);
        va_end(argptr);
        fclose(fp);
    }
}

// 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);
    
    // 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);
    }
    
    // Data format of the 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
    // }

    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, tempValues[tempIdx].tempInCentigrade);
        strcpy(broadcastMsgBuffer+strlen(broadcastMsgBuffer), ",");
    }
    sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastVoltAlerterFormat, 1, voltAlerter1.GetState());
    strcpy(broadcastMsgBuffer+strlen(broadcastMsgBuffer), ",");
    sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastVoltAlerterFormat, 2, voltAlerter2.GetState());
    strcpy(broadcastMsgBuffer+strlen(broadcastMsgBuffer), ",");
    sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastVoltAlerterFormat, 3, voltAlerter3.GetState());
    sprintf(broadcastMsgBuffer+strlen(broadcastMsgBuffer), broadcastMsgSuffix, timeNow);
        
    // Send
    int bytesToSend = strlen(broadcastMsgBuffer);
    int rslt = sendUDPSocket.sendTo(broadcastEndpoint, broadcastMsgBuffer, bytesToSend);
    if (rslt == bytesToSend)
    {
        pc.printf("Broadcast (len %d) Sent ok %s\r\n", bytesToSend, broadcastMsgBuffer);
    }
    else if (rslt == -1)
    {
        pc.printf("Broadcast Failed to send %s\r\n", broadcastMsgBuffer);
    }
    else
    {
        pc.printf("Broadcast Didn't send all of %s\r\n", broadcastMsgBuffer);
    }
    
    // Log
    char timeBuf[32];
    time_t seconds = time(NULL);
    strftime(timeBuf, 32, "%Y-%m-%d %H:%M:%S", localtime(&seconds));
    LogData("%s\t%d\t%d ms\n", timeBuf, gasUseCounter.GetCount(), gasUseCounter.GetPulseRateMs());
    
    led3 = false;
}

// Ticker's tick function
//void TickFunction()
//{
//}

char* getGasUseCallback(int method, char* cmdStr, char* argStr)
{
    char* pResp = gasUseCounter.getGasUseCallback(cmdStr, argStr);
    pc.printf("Returning gas use %s\r\n", pResp);
    return pResp;
}

char* setGasUseCallback(int method, char* cmdStr, char* argStr)
{
    pc.printf("Setting gas use count %s\r\n", 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";
}

void http_thread(void const* arg)
{
    char* baseWebFolder = "/sd/";

    RdWebServer webServer;
    webServer.addCommand("", RdWebServerCmdDef::CMD_SDORUSBFILE, NULL, "index.htm", false);
    webServer.addCommand("gear-gr.png", RdWebServerCmdDef::CMD_SDORUSBFILE, NULL, NULL, true);
    webServer.addCommand("getgascount", RdWebServerCmdDef::CMD_CALLBACK, &getGasUseCallback);
    webServer.addCommand("setgascount", RdWebServerCmdDef::CMD_CALLBACK, &setGasUseCallback);

    webServer.init(WEBPORT, &led4, baseWebFolder);

    webServer.run();
}

void ntp_thread(void const* arg)
{
    while (1)
    {
        pc.printf("Trying to update time...\r\n");
        if (ntp.setTime("0.pool.ntp.org") == 0)
        {
          printf("Set time successfully\r\n");
          time_t ctTime;
          ctTime = time(NULL);
          printf("Time is set to (UTC): %s\r\n", ctime(&ctTime));
        }
        else
        {
          printf("Cannot set from NTP\r\n");
        }
        
        // 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);
                }
                pc.printf("%d mins to next NTP time refresh\r\n", (NTP_REFRESH_INTERVAL_HOURS-k-1)*60 + (59-i));
            }
        }
    }
}

int main()
{
    pc.baud(115200);
    pc.printf("Gas Monitor V2 - Rob Dobson 2014\r\n");

    printf("Broadcast Msg Len %d\r\n", broadcastMsgLen);

//    ticker.attach(&TickFunction,TICK_MS / 1000.0);

    // Initialise thermometers
    thermometers.Init();
    
    // Get the current count from the SD Card
    gasUseCounter.Init();
    
    // setup ethernet interface
    eth.init(); //Use DHCP
    eth.connect();
    
    pc.printf("IP Address is %s\r\n", eth.getIPAddress());
    
    // NTP Time setter
    Thread ntpTimeSetter(&ntp_thread);
    
    // Web Server
    Thread httpServer(&http_thread, NULL, osPriorityNormal, (DEFAULT_STACK_SIZE * 3));
    
    // Time of last broadcast
    time_t timeOfLastBroadcast = time(NULL);
    const int TIME_BETWEEN_BROADCASTS_IN_SECS = 60;
    while(true)
    {
        osDelay(LOOP_DELAY_IN_MS);
        led1 = !led1;

        // 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();
        led2 = voltAlerter1.GetState();
    }
}