a sensor hub for monitoring home environment

Dependencies:   HTS221 LIS3MDL LPS22HB LSM303AGR LSM6DSL VL53L0X picojson

Fork of HelloWorld_ST_Sensors by ST

main.cpp

Committer:
jaafaryn
Date:
2018-01-29
Revision:
14:0c8967e20f93
Parent:
13:f8e84b12665c

File content as of revision 14:0c8967e20f93:

/*------------------------------------------------------------------------------
                Home Environment Monitoring with STM32 Discovery IOT Node
                                Disco_HomeEnv
Features:
  - Monitor temperature, relative humidity and air pressure
  - Connects to a local TSDB server (over http) to log this data
  - Display the data on a Nextion smart display with 30s autosleep and proximity
    or touch detection wake up
Next:
  - add RTC sync and time display --> done
  - add light sensor to monitor illuminance --> low priority
  - improve reading by either: reduce power on time of WiFi module (batch logging)
    or by compensating for board own temperature --> WiP
  - use a local toolchain to compile mbed projects (with trueStudio) --> WiP 
  - add option to choose location from a list and update location tag
  - incrementally convert the project to electric energy monitoring:
        - read 2x simultaneous adc channels using DMA
        - save daily power readings to QSP mem for batch logging and recovey?
        - add screen to display current power drawing, daily and monthly cumulatives
------------------------------------------------------------------------------*/

#include "main.h"

/* ---------- SETUP ------------- */
void setup()
{
    printf("\n***************************************************************\n");
    printf("***      Home Env on STM32 IoT Discovery                    ***\n");
    printf("***************************************************************\n");

    printf("> Initializing sensors and WiFi... \n");
    
    int range_status;

    /* Init all sensors with default params */
    hum_temp.init(NULL);
    press_temp.init(NULL);
    magnetometer.init(NULL);
    acc_gyro.init(NULL);
    range_status = range.init_sensor(VL53L0X_DEFAULT_ADDRESS);

    /* enable sensors */
    hum_temp.enable();
    press_temp.enable();
    // acc_gyro.enable_x();
    // acc_gyro.enable_g();
    
    uint8_t id;
    hum_temp.read_id(&id);
    printf("HTS221  humidity & temperature id  = 0x%X\r\n", id);
    press_temp.read_id(&id);
    printf("LPS22HB pressure & temperature id  = 0x%X\r\n", id);
    magnetometer.read_id(&id);
    printf("LIS3MDL magnetometer id            = 0x%X\r\n", id);
    acc_gyro.read_id(&id);
    printf("LSM6DSL accelerometer&gyroscope id = 0x%X\r\n", id);
    printf("VL53L0x status                     = %d \r\n", range_status);

    sense_enabled = true;

    /* Setup display */
    WAKE_UP_DISPLAY;
    WIFI_ICON_OFF;
    UPLOAD_ICON_OFF;
    NO_WAKEUP_ON_SERIALIN;// no wakeup on serial in
    SLEEP_ON_NOTOUCH_30S;// sleep after 30sec no touch
    WAKEUP_ON_TOUCH;// wake up on touch


    /*Initialize WIFI module */
    WiFi_on = connectWiFi();
    if (WiFi_on) {
        led3 = 1;
        //WiFi_led_ticker.attach(&toggle_led3_cb, 0.25);
        rtc_synced = sync_rtc();
        // enter power save mode
        /*if (WIFI_SetPowerSaveMode(1, (UPLOAD_PERIOD_S - 1)*1000) == WIFI_STATUS_OK) {
            printf("> es-wifi entered power save mode\n");
        } else {
            printf("> ERROR: es-wifi did not enter power save mode\n");
        }*/
    }

    // start event tickers
    getMeasurementsTicker.attach(&time_to_sense_cb, SENSE_PERIOD_S);
    sendMeasurementsTicker.attach(&time_to_send_cb, UPLOAD_PERIOD_S);
    checkProximityTicker.attach(&time_to_check_distance_cb, CHECK_PROXIMITY_PERIOD_S);

} // Setup


