DreamForce 2013 Hands-On Demo

Dependencies:   C12832_lcd EthernetInterface-ansond-patched HTTPClient-thermostat-remotes LM75B MMA7660 SocketIO WebSocketClient-ThermostatDemo mbed-rtos mbed picojson

Fork of ThermostatHandsOn by Doug Anson

Thermostat.cpp

Committer:
ansond
Date:
2013-11-11
Revision:
7:57681d46485d
Parent:
6:510193bc7f26

File content as of revision 7:57681d46485d:

/* Thermostat.cpp */
/* Copyright (C) 2013 mbed.org, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
 
 /*
  * Operation: with the MBED appboard - the following is available:
  *     1. connect/disconnect: push and hold the joystick forward (towards potentiometer) until a connect/disconnect message is seen
  *     2. control messages  
  *         "led1" --> "on" or "off" (led1 is also used as the TX status...)
  *         "led2" --> "on" or "off" (led2 is also used as the RX status...)
  *         "led3" --> "on" or "off"
  *         "led4" --> "on" or "off"
  *         "blink" --> <dont care> should blink all 4 LEDs a few times
  *         "reset" --> <dont care> will reset the simulated error state for the device
  *     3. simulated error state: depress and hold the joystick until the RGB LED turns red
  *     4. rotate the potentiometer closest to the LCD panel to manipulate the battery level (0 - 100%)
  *     5. rotate the potentiometer nearest the RGB LED to add/subtract 10 degrees from the current ambient temperature
  */

// Primary include
#include "Thermostat.h"

// DreamForce 2013 Tunables START

//
// Our default Latitude and Longitude
//
#define DEFAULT_LATITUDE                     37.7842
#define DEFAULT_LONGITUDE                   -122.4016  

//
// Name our endpoint something unique
//
#define DEFAULT_ENDPOINT_NAME               "DreamForce Thermostat"

// DreamForce 2013 Tunables END

// Wait Loop default sleep per iteration
#define DEFAULT_MAIN_LOOP_WAIT              2.0     // 2 seconds

// JSON parsing support
#include "picojson.h"

//
// Accelerometer Support
//
#include "MMA7660.h"
MMA7660 acc(p28, p27);

//
// Temperature Sensor Support
//
#include "LM75B.h"
LM75B temp_sensor(p28,p27);

//
// Ethernet support
//
#include "EthernetInterface.h"
EthernetInterface ethernet;

//
// Thermostat SocketIO Support
//
#include "ThermostatSocketIO.h"
ThermostatSocketIO socketio(DEFAULT_ENDPOINT_NAME);

// Include LED Utils
#include "Thermostat-LEDUtils.h"

// Include Base Utils
#include "Thermostat-BaseUtils.h"

// Include Location Stubs
#include "Thermostat-Location.h"

// Default constructor
Thermostat::Thermostat() {
    this->m_temperature = 0.0;
    this->m_battery = 50.0;
    this->m_status = "OK";
}

// Destructor
Thermostat::~Thermostat() {
    // close down connections
    socketio.close();
    ethernet.disconnect();
    this->turnRGBLEDBlue();
}

// get the temp
float Thermostat::getTemperature() {
    // get Pot 2 an additive/subtractive value to the ambient temp
    float scale = Pot2.read();
    scale = scale * 20.0;    // scale to 0 - 20
    scale = scale - 10.0;    // scale -10 to +10
        
    // Get the Temperature (in C)
    float c = temp_sensor.read();
    float f = ((9/5) * c ) + 32;
    
    // now get the ambient temp and scale it
    this->m_temperature =  c + scale;    
    this->display("Temp %.1f C (%.1f F)",this->m_temperature,f);
    this->display_lcd("Temp: %.1f C (%.1f F)",this->m_temperature,f);
     
    // return the temperature    
    return this->m_temperature;
}

// get the current battery level
float Thermostat::getBatteryLevel() {  
    // get Pot 1 an additive/subtractive value to simulate battery level
    this->m_battery = Pot1.read();
    this->m_battery = this->m_battery * 100.0;  // scale to 0 - 100;
        
    // return the battery level
    return this->m_battery;
}

// receive from the heroku SocketIO web service
char *Thermostat::receiveFromWSService(char *buffer) {
    bool success = socketio.read(buffer);
    if (success == true)
        this->display("SocketIO: Read success");
    else
        this->display("SocketIO: Read failure");
    
    return buffer;
}    

