Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
Yeeeeee Hawwwww!!! It’s summer time in Texas and with that comes outdoor cooking. One of the cornerstones of outdoor cooking is BBQ. BBQ is a method of cooking that roasts a protein over indirect heat for a long period of time typically using smoke as a flavoring agent. Cooking BBQ well is an art, which is why folks invest thousands in cooking rigs and compete in cook-offs at rodeos.
The weapon of choice for BBQ’ing is a tool called the smoker. A smoker is nothing more than a large metal box that has a place for a small wood fire to be built and an area away from the fire for the protein to sit. These come in all shapes and sizes depending on the amount of meat that needs to be cooked. The larger smokers used in competitive cooking can produce great BBQ, but require years of experience to be able to manage the temperature and cook the meat optimally. In addition to the experience needed, the chef must constantly watch the smoker to ensure it never gets too hot or too cold. There’s got to be an easier way!
Home chefs that don’t need to cook a whole hog in one sitting can invest in a tool called the electric smoker. Electric smokers use a heating element similar to what you’d find in your oven to heat up a small wood chip box which produces smoke. These work just as well as their larger competition brethren, but often come with poor controls and instrumentation. I set out to change this by augmenting my smoker with some IoT smarts.
When smoking meat there are two factors that need to be monitored: the smoker’s temperature and the temperature of the protein we are cooking. Both of these measurements can be taken using thermocouples. I ordered two thermocouples online. One thermocouple was a cheap cylindrical model that was designed to be bolted through sheet metal. This proved to be perfect for monitoring the temperatures of the smoker cabinet. The other thermocouple was a food safe meat probe style thermocouple. I drilled two holes in the back of the smoker cabinet and mounted the thermocouples. These were of course connected up to an ADS1118 BoosterPack, which luckily can be used to read both of the sensors without any modification.
Now that I had the temperatures instrumented, I needed a way to control the heating element of the smoker. For this I turned to my trusty solid state relay board. Because the heating element of the smoker was more high power than the loads I had previously powered using the solid state relay board, I decided to parallel all four channels so that the load would be distributed across the channels.
After wiring everything up, it was time to work on the software for the smoker. While I could have reused the software I had previously made for thesous vide, I decided to start from scratch and develop a new user interface. I wanted something that would allow me to graphically show the temperatures in real time and after looking around I decided to use Node-RED. Node-RED is a graphical tool based on Node-JS that allows users to connect the IoT. Blocks exist for everything from MQTT to Twitter. I found an example online that showed how to make a control panel and graph and modified it to suit my needs. On the embedded side, I used the Arduino PID and MQTT libraries to implement the control loop and communications with the cloud.
To test out the smoker, I decided to cook a cut of meat which you don’t normally see at BBQ restaurants: Short Ribs. Short ribs are an idea cut for smoking because the marbling is consistent throughout which helps to keep the meat moist during the cooking process. The reason you don’t see them sold more often though is because they take a large amount of space in the smoker for the amount of meat they yield when finished. After applying a dry rub the day before, I tossed them in the smoker for approximately 10 hours. When the meat temperature reached 200F I pulled them out and let them rest for 30 minutes before slicing and serving.
As you can see, they turned out incredible! Once again, LaunchPad and a little ingenuity have changed cooking forever.
#include "Energia.h"
#include <WiFi.h>
#include <ADS1118.h>
#include <PubSubClient.h>
#include <SPI.h>
#include <PID_v1.h>
#include <String.h>
void setup();
void loop();
#define RelayPin 37
WiFiClient wclient;
byte server[] = { 198, 41, 30, 241 }; // Public MQTT Brokers: http://mqtt.org/wiki/doku.php/public_brokers
byte ip[] = { 172, 16, 0, 100 };
char sensorRead[4];
#define WIFI_SSID "AndroidAP"
#define WIFI_PWD "Energia!"
PubSubClient client(server, 1883, callback, wclient);
ADS1118 ADS;
char smokerState = 0;
double smokerTemp = 0.0;
double meatTemp = 0.0;
double meatSetpoint = 0.0;
//Define Variables we'll be connecting to
double Setpoint, Output;
//Specify the links and initial tuning parameters
PID myPID(&smokerTemp, &Output, &Setpoint, 850, 0.5, 0.1, DIRECT);
int WindowSize = 5000;
unsigned long windowStartTime;
void callback(char* inTopic, byte* payload, unsigned int length){
// In order to republish this payload, a copy must be made
// as the orignal payload buffer will be overwritten whilst
// constructing the PUBLISH packet.
Serial.println(inTopic);
Serial.println((char*)payload);
if(strcmp(inTopic, "LpSmokerStateOut")==0){
Serial.println("State Change Recieved");
if(payload[0] == '1'){
smokerState = 1;
Serial.println("Smoker On");
}else if(payload[0] == '0'){
smokerState = 0;
Serial.println("Smoker Off");
}
// Serial.println(payload);
}else if(strcmp(inTopic, "LpSmokerSetOut")==0){
Serial.println("Smoker Temp Recieved");
payload[3] = 0;
Setpoint = double(atoi((char*)payload));
Serial.println(Setpoint);
// Serial.println(payload);
}else if(strcmp(inTopic, "LpMeatSetOut")==0){
Serial.println("Meat Temp Recieved");
payload[3] = 0;
meatSetpoint = double(atoi((char*)payload));
Serial.println(meatSetpoint);
// Serial.println(payload);
}
// // Allocate the correct amount of memory for the payload copy
// byte* p = (byte*)malloc(length);
// // Copy the payload to the new buffer
// memcpy(p,payload,length);
// client.publish("outTopic", p, length);
// // Free the memory
// free(p);
}
void setup() {
Setpoint = 225;
smokerState = 0;
//IO setup
pinMode(40, OUTPUT);
pinMode(39, OUTPUT);
pinMode(38, OUTPUT);
pinMode(37, OUTPUT);
digitalWrite(40, HIGH);
digitalWrite(39, HIGH);
digitalWrite(38, HIGH);
digitalWrite(37, HIGH);
//Start up the Serial port for debugging
Serial.begin(115200);
//Configure the thermocouple interface
ADS.begin();
//Turn off that annoying buzzer
noTone(2);
//Start up WiFi, but don't wait for a connection
Serial.println("Start WiFi");
WiFi.begin(WIFI_SSID);//, WIFI_PWD);
while (WiFi.localIP() == INADDR_NONE) {
// print dots while we wait for an ip addresss
Serial.print(".");
delay(300);
}
Serial.println("\nIP Address obtained");
windowStartTime = millis();
//initialize the variables we're linked to
Setpoint = 225;
//tell the PID to range between 0 and the full window size
myPID.SetOutputLimits(0, WindowSize);
//turn the PID on
myPID.SetMode(AUTOMATIC);
client.connect("LaunchPadClientTrey");
Serial.print(".");
delay(300);
client.subscribe("LpSmokerStateOut");
client.subscribe("LpSmokerSetOut");
client.subscribe("LpMeatSetOut");
}
void sendMQTT(){
//Make sure we're connected to WiFi
if(WiFi.localIP() == INADDR_NONE) {
return;
}
//Make sure we are connected to the MQTT broker
if(!client.connected()){
client.connect("LaunchPadClientTrey");
Serial.print(".");
delay(300);
client.subscribe("LpSmokerStateOut");
client.subscribe("LpSmokerSetOut");
client.subscribe("LpMeatSetOut");
return;
}
//Publish our data to the MQTT Broker
// convert into to char array
String str = (String)int(smokerState);
int str_len = str.length() + 1; // Length (with one extra character for the null terminator)
char char_array[str_len]; // Prepare the character array (the buffer)
str.toCharArray(char_array, str_len); // Copy it over
// publish data to MQTT broker
if (client.connected()) {
client.publish("LpSmokerStateIn", char_array);
}
String str1 = (String)int(Setpoint);
int str_len1 = str1.length() + 1; // Length (with one extra character for the null terminator)
char char_array1[str_len1]; // Prepare the character array (the buffer)
str1.toCharArray(char_array1, str_len1); // Copy it over
// publish data to MQTT broker
if (client.connected()) {
client.publish("LpSmokerSetIn", char_array1);
}
String str2 = (String)int(meatSetpoint);
int str2_len = str2.length() + 1; // Length (with one extra character for the null terminator)
char char_array2[str2_len]; // Prepare the character array (the buffer)
str2.toCharArray(char_array2, str2_len); // Copy it over
// publish data to MQTT broker
if (client.connected()) {
client.publish("LpMeatSetIn", char_array2);
}
String str3 = (String)int(smokerTemp);
int str_len3 = str3.length() + 1; // Length (with one extra character for the null terminator)
char char_array3[str_len3]; // Prepare the character array (the buffer)
str3.toCharArray(char_array3, str_len3); // Copy it over
// publish data to MQTT broker
if (client.connected()) {
client.publish("LpSmokerTempAct", char_array3);
}
String str4 = (String)int(meatTemp);
int str_len4 = str4.length() + 1; // Length (with one extra character for the null terminator)
char char_array4[str_len4]; // Prepare the character array (the buffer)
str4.toCharArray(char_array4, str_len4); // Copy it over
// publish data to MQTT broker
if (client.connected()) {
client.publish("LpMeatTempAct", char_array4);
}
}
void loop() {
static int loadShare = 0;
static long lastTime = millis();
ADS.run();
if((millis() - lastTime) > 15000){
//Turn off the heat temporarily
digitalWrite(40, HIGH);
digitalWrite(39, HIGH);
digitalWrite(38, HIGH);
digitalWrite(37, HIGH);
if(client.connected())
client.loop();
sendMQTT();
}
meatTemp = ADS.readFarenheit(1);
smokerTemp = ADS.readFarenheit(0);
// Serial.print("Smoker Temp: ");
// Serial.print(smokerTemp);
// Serial.println("F");
//
//
// Serial.print("Meat Temp: ");
// Serial.print(meatTemp);
// Serial.println("F");
if(smokerState)
{
myPID.Compute();
/************************************************
* turn the output pin on/off based on pid output
************************************************/
unsigned long now = millis();
if(now - windowStartTime>WindowSize)
{ //time to shift the Relay Window
windowStartTime += WindowSize;
}
if(Output > now - windowStartTime){
//Turn the heat on and share the load across the triacs
digitalWrite(40, loadShare & 1);
digitalWrite(39, loadShare & 2);
digitalWrite(38, loadShare & 4);
digitalWrite(37, loadShare & 8);
loadShare++;
if(loadShare == 0x10)
loadShare = 1;
Serial.println("Heat On");
}else{
//Turn the heat off
digitalWrite(40, HIGH);
digitalWrite(39, HIGH);
digitalWrite(38, HIGH);
digitalWrite(37, HIGH);
Serial.println("Heat Off");
}
} else {
//If the smoker isn't turned on make sure the triacs are turned off
digitalWrite(40, HIGH);
digitalWrite(39, HIGH);
digitalWrite(38, HIGH);
digitalWrite(37, HIGH);
}
}
Node-RED Flow
JSON[{"id":"e4924113.e3d978","type":"mqtt-broker","broker":"198.41.30.241","port":"1883","clientid":""},{"id":"c66445d5.585f48","type":"websocket-listener","path":"/ws/mobiui","wholemsg":"false"},{"id":"c8ba3187.3745d","type":"http in","name":"http request: control","url":"/control","method":"get","x":178,"y":90,"z":"6fb59101.904a7","wires":[["413cc48.fbec33c"]]},{"id":"413cc48.fbec33c","type":"template","name":"chart","field":"payload","template":"<!DOCTYPE HTML>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <meta HTTP-EQUIV=\"CACHE-CONTROL\" CONTENT=\"NO-CACHE\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /> \n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n <title>Node-RED mobi ui</title>\n <link rel=\"stylesheet\" href=\"//code.jquery.com/mobile/1.4.3/jquery.mobile-1.4.3.min.css\" />\n <script src=\"//code.jquery.com/jquery-1.11.1.min.js\"></script>\n <script src=\"//code.jquery.com/mobile/1.4.3/jquery.mobile-1.4.3.min.js\"></script>\n <script src=\"//cdnjs.cloudflare.com/ajax/libs/highcharts/4.0.3/highcharts.js\"></script>\n <script>//Node-Red mobi ui - LHG industrialinternet.co.uk\n//\tconsole.log(\"NR mobi UI 1.0: Controls, Chart\");\n\tvar schedule = null;\n\tvar itemLookup = null;\n\tvar statusMsg = false;\n\tvar daiableWidgets = false; \n\tvar connected = false;\n\tvar wsUri=\"wss://\"+window.location.hostname+\"/ws/mobiui\";\n\tvar ws=null;\n\tvar smokerTempAct=69;\n\tvar meatTempAct=69;\n\t\n\t var options = {\n chart: {\n type: 'spline',\n animation: Highcharts.svg, // don't animate in old IE\n events: {\n load: function () {\n\n // set up the updating of the chart each second\n var series0 = this.series[0];\n setInterval(function () {\n var x = (new Date()).getTime(), // current time\n y = smokerTempAct;\n series0.addPoint([x, y], true, true);\n }, 15000);\n var series1 = this.series[1];\n setInterval(function () {\n var x = (new Date()).getTime(), // current time\n y = meatTempAct;\n series1.addPoint([x, y], true, true);\n }, 15000);\n }\n }\n\n },\n title: {\n text: 'Current Smoker Temperature'\n },\n xAxis: {\n type: 'datetime',\n tickPixelInterval: 1500\n },\n yAxis: {\n title: {\n text: 'Temperature (F)'\n },\n plotLines: [{\n value: 0,\n width: 1,\n color: '#808080'\n }]\n },\n tooltip: {\n formatter: function () {\n return '<b>' + this.series.name + '</b><br/>' +\n Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +\n Highcharts.numberFormat(this.y, 2);\n }\n },\n legend: {\n enabled: true\n },\n exporting: {\n enabled: false\n },\n series: [{\n name: 'Smoker Temperature',\n data: (function () {\n // generate an array of random data\n var data = [],\n time = (new Date()).getTime(),\n i;\n\n for (i = -19; i <= 0; i += 1) {\n data.push({\n x: time + i * 1000,\n y: Math.random()\n });\n }\n return data;\n }())\n },{\n name: 'Meat Temperature',\n data: (function () {\n // generate an array of random data\n var data = [],\n time = (new Date()).getTime(),\n i;\n\n for (i = -19; i <= 0; i += 1) {\n data.push({\n x: time + i * 1000,\n y: Math.random()\n });\n }\n return data;\n }())\n }]\n };\n\n\n\t\n\tfunction wsConn() {\n\t\tws = new WebSocket(wsUri);\n\t\tws.onmessage = function(m) {\n//\t\t\tconsole.log('< from-node-red:',m.data);\n\t\t\tif (typeof(m.data) === \"string\" && m. data !== null){\n\t\t\t\tvar msg =JSON.parse(m.data);\n\t\t\t\tvar ftc = msg.id.substring(0, 3);\n\t\t\t\t//console.log(\"id:\"+msg.id+\" fct:\"+ftc);\n\t\t\t\tif(ftc== \"init\") {init( msg.v);}\n\t\t\t\tif(ftc==\"tsw\"){setFlip(msg.id,msg.v);}\n\t\t\t\tif(ftc==\"sld\"){setSlider(msg.id,msg.v);}\n\t\t\t\tif(ftc==\"val\"){setValue(msg.id,msg.v);}\n\t\t\t\t//if(ftc==\"cha\"){showCharts( msg.v.values,msg.v.colors,msg.v.engs,msg.v.tags,msg.v.names,msg.v.nos,msg.v.title,.msg.v.yTitle)};\n\t\t\t\tif(ftc==\"cha\"){showCharts(msg.id,msg.v)};\n\t\t\t\tif(ftc==\"shd\"){setSchedule(msg.v);}\n\t\t\t\tif(ftc==\"sta\"){setStatus(msg.v.stMsg,msg.v.dur,msg.v.pri);}\n\t\t\t\tif(ftc==\"ack\"){clearReq();}\n\t\t\t}\n\t\t}\n\t\tws.onopen = function() { \n\t\t\tstatusMsg=false;\n\t\t\tif(daiableWidgets==true){enablePage();}\n\t\t\tsetStatus(\"Connected\",5,0); \n\t\t\tconnected = true;\n\t\t\tvar obj = {\"id\":\"init\",\"v\":1};\n\t\t\tgetRequest = JSON.stringify(obj); \t\n\t\t\tws.send(getRequest);\t\t\t// Request ui status from NR\n//\t\t\tconsole.log(\"sent init requeset\");\n\t\t\t\n\t\t$('#container').highcharts(options);\n\t\t\t\n\t\t\t\n\t\t}\n\t\tws.onclose = function() {\n//\t\t\tconsole.log('Node-RED connection closed: '+new Date().toUTCString()); \n\t\t\tconnected = false; \n\t\t\tws = null;\n\t\t\tsetStatus(\"No connection to server!\",0,1);\n\t\t\tif(daiableWidgets==false){disablePage();}\n\t\t\tsetTimeout(wsConn, 10000);\n\t\t}\t\n\t\tws.onerror = function(){\n\t\t\t//console.log(\"connection error\");\n\t\t}\n\t}\n\t$( window ).load(function(){wsConn()});\n//\t wsConn(); \t\t\t\t\t// connect to Node-RED server\n\tfunction init(values){ \t\t// initialise UI controls\n\t\tui = JSON.parse(values);\n\t\t for (var item in ui) {\n\t\t\t//console.log(\"item: \"+item);\n\t\t\tvar m = ui[item];\n\t\t\tinitSetters(m);\n\t\t}\n\t}\n\tfunction initSetters(msg){ \t// update UI widgets on connect \n//\t\tconsole.log(\"init item id:\"+msg.id+\" value:\"+ msg.v);\n\t\tif(ftc==\"tsw\"){setFlip(msg.id,msg.v);}\n\t\tif(ftc==\"sld\"){setSlider(msg.id,msg.v);}\n\t\tif(ftc==\"val\"){setValue(msg.id,msg.v);}\n\t\tif(ftc==\"cha\"){showCharts(msg.id,msg.v)};\n\t\tif(ftc==\"shd\"){setSchedule(msg.v);}\n\t\tif(ftc==\"sta\"){setStatus(msg.v);}\n\t}\n\tfunction setFlip(_id,_v){ \t// update flip \n\t\tmyselect = $(\"#\"+_id);\n\t\t//console.log(\"flip id:\"+_id+\" value:\"+_v+\" tyepof:\"+ typeof _v +\" state:\"+.data('state')+\" req:\"+myselect.data('req'));\n\t\t//if(myselect.data('req')==1) return; // request on progress stops flip UI chatter \n\t\tif(myselect.data('state')!=_v){\n\t\t\tif(_v== true || _v=='true'){\n\t\t\t\tmyselect[0].selectedIndex=1; \n\t\t\t\tmyselect.data('state',1)\n\t\t\t} \n\t\t\telse { \n\t\t\t\tmyselect[0].selectedIndex=0;\n\t\t\t\tmyselect.data('state',0);\n\t\t\t}\n\t\t\tmyselect.flipswitch(\"refresh\");\n\t\t\t//myselect.stopImmediatePropagation();\n\t\t\t//console.log(\"jq:\"+myselect[0].selectedIndex+\" flip id:\"+_id+\" v:\"+value+\" data-state:\"+myselect.data('state'));\n\t\t}\n\t}\n\tfunction setSlider(_id,_v){\t// update slider\n\t\tmyselect = $(\"#\"+_id);\n\t\t myselect.val(_v);\n\t\t myselect.slider('refresh');\n\t}\n\tfunction setValue(_id,_v){\t// update value display\n\t\tmyselect = $(\"#\"+_id);\n\t\tmyselect.val(_v);\n\t}\n\tfunction showCharts(_id,_v){ // render chart\n\t\tif(_id == \"chart-1\"){\n\t\t\tsmokerTempAct = _v;\n\t\t}else if(_id == \"chart-2\"){\n\t\t\tmeatTempAct = _v;\n\t\t}\n\t\t\t\n \n\t}\n\tfunction setSchedule(_v){\t// update schedule \n\t\t//console.log(\"shed:\"+_v);\n\t\tschedule = JSON.parse(_v);\n//\t\tconsole.log(\"shed:\"+schedule.items[0].id);\n\t}\n\tfunction setStatus(msg,dur,pri){\t // show msg on status bar\n\t\tif(statusMsg == true){return};\n\t\tstatusMsg= true;\n\t\tif(pri>0){\n\t\t\t//msg = \"<span class='alert'>\"+msg+\"</span>\";\n\t\t\t$(\"#statusView\").toggleClass(\"statusViewAlert\");\n\t\t} else {\n\t\t\t$(\"#statusView\").toggleClass(\"statusView\");\n\t\t}\n\t\t$(\"#statusView\").show();\n\t\t$(\"#statusView\").html(msg);\n\t\tdur = dur*1000;\n\t\tif(dur >0){\n\t\t\tsetTimeout(function(){$(\"#statusView\").hide(200);$(\"#statusView\").html(\"\"); statusMsg= false},dur)\n\t\t}\n\t}\t\n\tfunction disablePage(){\t\t\n\t\t$(\"[data-role=flipswitch]\").flipswitch( \"disable\" );\n\t\t//$(\"[data-role=range]\").disabled = true;\n\t\t$(\"[data-role=range]\").slider( \"option\", \"disabled\", true );\n\t\t$(\"[data-rel=popup]\").toggleClass(\"ui-disabled\");\n\t\tdaiableWidgets = true;\n\t}\n\tfunction enablePage(){\n\t\t$(\"[data-role=flipswitch]\").flipswitch( \"enable\" );\n\t\t$(\"[data-role=range]\").slider( \"option\", \"enabled\", true );\n\t\t$(\"[data-role=range]\").slider( \"enable\" );\n\t\t$(\"[data-rel=popup]\").toggleClass(\"ui-disabled\");\n\t\tdaiableWidgets = false;\n\t}\n\t$(function() { \t\t\t\t// UI event handlers \n\t\t// Flip-switch change\n\t\t$(\"[data-role=flipswitch]\").bind( \"change\", function(event, ui) {\n\t\t\t//console.log(\"id:\"+this.id+\" val:\"+$(this).flipswitch().val()+\" state:\"+$(this).data('state')+\" req:\"+$(this).data('reqstate'));\n\t\t\tvar _value = $(this).flipswitch().val();\n\t\t\tif($(this).data('state') != _value){\n\t\t\t\t$(this).data('state',_value); \n\t\t\t\tvar obj = {\"id\":\"\"+this.id+\"\",\"v\":_value};\n\t\t\t\tsetActions = JSON.stringify(obj); \t\n\t\t\t\tws.send(setActions);\n\t\t\t}\n\t\t});\n\t\t// Slider end\n\t\t$(\".ui-slider\").bind( \"slidestop\", function(event, ui) {\n\t\t\tvar obj = {\"id\":\"\"+event.target.id+\"\",\"v\":event.target.value};\n\t\t\tsetActions = JSON.stringify(obj); \t\n\t\t\tws.send(setActions);\n\t\t});\n\t\t// Popup send\n\t\t$(\"[data-ui-type=pop-save]\").bind( \"click\", function(event, ui) {\n\t\t\tbid = this.id.split(\"_\");\n\t\t\twid =\"#\"+bid[0]+\"-pop\";\n\t\t\t$(wid ).popup( \"close\" );\n\t\t\ttid=\"#\"+bid[0];\n\t\t\tvar obj = {\"id\":\"\"+bid[0]+\"\",\"v\":$(tid ).val()};\n\t\t\tsetActions = JSON.stringify(obj); \t\n\t\t\tws.send(setActions);\n\t\t});\n\t\t// Grouped Radio buttons click\n\t\t$(\"[data-ui-type=shd-sel]\").bind( \"click\", function(event, ui) {\n\t\t\t$(\"[data-ui-type=shd-sel]\").prop( \"checked\", false ).checkboxradio( \"refresh\" );\n\t\t\t$(this ).prop( \"checked\", true ).checkboxradio( \"refresh\" );\n\t\t\tvar item = this.id.split(\"-\");\n\t\t\tif( itemLookup == null){\n\t\t\t\t itemLookup = item[1]-1;\n\t\t\t } else { // Copy item edits back obj\n\t\t\t\tschedule.items[itemLookup].tag\t\t = $('#shd-tag').val();\n\t\t\t\tschedule.items[itemLookup].startTime = $('#shd-st').val();\n\t\t\t\tschedule.items[itemLookup].startValue = $('#shd-st-v').val();\n\t\t\t\tschedule.items[itemLookup].endTime\t = $('#shd-et').val();\n\t\t\t\tschedule.items[itemLookup].endValue\t = $('#shd-et-v').val();\n\t\t\t\tfor (dow = 0; dow< 7; dow++) {\n\t\t\t\t\tvar dowUI = '#shd-dow-'+dow;\n\t\t\t\t\tif( $(dowUI).prop( \"checked\")){\n\t\t\t\t\t\tschedule.items[itemLookup].dofWeek[dow] = 1 ;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tschedule.items[itemLookup].dofWeek[dow] = 0 ;\n\t\t\t\t\t}\n\t\t\t\t} \n\t\t\t\titemLookup = item[1]-1;\n\t\t\t }\n//\t\t\tconsole.log(\"shed item\"+item[1]+\" lookup tag:\"+schedule.items[itemLookup].tag);\n\t\t\t$('#shd-tag').val(schedule.items[itemLookup].tag);\n\t\t\t$('#shd-st').val(schedule.items[itemLookup].startTime);\n\t\t\t$('#shd-st-v').val(schedule.items[itemLookup].startValue);\n\t\t\t$('#shd-et').val(schedule.items[itemLookup].endTime);\n\t\t\t$('#shd-et-v').val(schedule.items[itemLookup].endValue);\n\t\t\t//console.log(\"group radio - id:\"+ this.id+\" val:\"+$(this).val());\n\t\t\tfor (dow = 0; dow< 7; dow++) {\n\t\t\t\tvar dowUI = '#shd-dow-'+dow;\n\t\t\t\tif (schedule.items[itemLookup].dofWeek[dow]==1){\n\t\t\t\t\t$(dowUI).prop( \"checked\", true ).checkboxradio( \"refresh\" )\n\t\t\t\t} else {\n\t\t\t\t\t$(dowUI).prop( \"checked\", false ).checkboxradio( \"refresh\" )\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t// Schedule save\n\t\t$( \"#shd-save\" ).bind( \"click\", function(event, ui) {\n//\t\t\tconsole.log(\"shd-save\");\n\t\t\tif( itemLookup != null){\n\t\t\t\tschedule.items[itemLookup].tag\t\t = $('#shd-tag').val();\n\t\t\t\tschedule.items[itemLookup].startTime = $('#shd-st').val();\n\t\t\t\tschedule.items[itemLookup].startValue = $('#shd-st-v').val();\n\t\t\t\tschedule.items[itemLookup].endTime\t = $('#shd-et').val();\n\t\t\t\tschedule.items[itemLookup].endValue\t = $('#shd-et-v').val();\n\t\t\t\tfor (dow = 0; dow< 7; dow++) {\n\t\t\t\t\tvar dowUI = '#shd-dow-'+dow;\n\t\t\t\t\tif( $(dowUI).prop( \"checked\")){\n\t\t\t\t\t\tschedule.items[itemLookup].dofWeek[dow] = 1 ;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tschedule.items[itemLookup].dofWeek[dow] = 0 ;\n\t\t\t\t\t}\n\t\t\t\t} \n\t\t\t\tvar obj = {\"id\":\"shd-save\",\"v\":schedule};\n\t\t\t\tgetRequest = JSON.stringify(obj); \t\n\t\t\t\tws.send(getRequest);\n\t\t\t}\n\t\t});\n\t\t// Utills\n\t\tvar showHide=0;\n\t\t$(window).keydown(function(event) {\n\t\t\tif(event.shiftKey && event.keyCode == 68 ) { \n\t\t\t\t//console.log(event.keyCode);\n\t\t\t\tif(showHide==0){$(\"#foo\").show('slow');showHide=1;}else{$(\"#foo\").hide();showHide=0;}\n\t\t\t\tevent.preventDefault(); \n\t\t\t}\n\t\t}); \n\t\t$( document ).on( \"vclick\", \"#b1\", function() {\n\t\t\tlocation.reload();\n//\t\t\tconsole.log(\"reload button\");\n\t\t});\t\n\t});\n </script>\n <style>\n\t\t@media only screen and (min-width: 521px){\n\t\t\t#header1 {width: 507px !important; margin:auto auto !important; position: relative !important; border:1px solid #cccccc;}\n\t\t\t#c1 {width: 475px !important; min-height:500px !important; margin:auto auto !important; position: relative !important; border:1px solid #cccccc;}\n\t\t}\t\n\t\t.alert {font-weight: bold; color: #FF6C01;}\n\t\t.smallin .ui-input-text {width: 200px !important; color:red;}\n\t\tlegend.h1lb {padding:10px 0 5px 0;}\n\t\t.statusView {width: 100% !important; margin:0px; position: relative !important; height:28px; padding:10px 0 0 0; background-color:#FAFAFA; font-weight:bold; -webkit-border-radius: 0.7em !important; border-radius: 0.7em; display:none; text-align:center; }\n\t\t.statusViewAlert {width: 100% !important; margin:0px; position: relative !important; height:28px; padding:10px 0 0 0; background-color:#F8B584 !important; font-weight:bold; -webkit-border-radius: 0.7em !important; border-radius: 0.7em; display:none; text-align:center; }\n\t\t.ui-flipswitch {\"border-radius: 2em !important; -webkit-border-radius: 2em !important; -moz-border-radius: 2em !important;}\n\t\t/* Active button */\n\t\t.ui-page-theme-a .ui-btn.ui-btn-active,html .ui-bar-a .ui-btn.ui-btn-active,html .ui-body-a .ui-btn.ui-btn-active,html body .ui-group-theme-a .ui-btn.ui-btn-active,html head + body .ui-btn.ui-btn-a.ui-btn-active,\n\t\t/* Active checkbox icon */\n\t\t.ui-page-theme-a .ui-checkbox-on:after,html .ui-bar-a .ui-checkbox-on:after,html .ui-body-a .ui-checkbox-on:after,html body .ui-group-theme-a .ui-checkbox-on:after,.ui-btn.ui-checkbox-on.ui-btn-a:after,\n\t\t/* Active flipswitch background */.ui-page-theme-a .ui-flipswitch-active,html .ui-bar-a .ui-flipswitch-active,html .ui-body-a .ui-flipswitch-active,html body .ui-group-theme-a .ui-flipswitch-active,\n\t\thtml body .ui-flipswitch.ui-bar-a.ui-flipswitch-active,\n\t\t/* Active slider track */\n\t\t.ui-page-theme-a .ui-slider-track .ui-btn-active,html .ui-bar-a .ui-slider-track .ui-btn-active,html .ui-body-a .ui-slider-track .ui-btn-active,html body .ui-group-theme-a .ui-slider-track .ui-btn-active,html body div.ui-slider-track.ui-body-a .ui-btn-active {\n\t\t\tbackground-color: \t\t#2A2A2A !important;\n\t\t\tborder-color:\t \t\t#FF6C01 !important;\n\t\t\tcolor: \t\t\t\t\t#fff /*{a-active-color}*/;\n\t\t\ttext-shadow: 0 /*{a-active-shadow-x}*/ 1px /*{a-active-shadow-y}*/ 0 /*{a-active-shadow-radius}*/ #005599 /*{a-active-shadow-color}*/;\n\t\t}\n </style>\n</head>\n<body bgcolor=\"#2A2A2A\">\n <!-- Home -->\n <div data-role=\"page\" id=\"controls-charts\">\n\t<div id=\"c1\" class=\"ui-content\">\n\t<div id=\"statusView\" style=\"\"></div>\t\n\t\t<div class=\"ui-field-contain\">\n\t\t\t<label for=\"tsw-1\">Smoker State:</label>\n\t\t\t<select id=\"tsw-1\" data-role=\"flipswitch\" data-state=\"0\" data-req=\"\">\n\t\t\t <option value=\"0\">Off</option>\n\t\t\t <option value=\"1\">On</option>\n\t\t\t</select>\n\t\t\t</div>\n\t\t\t<div class=\"ui-field-contain\">\n\t\t\t<form>\n \t\t\t<label for=\"sld-1\">Smoker Setpoint (F):</label>\n \t\t\t<input type=\"range\" name=\"sld-1\" id=\"sld-1\" min=\"100\" max=\"350\" value=\"225\">\n \t\t</form>\n\t\t\t</div>\n\t\t\t<div class=\"ui-field-contain\">\n\t\t\t<form>\n \t\t\t \t<label for=\"sld-2\">Meat Done Temperature (F):</label>\n \t\t\t \t<input type=\"range\" name=\"sld-2\" id=\"sld-2\" min=\"100\" max=\"350\" value=\"190\">\n \t\t\t</form>\n\t\t</div>\n\t\t<div id=\"container\" style=\"width: 100%; margin-left:-15px;\"></div>\n\t\t</div>\n\t\t\t\t\n\t</div><!-- eof content -->\n </body>\n</html>","x":451,"y":81,"z":"6fb59101.904a7","wires":[["5cc132d3.a33ecc"]]},{"id":"5cc132d3.a33ecc","type":"http response","name":"http response: Control Interface","x":737,"y":106,"z":"6fb59101.904a7","wires":[]},{"id":"b1b3bc12.4e4c4","type":"function","name":"ui dispatcher","func":"/* \tUI dispatcher v0.1a 23/06/2014\n\tRecives changes from UI and actions them */\nvar obj = JSON.parse(msg.payload);\ndelete msg.payload;\ndelete msg._session;\n\n// toggle switch tsw-1\t- output 1 \nif(obj.id==\"tsw-1\"){\n\tmsg.id = obj.id; \t\n\tmsg.state = obj.v;\t\n\treturn [msg, null, null, null];\n\n// toggle switch tsw-2\t- output 2 \n} else if(obj.id==\"tsw-2\"){\n\tmsg.id = obj.id; \t\t\n\tmsg.state = obj.v;\t\n\treturn [null, msg, null];\n\t\n// slider sld-1 \t\t- output 3 \t\n} else if(obj.id==\"sld-1\"){\n\tmsg.id = obj.id; \n\tmsg.value = obj.v;\t\n\treturn [null, null, msg, null];\n\n// txt popup txt-1 \t\t- output 4 \t\n} else if(obj.id==\"sld-2\"){\n\tmsg.id = obj.id; \n\tmsg.value = obj.v;\t\n\treturn [null, null, null, msg];\n\n// do nothing\n} else {\n\treturn [null, null, null, null];\n}","outputs":"4","x":398,"y":384,"z":"6fb59101.904a7","wires":[["e6cf752c.193088","531e2139.ace1e"],[],["5ae130f6.a51ed","531e2139.ace1e"],["7229fba2.8dd604","531e2139.ace1e"]]},{"id":"13af600d.ec50a","type":"comment","name":"Dispatch mobi UI commands - web sockets","info":"","x":244.9999942779541,"y":328.9999899864197,"z":"6fb59101.904a7","wires":[]},{"id":"2fdb1f6c.d024e","type":"websocket in","name":"web socket mobiUI - in","server":"c66445d5.585f48","x":185.9999942779541,"y":383.99998235702515,"z":"6fb59101.904a7","wires":[["b1b3bc12.4e4c4","4474dc21.bb8b24"]]},{"id":"e6cf752c.193088","type":"function","name":"Format state slider","func":"if(msg.id == \"tsw-1\"){\n\tmsg.payload = msg.state.toString();\n\treturn msg;\n}\nreturn null;\n","outputs":1,"x":598.888916015625,"y":326.4444274902344,"z":"6fb59101.904a7","wires":[["bfdce141.40232","fdb92c8e.0246d"]]},{"id":"5ae130f6.a51ed","type":"function","name":"Format Smoker Slider","func":"msg.payload = msg.value.toString();\nreturn msg;","outputs":1,"x":606.4444580078125,"y":389.22222900390625,"z":"6fb59101.904a7","wires":[["d630819b.29cf8","fdb92c8e.0246d"]]},{"id":"de0cac56.21f35","type":"websocket out","name":"web socket mobiUI - in","server":"c66445d5.585f48","x":625,"y":530,"z":"6fb59101.904a7","wires":[]},{"id":"da42a1b8.25bd6","type":"inject","name":"","topic":"","payload":"0","payloadType":"string","repeat":"","crontab":"","once":false,"x":189,"y":511,"z":"6fb59101.904a7","wires":[["5e418315.a1be7c"]]},{"id":"5e418315.a1be7c","type":"function","name":"","func":"msg.payload = {id:\"tsw-1\",v:parseInt(msg.payload)};\nreturn msg;","outputs":1,"x":360,"y":516,"z":"6fb59101.904a7","wires":[["de0cac56.21f35"]]},{"id":"832107a6.7cdef8","type":"inject","name":"","topic":"","payload":"1","payloadType":"string","repeat":"","crontab":"","once":false,"x":190,"y":545,"z":"6fb59101.904a7","wires":[["5e418315.a1be7c"]]},{"id":"e939184.f16c6e8","type":"function","name":"","func":"msg.payload = {id:\"sld-1\",v:msg.payload}\nreturn msg;","outputs":1,"x":356,"y":595,"z":"6fb59101.904a7","wires":[[]]},{"id":"6f43f374.90bc0c","type":"inject","name":"","topic":"","payload":"120","payloadType":"string","repeat":"","crontab":"","once":false,"x":187,"y":619,"z":"6fb59101.904a7","wires":[["e939184.f16c6e8"]]},{"id":"e7881b62.1877e8","type":"mqtt in","name":"","topic":"LpSmokerTempAct","broker":"e4924113.e3d978","x":192,"y":747,"z":"6fb59101.904a7","wires":[["47016916.b8fe98"]]},{"id":"47016916.b8fe98","type":"function","name":"","func":"msg.payload = {id:\"chart-1\",v:parseInt(msg.payload)}\nreturn msg;","outputs":1,"x":363,"y":748,"z":"6fb59101.904a7","wires":[["de0cac56.21f35"]]},{"id":"644d8c90.9bb274","type":"function","name":"","func":"msg.payload = {id:\"chart-2\",v:parseInt(msg.payload)}\nreturn msg;","outputs":1,"x":364,"y":790,"z":"6fb59101.904a7","wires":[["de0cac56.21f35"]]},{"id":"7059f7d9.8fa608","type":"mqtt in","name":"","topic":"LpMeatTempAct","broker":"e4924113.e3d978","x":184,"y":784,"z":"6fb59101.904a7","wires":[["644d8c90.9bb274"]]},{"id":"7229fba2.8dd604","type":"function","name":"Format Meat Slider","func":"msg.payload = msg.value.toString();\nreturn msg;","outputs":1,"x":600,"y":439,"z":"6fb59101.904a7","wires":[["adaed7a4.525128","fdb92c8e.0246d"]]},{"id":"bfdce141.40232","type":"mqtt out","name":"","topic":"LpSmokerStateOut","qos":"","retain":"","broker":"e4924113.e3d978","x":856,"y":326,"z":"6fb59101.904a7","wires":[]},{"id":"d630819b.29cf8","type":"mqtt out","name":"","topic":"LpSmokerSetOut","qos":"","retain":"","broker":"e4924113.e3d978","x":850,"y":389,"z":"6fb59101.904a7","wires":[]},{"id":"adaed7a4.525128","type":"mqtt out","name":"","topic":"LpMeatSetOut","qos":"","retain":"","broker":"e4924113.e3d978","x":842,"y":439,"z":"6fb59101.904a7","wires":[]},{"id":"4ca6fb60.b35904","type":"mqtt in","name":"","topic":"LpSmokerStateIn","broker":"e4924113.e3d978","x":180,"y":471,"z":"6fb59101.904a7","wires":[["597cc969.a68338","5e418315.a1be7c"]]},{"id":"45539404.baac6c","type":"mqtt in","name":"","topic":"LpSmokerSetIn","broker":"e4924113.e3d978","x":173,"y":580,"z":"6fb59101.904a7","wires":[["e939184.f16c6e8"]]},{"id":"5ed28371.a12d7c","type":"mqtt in","name":"","topic":"LpMeatSetIn","broker":"e4924113.e3d978","x":173,"y":655,"z":"6fb59101.904a7","wires":[["b996cebd.46693"]]},{"id":"3ee6ccd8.c11934","type":"inject","name":"","topic":"","payload":"150","payloadType":"string","repeat":"","crontab":"","once":false,"x":182,"y":691,"z":"6fb59101.904a7","wires":[["b996cebd.46693"]]},{"id":"b996cebd.46693","type":"function","name":"","func":"msg.payload = {id:\"sld-2\",v:msg.payload}\nreturn msg;","outputs":1,"x":360,"y":668,"z":"6fb59101.904a7","wires":[[]]},{"id":"b2b8212.f4d47e","type":"inject","name":"","topic":"","payload":"120","payloadType":"string","repeat":"","crontab":"","once":false,"x":177,"y":844,"z":"6fb59101.904a7","wires":[["47016916.b8fe98"]]},{"id":"9f5aea11.60a518","type":"inject","name":"","topic":"","payload":"130","payloadType":"string","repeat":"","crontab":"","once":false,"x":193,"y":891,"z":"6fb59101.904a7","wires":[["644d8c90.9bb274"]]},{"id":"4474dc21.bb8b24","type":"debug","name":"","active":false,"console":"false","complete":"false","x":389,"y":277,"z":"6fb59101.904a7","wires":[]},{"id":"531e2139.ace1e","type":"debug","name":"","active":false,"console":"false","complete":"false","x":580,"y":268,"z":"6fb59101.904a7","wires":[]},{"id":"fdb92c8e.0246d","type":"debug","name":"","active":false,"console":"false","complete":"false","x":903,"y":220,"z":"6fb59101.904a7","wires":[]},{"id":"b25b38fc.4da4c8","type":"inject","name":"","topic":"","payload":"1","payloadType":"string","repeat":"","crontab":"","once":false,"x":660,"y":202,"z":"6fb59101.904a7","wires":[["bfdce141.40232","fdb92c8e.0246d"]]},{"id":"597cc969.a68338","type":"debug","name":"","active":true,"console":"false","complete":"false","x":382,"y":471,"z":"6fb59101.904a7","wires":[]}]
Comments