/* ---------- LOOP ------------- */
void loop() {
    time_t rawtime;
    tm *pTime;
    
    if (take_measurements) {
        // Environment data
        hum_temp.get_temperature(&tempC_val);
        tempF_val = tempC_val*(9.0/5.0) + 32;
        hum_temp.get_humidity(&RH_val);

        press_temp.get_pressure(&Patm_val);
        press_temp.get_temperature(&tempC_val2);
        

        // Inertial data
        /*
        magnetometer.get_m_axes(axes);
        printf("LIS3MDL [mag/mgauss]:    %6ld, %6ld, %6ld\r\n", axes[0], axes[1], axes[2]);

        acc_gyro.get_x_axes(axes);
        printf("LSM6DSL [acc/mg]:        %6ld, %6ld, %6ld\r\n", axes[0], axes[1], axes[2]);

        acc_gyro.get_g_axes(axes);
        printf("LSM6DSL [gyro/mdps]:     %6ld, %6ld, %6ld\r\n", axes[0], axes[1], axes[2]);
        */

        // print results to terminal
        /*
        printf("HTS221:  temp= %.2f C, hum= %.2f%%\r\n", tempC_val, RH_val);
        printf("         temp= %.2f F\n", tempF_val);
        printf("LPS22HB: patm= %.2f mbar, temp= %.2f C\r\n", Patm_val, tempC_val2);
        printf("VL53L0X [mm] = %6ld\r\n", distance_val);
        */
        // refresh screen with updated measurements
        nextion.printf("valC.txt=\"%.1f\"\xff\xff\xff", tempC_val);
        nextion.printf("valF.txt=\"%.1f\"\xff\xff\xff", tempF_val);
        nextion.printf("valRH.txt=\"%.1f\"\xff\xff\xff", RH_val);
        nextion.printf("valAtm.val=%.0f\xff\xff\xff", Patm_val);
        nextion.printf("valRange.val=%d\xff\xff\xff", distance_val);

        take_measurements = false;
    }

    if (send_measurements) {

        printf("HTS221:  temp= %.2f C, hum= %.2f%%\r\n", tempC_val, RH_val);

        // body of the request
        char request_body[256];
        char *InfluxServerUrl = INFLUX_SERVER_URL;
        sprintf(request_body, "disco_iot,loc=%s temperature=%.2f,humidity=%.1f,pressure=%.1f \n","playroom", tempC_val, RH_val, Patm_val);

        // build header of the request
        sprintf((char *)http_request, "POST %s HTTP/1.1\r\nHost: %s \r\n", INFLUX_WRITE_EP, InfluxServerUrl);
        strcat((char *)http_request, "Accept: */*\r\n");
        strcat((char *)http_request, "User-agent: ES-WIFI TcpClient\r\n");
        strcat((char *)http_request, "Connection: Close\r\n"); //"Connection: Keep-Alive\r\n"
        char buffer[64];
        sprintf(buffer, "Content-Length: %d \r\n\r\n", strlen(request_body));
        strcat((char *)http_request, buffer);

        // append body to the header of the request
        strcat((char *)http_request, request_body);
        reqLen = strlen((char *)http_request);

        // Establish a connection to DB server
        uint8_t socketid = 1;
        if (checkWiFi()) {
            led3 = 1;
            if (connectToServer(INFLUX_SERVER_URL, INFLUX_SERVER_PORT, socketid)) {
                ledhttp =1;
                // submit POST request
                // printf("> Sending a POST request with length=%d including a body length=%d\n", reqLen, strlen(request_body));
                // printf((char *)http_request);
                uint16_t dataLen;
                if (WIFI_SendData(socketid, http_request, reqLen, &dataLen, WIFI_WRITE_TIMEOUT) != WIFI_STATUS_OK) {
                    printf("> ERROR: Could not send request to %s", InfluxServerUrl);
                    UPLOAD_ICON_OFF;
                } else {
                    request_sent++;
                    UPLOAD_ICON_ON;
                }
                // get server response
                // memset(&http_resp[0], 0, sizeof(http_resp));
                WIFI_ReceiveData(socketid, http_resp, sizeof(http_resp), &dataLen, WIFI_READ_TIMEOUT);
                if(dataLen > 0) {
                    /*
                    printf("> Server response:\n");
                    printf((char *)http_resp);
                    printf("\n> Response length: %d \n", dataLen);
                    */
                    memset(&buffer[0], 0, sizeof(buffer));
                    // get the first line of the response
                    strcpy(buffer, strtok((char *)http_resp, "\r\n"));
                    // extract the response code
                    int response_code = 0;
                    sscanf(buffer, "HTTP/1.1 %d", &response_code);
                    // printf("> Response code: %d \n", response_code);
                    /* common response codes from InfluxDB API:
                    HTTP/1.1 204 No Content
                    HTTP/1.1 400 Bad Request
                    HTTP/1.1 404 Not Found
                    HTTP/1.1 500 Internal Server Error
                    */
                    if (response_code == 204) request_acked++;
                } else {
                    UPLOAD_ICON_OFF;
                    printf("> Error: No response from the server %s.\n", InfluxServerUrl);
                }
                printf("> Requests sent: %d, ack'ed: %d\n", request_sent, request_acked);
                ledhttp = 0;
                WIFI_CloseClientConnection(socketid);
            } else {
                printf("> ERROR: Could not open connection to %s \n", InfluxServerUrl);
            }
            
            // enter power save mode for UPLOAD_PERIOD_S - 1 sec, beacon interval = 100ms
            if (WIFI_SetPowerSaveMode(1, (UPLOAD_PERIOD_S - 1)*1000) == WIFI_STATUS_OK) {
               printf("> es-wifi entered power save mode\n");
            } else {
                printf("> ERROR: es-wifi did not enter power save mode\n");
            }
            
        } else {
            printf("> ERROR: Could not connect to WiFi \n");
            led3 = 0;
        }
        
        if (rtc_synced) {
            time(&rawtime);
            pTime = localtime(&rawtime);
            // printf ("Current local time and date: %s", asctime(pTime));
            // printf("> %d:%d:%d\n", pTime->tm_hour, pTime->tm_min, pTime->tm_sec);
        } else {
            rtc_synced = sync_rtc();
        }   
        
        send_measurements = false;
    }

    if (check_proximity) {
        // make sure display is awake when somebody get close to the screen
        // Proximity value
        uint32_t dist_val = 0;
        int status = range.get_distance(&dist_val);
        if (status == VL53L0X_ERROR_NONE) {
            if (dist_val < 500) {
                nextion.printf("sleep=0\xff\xff\xff");// send wake up command
            }
            distance_val = dist_val;
        } else {
            distance_val = 9999;
        }
        
        // since this is a 1-sec period ticker, we do clock display refresh in here too
        // get a formatted local time to be displayed
        if (rtc_synced) {
            time(&rawtime);
            pTime = localtime(&rawtime);
            nextion.printf("hour.txt=\"%02d\"\xff\xff\xff", pTime->tm_hour);
            nextion.printf("minute.txt=\"%02d\"\xff\xff\xff", pTime->tm_min);
            nextion.printf("vis sec,1\xff\xff\xff");
            //nextion.printf("vis sec,%d\xff\xff\xff", pTime->tm_sec % 2 == 0 ? 1 : 0);
        }
        
        check_proximity = false;
    }       
} // Loop