// translate the LED status 
int Thermostat::translateLEDStatus(const char *status, int current) {
   int i_status = current; 
     
   if (status != NULL && strlen(status) > 0) {
        if (strcmp(status,"on") == 0 || strcmp(status,"ON") == 0 || strcmp(status,"On") == 0 || strcmp(status,"1") == 0 || strcmp(status,"oN") == 0)
            i_status = 1;
        if (strcmp(status,"off") == 0 || strcmp(status,"OFF") == 0 || strcmp(status,"Off") == 0 || strcmp(status,"0") == 0 || strcmp(status,"oFF") == 0 || strcmp(status,"oF") == 0 || strcmp(status,"ofF") == 0)
            i_status = 0;
   }
   
   // return the status
   return i_status; 
}

// reset the device status to OK
void Thermostat::resetDeviceStatus() {
    this->turnRGBLEDGreen();
    this->m_status = "OK";
    socketio.resetMessageCounter();
    this->resetAllLEDs();
}

// basic parsing and processing of the control message
//
// Control Message Format: 5:::{"name":"control-device","args":[{"hello":"world"}]}
//
void Thermostat::parseAndActOnControlMessage(char *json) {
   picojson::value v;
      
   if (json != NULL && strlen(json) > 0 && strstr(json,"{") != NULL) {
       // move past the socket.io header
       char *json_proper = strstr(json,"{");
       
       // parse the packet
       string err = picojson::parse(v, json_proper, json_proper + strlen(json_proper));
       
       // the args value is an array of json values
       picojson::array list = v.get("args").get<picojson::array>();
       
       // loop through the array and parse/process each element
       for (picojson::array::iterator iter = list.begin(); iter != list.end(); ++iter) {
            // Toggle LEDs
            if ((*iter).get("led1") != NULL) led1.write(this->translateLEDStatus((*iter).get("led1").get<string>().c_str(),(int)led1));
            if ((*iter).get("led2") != NULL) led2.write(this->translateLEDStatus((*iter).get("led2").get<string>().c_str(),(int)led2));
            if ((*iter).get("led3") != NULL) led3.write(this->translateLEDStatus((*iter).get("led3").get<string>().c_str(),(int)led3));
            if ((*iter).get("led4") != NULL) led4.write(this->translateLEDStatus((*iter).get("led4").get<string>().c_str(),(int)led4));
            if ((*iter).get("reset") != NULL) this->resetDeviceStatus();
            if ((*iter).get("blink") != NULL) this->blinkAllLEDs();
        }
   }
}

// recv and process a control message
void Thermostat::processControlMessage() {
    
    if (socketio.is_connected()) {
        char message[SOCKETIO_MESSAGE_LENGTH];
        if (socketio.read(message)) {
            // log the message
            this->display("Received control message: %s",message);
            
            // process the message
            this->parseAndActOnControlMessage(message);
            
            // blink the RX led
            this->blinkTransportRxLED();
        }
        else {
            // no messages received - log
            this->display("No control message received. OK");
        }
    }
}

// send status (no params) to the service
void Thermostat::sendStatus() {         
    // send the status
    this->sendStatus(this->getTemperature(),this->getBatteryLevel());
}

// send status (temp & battery) to the service
void Thermostat::sendStatus(float temp, int bat) { 
    // incorporate location coordinates 
    this->sendStatus(temp,this->m_latitude,this->m_longitude,bat);
}

// send status (temp, lat/long, battery) to the service
void Thermostat::sendStatus(float temp, float latitude, float longitude, float bat) {         
    // Announce 
    this->display("Send: status...");
    this->display_lcd("Sending status...");
                    
    // now send...
    int sent = socketio.emit(temp,latitude,longitude,bat,this->getErrorState(),this->m_status);
     
    // Log
    if (sent > 0) {
       this->display("Send success. Ready...");
       this->display_lcd("Send status: OK.\r\nSleeping...");
    }
    else {
       this->display("Send Failed: sent=%d",sent);
       this->display_lcd("Send status: FAILED.\r\nSleeping...");
    }
    
    // blink the TX led
    this->blinkTransportTxLED();
}

// connect to the heroku WebService
bool Thermostat::connectWebSocketService() {    
    // Log
    this->display("Connecting to SocketIO...");
    this->display_lcd("SocketIO connecting...");
    
    // only connect if we are not already so
    if (!socketio.is_connected()) socketio.connect();
    
    // check connection status
    bool connected = socketio.is_connected();
            
    // log 
    if (connected == true) {
        this->display("SocketIO: Connected!");
        this->display_lcd("SocketIO: Connected.");
    }
    else { 
        this->display("SocketIO: Not connected!");
        this->display_lcd("SocketIO: Connect FAILED.");
    }
        
    // return the status
    return connected;
}

