Dreamforce 2013 MiniHack Thermostat Challenge - completed code

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

Revision:
0:26c48388f725
Child:
1:3faa003ad6e6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Thermostat.cpp	Tue Nov 05 21:31:01 2013 +0000
@@ -0,0 +1,458 @@
+/* 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.
+ */
+ 
+//
+// 2013 DreamForce MiniHack - 
+// Look for the method: Thermostat::parseAndActOnControlMessage(char *json)
+// From there, add code to look for a "text" message - send its contents to the LCD panel
+// using the this->display(char *) method. You can use the other messages as a reference.
+// 
+
+ /*
+  * 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("text") != NULL) this->display((*iter).get("text").get<string>().c_str());
+            if ((*iter).get("blink") != NULL) this->blinkAllLEDs();
+            
+            //
+            // 2013 DreamForce MiniHack - add code to look for a "text" message - send its contents to the LCD panel
+            // using the this->display(char *) method
+            //
+            // Answer:
+            //
+            if ((*iter).get("text") != NULL) this->display((*iter).get("text").get<string>().c_str()); 
+        }
+   }
+}
+
+// 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();
+                
+        // 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);
+}
\ No newline at end of file