int main() {
    setup();
    while(1) {
        loop();
    }
}


/*                                         */
/* Interrupts servicing callback functions */
/*                                         */

// ISR for flashing WiFi LED
/*
void toggle_led3_cb()
{
    led3 = !led3;
}*/

// ISR to enable taking measurements (and refresh display)
void time_to_sense_cb(void)
{
    if (sense_enabled) {
        take_measurements = true;
    }
}

// ISR to enable handling sending data
void time_to_send_cb(void)
{
    if (sense_enabled) {
        send_measurements = true;
    }
}

// ISR to enable checking distance
void time_to_check_distance_cb(void)
{
    check_proximity = true;
}

/**
  * @brief  Connect to the WiFi network with credentials in mbed_app.json
  * @param  None
  * @retval True for WiFi connected, False if there was an error
  */
bool connectWiFi(void)
{
    uint8_t  IP_Addr[4];
    uint8_t  MAC_Addr[6];

    if(WIFI_Init() ==  WIFI_STATUS_OK) {
        // printf("> WiFi module initialized.\n");
        char fwVer[32];
        if(WIFI_GetModuleFwRevision(fwVer) == WIFI_STATUS_OK) {
          printf("> Firmware version: %s\n", fwVer);
        }
        /*
        if(WIFI_GetMAC_Address(MAC_Addr) == WIFI_STATUS_OK) {
            printf("> WiFi module MAC Address : %X:%X:%X:%X:%X:%X\n",
                   MAC_Addr[0],
                   MAC_Addr[1],
                   MAC_Addr[2],
                   MAC_Addr[3],
                   MAC_Addr[4],
                   MAC_Addr[5]);
        } else {
            printf("> ERROR : CANNOT get MAC address\n");
        }*/
        
        if( WIFI_Connect(MBED_CONF_APP_WIFI_SSID, MBED_CONF_APP_WIFI_PASSWORD, WIFI_ECN_WPA2_PSK) == WIFI_STATUS_OK) {
            if(WIFI_GetIP_Address(IP_Addr) == WIFI_STATUS_OK) {
                printf("> IP Address : %d.%d.%d.%d\n",
                       IP_Addr[0],
                       IP_Addr[1],
                       IP_Addr[2],
                       IP_Addr[3]);
                WIFI_ICON_ON;
                UPLOAD_ICON_OFF; // reset db sync status if (re-)connect happened
                return true;
            } else {
                printf("> ERROR : es-wifi module CANNOT get IP address\n");
            }
        } else {
            printf("> ERROR : es-wifi module NOT connected\n");
        }
    } else {
        printf("> ERROR : WIFI Module cannot be initialized.\n");
    }
    WIFI_ICON_OFF;
    UPLOAD_ICON_OFF;
    return false;
}