// Connect up Ethernet
bool Thermostat::connectEthernet() { 
    bool connected = false;
    char *ipAddr = NULL;
    
    // Use DHCP
    this->display("Initializing Ethernet...");
    this->display_lcd("Ethernet: Initializing...");
    ethernet.init(); 
    
    // attempt connection
    this->display("Connecting Ethernet...");
    this->display_lcd("Ethernet: Connecting...");
    if (ethernet.connect() == 0) connected = true;
    
    // check connection status
    if (connected) {
        ipAddr = ethernet.getIPAddress();
        if (ipAddr != NULL && strlen(ipAddr) > 0)
            connected = true;
    }
   
    // log 
    if (connected == true) {
        this->display("Ethernet: Connected.\r\nIP: %s", ipAddr);
        this->display_lcd("Ethernet: Connected\r\nIP: %s",ipAddr);
    }
    else {
        this->display("Ethernet: Not connected");
        this->display_lcd("Ethernet: Connect FAILED.");
    }
    
    // return the status
    return connected; 
}

// gracefully de-register and close down the WS connection
void Thermostat::gracefullyDisconnect() {    
    // disconnect
    if (socketio.is_connected()) socketio.close();
        
    // announce
    this->display("Disconnected.");
    this->display_lcd("Disconnected.");
    this->turnRGBLEDBlue();
    
    // reset any status
    this->m_status = "OK";
    socketio.resetMessageCounter();
}

// external function in main() to check for the CTRL-C key press to exit the application gracefully
extern void checkForExit();

// main loop
void Thermostat::mainLoop() {
    // initialize our location
    this->initLocation(); 
    
    // begin the main loop
    while(true) {
        // sleep for a bit
        checkForExit();
        wait(DEFAULT_MAIN_LOOP_WAIT);
        
        // announce our position
        this->updateCoordinates();
        
        // if not connected... reconnect
        if (!socketio.is_connected()){
            // announce
            this->display("Re-connecting...");
            this->display_lcd("Re-connecting...");
            
            // re-connect
            if (this->connectWebSocketService()) {
                // announce
                this->display("Reconnect success");
                this->display_lcd("Reconnect: SUCCESS");
                this->turnRGBLEDGreen();
                this->resetAllLEDs();
            }
            else {
                // announce
                this->display("Reconnect failure");
                this->display_lcd("Reconnect: FAILED");
                this->gracefullyDisconnect();
                this->turnRGBLEDRed();
            }
        }
                
        // check and react to the joystick button press
        if (joystick_pressed) {
            if (this->m_rgbLEDColor > 1) {
                this->turnRGBLEDRed();
                this->m_status = "FAIL";
            }
            else {
                this->turnRGBLEDGreen();
                this->m_status = "OK";
            }
        }
        else if (socketio.is_connected() && this->m_rgbLEDColor > 121) {
            this->turnRGBLEDGreen();
        }
        
        // check the status of the joystick
        if (joystick) {
            if (socketio.is_connected()) {
                // announce 
                this->display("Disconnecting...");
                this->display_lcd("Disconnecting...");
                
                // disconnect
                this->gracefullyDisconnect();
            }
            else if (!socketio.is_connected()){
                // announce
                this->display("Re-connecting...");
                this->display_lcd("Re-connecting...");
                
                // re-connect
                if (this->connectWebSocketService()) {
                    // announce
                    this->display("Reconnect success");
                    this->display_lcd("Reconnect: SUCCESS");
                    this->turnRGBLEDGreen();
                    this->resetAllLEDs();
                }
                else {
                    // announce
                    this->display("Reconnect failure");
                    this->display_lcd("Reconnect: FAILED");
                    this->gracefullyDisconnect();
                    this->turnRGBLEDRed();
                }
            }
        }
        
        // if we are connected, send our status
        if (socketio.is_connected()) {
            // send status
            this->sendStatus();
            checkForExit();
        }
        
        // if we are connected, read any control we may receive
        if (socketio.is_connected()) {
            // process control messages
            this->processControlMessage();
            checkForExit();
        }
    }
}

// Run the Demo
void Thermostat::runDemo() {
    // Announce
    this->display("Thermostat Hands-On Demo v1.0");
    this->display_lcd("Thermostat Hands-On\r\nDemo v1.0");
            
    // init the RGB LED
    this->display("Initializing LEDs...");
    this->turnRGBLEDBlue();
    
    // Log
    this->display("Connecting...");
    this->display_lcd("Connecting...");
    
    // connect and send the initial status
    if (this->connectEthernet() == true && this->connectWebSocketService() == true) {
        this->sendStatus();
        this->mainLoop();
    }
    else {
        this->display("Connection failure. Application exiting...");
        this->display_lcd("Connect: FAILURE.\r\nApplication Exiting");
    }
        
    // exit the application if we get here
    exit(1);
}