/**
  * @brief  check WiFi connection status, retry once if it's offline
  * @param  None
  * @retval True for WiFi (re)connected, False if reconnection failed
  */

bool checkWiFi(void)
{
    uint8_t  ip_addr[4];
    if(WIFI_GetIP_Address(ip_addr) == WIFI_STATUS_OK) {
        WIFI_ICON_ON;
        return true;
    } else {
        return connectWiFi();
    }
}

/**
  * @brief  create a TCP session on a server
  * @param  serverUrl : TCP server URL
  * @param  serverPort : port TCP server is listening on
  * @retval true if connection established
  */
bool connectToServer(char *serverUrl, uint16_t serverPort, uint8_t socket_id)
{
    uint8_t  serverIP[] = {0, 0, 0, 0};
    uint16_t trials = CONNECTION_TRIAL_MAX;

    while (trials--) {
        // get the server IP through a (local) DNS resolve
        if (WIFI_GetHostAddress(serverUrl, serverIP) == WIFI_STATUS_OK) {
            
            printf("> %s resolved to: %d.%d.%d.%d\n", serverUrl,
                   serverIP[0],
                   serverIP[1],
                   serverIP[2],
                   serverIP[3]);
            // establish TCP connection to server
            if( WIFI_OpenClientConnection(socket_id, WIFI_TCP_PROTOCOL, "TCP_Client", serverIP, serverPort, 0) == WIFI_STATUS_OK) {
                printf("> Connected to %s .\n", serverUrl);
                return true;
            }
        } else {
            printf("> ERROR : Cannot resolve URL: %s\n", serverUrl);
            printf("> Trials left: %d\n", trials);
        }
    }
    
    if(!trials) {
        printf("> ERROR : Cannot establish connection\n");
    }
    return false;
}


/**
  * @brief  get current unix timestamp and use it to sync rtc
  * @retval True if successful, False if failed
  */
bool sync_rtc() {
    // 1- establish connection to time server (socket 2)
    uint8_t socketid = 2;
    uint16_t dataLen;
    if (checkWiFi()) {
        if (connectToServer(TIME_SERVER_URL, TIME_SERVER_PORT, socketid)) {
            sprintf((char *)http_request, "GET %s HTTP/1.1\r\nHost: %s \r\n", TIME_SERVER_EP, TIME_SERVER_URL);
            strcat((char *)http_request, "Connection: Close\r\n\r\n");
            reqLen = strlen((char *)http_request);
            if (WIFI_SendData(socketid, http_request, reqLen, &dataLen, WIFI_WRITE_TIMEOUT) != WIFI_STATUS_OK) {
                printf("> ERROR: Could not send request to %s\n", TIME_SERVER_URL);
            } else {
                printf("> TS: Sent %d bytes\n", dataLen);
            }
            
        // 2- get server response and parse the json
            WIFI_ReceiveData(socketid, http_resp, sizeof(http_resp), &dataLen, WIFI_READ_TIMEOUT);
            if(dataLen > 0) {
                printf("> TS: Received %d bytes\n", dataLen);
                // extract the body (json payload) from the response
                char *resp_body = strstr((char *)http_resp, "\r\n\r\n"); // get the body + "OK"
                resp_body = strtok(resp_body, "\r\n"); // get rid of 'OK'
                picojson::value json_val;
                string err = picojson::parse(json_val, resp_body,  resp_body + strlen(resp_body));
                if(err.empty()) {
                    
                    // 3- convert timestamp  to local time and sync rtc            
                    uint32_t timestamp_val = (unsigned)json_val.get("timestamp").get<double>();
                    printf("> utc_ts = %u \n",timestamp_val); // print utc
                    timestamp_val -= 3600*5; // EST: utc - 5h
                    set_time(timestamp_val);
                    WIFI_CloseClientConnection(socketid); // close socket on success
                    return true;
                } else { 
                    printf("> ERROR: TS json parsing\n");
                }  
            } else {
                printf("> ERROR: Did not receive data from TS server\n");
            }
            WIFI_CloseClientConnection(socketid); // close socket on failure to retrieve time
        } else {
            printf("> ERROR: Cannot establish connection to %s \n", TIME_SERVER_URL);
        }
    }
    return false;
}