Showing posts with label Temperature. Show all posts
Showing posts with label Temperature. Show all posts

Thursday, April 4, 2019

Thermostat and Node Red

I don't know if I will cover all of this in one post or two. This is the biggest and most complex node I have done. Since it is built on built on public documents and similar ideas, it may go quick.

The reason I built this, we have a nice gas furnace in our sun-room. It is a good sun-room, with lots of windows. Windows are not known for holding in heat. I think this was an add on to our house (looking at the rest of the house and how it was framed underneath it seemed to be maybe a 3 season porch that someone insulted). Imagine this room with the sun shining and leaves on the trees!



The heater had a remote control that was mounted on the wall, and could be used as a thermostat. The bad thing is, the heater gets hot, and sometimes the remote didn't get put back into the little hook on the wall, and well when the remote is sitting on the heater, the case deforms.



I was eventually able to get the cover off the battery compartment, but it never quite worked after that. On eBay these controls sell for over $100, which seems extreme.  For about two years, we just manually controlled the heater with the manual override switch. Sometimes the switch would be left on for days, where other times it was really cold in the room, and it took an hour or more to get up to a comfortable temperature.

We needed a thermostat for that room. Keeping the temperature at some minimum would make the room livable all the time. Preventing the heater from staying on for days would save money. Initially I was going to build a standalone thermostat that would be connected via wires to the heater. Since the thermostat should be away from the heater, wires meant drilling in the walls, and fishing, and lotsa work. I thought about two nodeMCUs that would communicate over WiFi, and that lead me into node-red.

This was actually my first set of node-red nodes. I have a nodeMCU that controls a relay behind the gas stove in a 3D printed box. The Thermostat is controlled by another nodeMCU connected to a touch screen, and a temp/humidity sensor (DHT-22). It has a USB powersupply powering it. The case I am using now is something I found on thingiverse.

I used point to point wiring and followed some basic instructions I found. The touchscreen input and the display are separate devices, but both talk SPI so can be wired to the same SPI pins on the nodeMCU.



Adafruit has a graphics library for the ILI9341 display that includes several fonts. The touchscreen uses the XP2046 chip, so you will need a library for that as well. I think in the Arduino library manager, entering XP2046 will allow installing the library directly without any compiles or other problems. There are displays available in 2.4" and 2.8" (and probably other sizes) that all use the same libraries. The case will need to change to make it all work.

Once the libraries are all installed, then the fun starts. Doing any GUI, we need to provide proper information, as well as simple understandable controls. It is like a joke, if you have to explain it, it isn't that good. In the above display, the top box contains the current Room information. The bottom is the current heater settings with two arrows, one up to raise the temperature, and one down to lower the temperature.

The top of the display is the current date and time. I promise soon there will be an explanation of the Date/Time flow, but this is already getting long, so I probably won't explain it today.

The display can be as fancy as you want it to be. It could have 3d animations for the buttons and fading between temp and humidity, an analog clock or whatever you want to program. I started out pretty basic, and find it meets the needs of the family.

The code, well, it is a little messy, because it is doing a lot!

/**
 * Using the DHT 22 Sensor, get the current temprature and humidity. 
 * Send these values out on the MQTT topics:
 *    sunroom/temperature
 *    sunroom/humidity
 *    
 * Using the touchscreen, display the current time, temprature, humidity, 
 * and thermostat settings. The touch screen allows changing the thermostat
 * setting and sending the value on the MQTT topic:
 *     sunroom/thermostat
 * The thermostat can be changed by listening to the MQTT topic
 *     heater/thermostat
 * The on board LED is controlled by the MQTT topic
 *     heater/light
 * The time is received on the MQTT topic
 *     time/ISO-8601
 *     
 * The code is copyright 2018, Tom Brusehaver 
 * It can be copied using the OSF v3 license. Do what you want with this, but share.
 * 
 * The touchscreen connections are on:   https://nailbuster.com/?page_id=341
 * 
 * Note the input (touch) is the XPT2046, the graphics are the ILI9341
 * Many of the adafruit modules use a different touch input (most of the 
 * ILI9341 touch screen input examples don't work). 
 *
 * The board selected in the IDE is WeMos D1 R2 & Mini. Standard everything 
 * 
 */

#include <SimpleDHT.h>

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <PubSubClientTools.h>
#include <ArduinoJson.h>

#include <XPT2046_Touchscreen.h>
#include <SPI.h>
#include "Adafruit_GFX.h"
#include <Fonts/FreeSans9pt7b.h>
#include <Fonts/FreeSans18pt7b.h>
#include <Fonts/FreeSans12pt7b.h>
#include "Adafruit_ILI9341.h"

const char* ssid = "raspi-webgui";
const char* password = "XXXXXXXXi";
const char* MQTT_SERVER = "10.3.141.1";

String state="off";

// For the Adafruit shield, these are the default.
#define TFT_DC 2
#define TFT_CS 15

#define TCS_PIN  4
// MOSI=11, MISO=12, SCK=13

#define DHTTYPE DHT12   // DHT 22

// DHT Sensor
const int DHTpin = 16;
SimpleDHT22 dht22(DHTpin);

//XPT2046_Touchscreen ts(CS_PIN);
#define TIRQ_PIN  5
//XPT2046_Touchscreen ts(CS_PIN);  // Param 2 - NULL - No interrupts
//XPT2046_Touchscreen ts(CS_PIN, 255);  // Param 2 - 255 - No interrupts
XPT2046_Touchscreen ts(TCS_PIN, TIRQ_PIN);  // Param 2 - Touch IRQ Pin - interrupt enabled polling

// Use hardware SPI 
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);


WiFiClient espClient;
PubSubClient client(MQTT_SERVER, 1883, espClient);
PubSubClientTools mqtt(client);

/**
ThreadController threadControl = ThreadController();
Thread thread = Thread();
**/

/** Global Values **/
long lastMsg = 0;
String s = "";
int    thermostatSetting=70;
String currTime="2018-01-01T00:00";
String eraseTime="2018-01-01T00:00";
String currTemp="0";
String eraseTemp="0";
String currHumid="0";
String eraseHumid="0";
String currThermo="70";
String eraseThermo="70";
String publishedThermo="70";
int outsideTemperature=70;
String currOutsideTemp="70";

const int width=240;
const int height=320;

void setup() {
  Serial.begin(115200);

  // graphics start
  tft.begin();
  // touch start
  ts.begin();

  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); 

  tft.fillScreen(ILI9341_BLUE);
  displayThermoButtons();

  // Connect to WiFi
  Serial.print(s+"Connecting to WiFi: "+ssid+" ");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(s+" connected with IP: "+WiFi.localIP());

  // Connect to MQTT
  Serial.print(s+"Connecting to MQTT: "+MQTT_SERVER+" ... ");
  if (client.connect("SRThermostat")) {
    Serial.println("connected");

    mqtt.subscribe("heater/light", topic1_subscriber);
    mqtt.subscribe("heater/thermostat", topic2_subscriber);
    mqtt.subscribe("time/ISO-8601", topic3_subscriber);
    mqtt.subscribe("outside/farenheight", topic4_subscriber);
  } else {
    Serial.println(s+"failed, rc="+client.state());
  }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("SRThermostat")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic","thermostat Restart");
      mqtt.publish("sunroom/temperature", currTemp);
      mqtt.publish("sunroom/thermostat", currThermo);
      mqtt.subscribe("heater/light", topic1_subscriber);
      mqtt.subscribe("heater/thermostat", topic2_subscriber);
      mqtt.subscribe("time/ISO-8601", topic3_subscriber);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      // Wait 5 seconds before retrying
    }
  }
}

void loop() {
  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    Serial.print("Pressure = ");
    Serial.print(p.z);
    Serial.print(", x = ");
    Serial.print(p.x);
    Serial.print(", y = ");
    Serial.print(p.y);
    Serial.println();

    if ((p.y > 3000) && (p.y < 3800))
    {
      Serial.println("** Y zone **");
      if ((p.x > 1100) && (p.x < 1600)) // UP button
      {
        currThermo = String(++thermostatSetting);
      }
      if ((p.x > 500) && (p.x < 1000)) // Down button
      {
        currThermo = String(--thermostatSetting);
      }
    }
  }

  if (!client.connected()) {
    reconnect();
  }

  client.loop();
  
  publisher();           // send out current values.
  tft.setFont();
  displayTime(2);
  tft.setFont(&FreeSans18pt7b);
  displayTemp(2);
  tft.setFont(&FreeSans9pt7b);
  displayHumid(1);
  tft.setFont(&FreeSans9pt7b);
  displayThermostat(2);

  tft.drawRect(25, 40, 200, 125, ILI9341_LIGHTGREY);
  tft.setTextColor(ILI9341_LIGHTGREY);
  tft.setTextSize(1);
  tft.setCursor(28, 55);
  tft.println("Room");

  tft.drawRect(25, 170, 200, 125, ILI9341_LIGHTGREY);
  tft.setTextColor(ILI9341_LIGHTGREY);
  tft.setTextSize(1);
  tft.setCursor(28, 195);
  tft.println("Thermostat");
}

void displayTime(int fontSize)
{
  if (!currTime.equals(eraseTime))
  {
    tft.setCursor(1, 1);
    tft.fillRect(1, 1, width, fontSize*10, ILI9341_BLACK);
    tft.setTextColor(ILI9341_BLACK);
    tft.setTextSize(fontSize);
    tft.println(eraseTime);
  }
  tft.setCursor(1, 1);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(fontSize);
  tft.println(currTime);
  eraseTime = currTime;
}

void displayTemp(int fontSize)
{
  int X=60; 
  int Y=150;
  int intTemp = currTemp.toInt();
  if (!currTemp.equals(eraseTemp))
  {
    tft.fillRect(X, Y-90, 150, 100, ILI9341_BLUE);
  }
  tft.setCursor(X, Y);
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(fontSize);
  tft.println(intTemp);
  eraseTemp = currTemp;
}

void displayHumid(int fontSize)
{
  int X=50; 
  int Y=80;
  if (!currHumid.equals(eraseHumid))
  {
    tft.fillRect(X, Y-22, 120, 25, ILI9341_BLUE);
  }
  tft.setCursor(X, Y);
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(fontSize);
  tft.print("Humid: ");
  tft.print(currHumid);
  tft.println("%");
  eraseHumid = currHumid;
}

void displayThermostat(int fontSize)
{
  int X=35; 
  int Y=240;
  if (!currThermo.equals(eraseThermo))
  {
    tft.fillRect(0, Y-30, width, 35, ILI9341_BLUE);
    displayThermoButtons();
  }
  tft.setCursor(X, Y);
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(1);
  tft.print("Heat at: ");
  tft.setTextSize(fontSize);
  tft.print(currThermo);
  eraseThermo = currThermo;
}

void displayThermoButtons()
{
   int X = width-75;
   int Y = 180;

   tft.fillRect(X,Y, 50, 50, ILI9341_BLACK);
   tft.fillTriangle(X+5,Y+40, X+25,Y+5, X+45,Y+40, ILI9341_BLUE); 
   Y+=60;
   tft.fillRect(X, Y, 50, 50, ILI9341_BLACK);
   tft.fillTriangle(X+5,Y+10, X+25,Y+45, X+45,Y+10, ILI9341_BLUE);
}

float degCtoF(float deg)
{
  return (deg * 9.0 / 5.0) + 32.0;
}

void publisher() {
  long now = millis();
  if (now - lastMsg > 20000) 
  {
    lastMsg = now;
    Serial.print("Publish message: ");
    float humid = 0;
    float tempC = 0;
    if (dht22.read2(&tempC, &humid, NULL) == 0)
    {
      float tempFl = degCtoF(tempC);
      String val = String(tempFl);
      mqtt.publish("sunroom/temperature", val);
      currTemp=String(tempFl);
      Serial.print("Temp: ");  Serial.println(val);
      val = String(humid);
      mqtt.publish("sunroom/humidity", val);
      currHumid = val;
    }
    else
    {
      Serial.println("Error Reading DHT device");
    }
  }
  if (!currThermo.equals(publishedThermo))
  {
    mqtt.publish("sunroom/thermostat", currThermo);
    publishedThermo = currThermo;
  }
}

void topic1_subscriber(String topic, String message) {
  Serial.println(s+"Message arrived in function 1 ["+topic+"] "+message);
  if (message.equals("on"))
  {
    digitalWrite(BUILTIN_LED, LOW);   // LED on 
    state = "on";
  }
  else
  {
    digitalWrite(BUILTIN_LED, HIGH);
    state="off";
  }
}

/**
 * Read number value of thermostat setting.
 */
void topic2_subscriber(String topic, String message) {
  Serial.println(s+"Message arrived in function 2 ["+topic+"] "+message);
  int thermSetting = message.toInt();
  if ((thermSetting > 55) && (thermSetting < 99))
  {
    thermostatSetting = thermSetting;
    currThermo = String(thermostatSetting);
  }
}

/**
 * Read current time.  (ISO-8601 format: 2016-06-01T21:34:12.629Z)
 */
void topic3_subscriber(String topic, String message) {
  StaticJsonDocument<300> doc;

  DeserializationError error = deserializeJson(doc, message);
  if (error) {
    Serial.println("Parsing failed");
    return;
  }
   // Get the root object in the document
  JsonObject root = doc.as<JsonObject>();

  currTime = root["CDTstring"].as<String>();
  Serial.println(s+"Message arrived in function 3 ["+topic+"] "+currTime);
}

/**
 * Read outside temperature.
 */
void topic4_subscriber(String topic, String message) {
  int temp = message.toInt();
  outsideTemperature = temp;
  currOutsideTemp = String(outsideTemperature);
  
  Serial.println(s+"Message arrived in function 4 ["+topic+"] "+outsideTemperature);
}

The code starts as most of other NodeMCU nodes I have shown. The WiFi SSID and password are there, along with the MQTT broker address. Several globals are defined next. Since the screen tries to update as fast as possible, I have "erase" values. These are the previous settings that the screen will draw in the background color to speed up changes to that section of the screen. I full refresh of the screen over SPI is visible, and should be avoided if possible.

In the setup function, I initialize the ILI9341 display, and read out the current settings. Then I fill the whole screen with BLUE. This is a very saturated blue, and I have thought about changing it to something more muted. In a previous post, I talk about GUIs and muted colors. I then draw the thermostat buttons. This is in another function, and allows me to play with different looking buttons. I've found for this, the basic buttons (triangles inside of squares) are nice enough.

The WiFi and MQTT are started next. I subscribe to 4 topics:
  • heater/light   - The basic on board LED state (on/off) for testing.
  • heater/thermostat - The setting that node-red wants the thermostat at
  • time/ISO-8601 - The time to display
  • outside/farenheight  - The outside temperature (not displayed).
The reconnect publishes 2 messages, if the system ever disconnects from the MQTT broker, current temperature (sunroom/temperature topic) and the current thermostat setting (sunroom/thermostat topic). The thermostat can not only set the temperature to keep the room at, it also listens to Node-Red to know what it wants the thermostat to be set at. This adds complexity to Node-Red, this node is pretty simple, just displaying the last input to the thermostat it got. 

The first thing the loop function checks is if anything is touching the screen. These touch screens are resistive, so they take some pressure to register a touch. For this app, the screens are fine, since the touched area will be quite large. The touch location is something you have to work out depending on the orientation of the screen. The screen touch is roughly positions 0 to 4000 right to left (y direction) and 0-1800 top to bottom (x direction). For my display with the up/down arrows in the lower left, the arrows are 3000 on the left, and 3800 on the right. The up arrow touch area starts at 1100 bottom and goes to 1600 top. The down arrow starts at 500 bottom and goes to 1000 on top. This I derived trial and error with the help of the serial output. 

The loop then calls the publisher to read the current temperature and humidity, and publish these values. Unlike other nodes, I convert the temperature C to temperature F before publishing. Someday I may switch this, but for now, it is done here. The screen is updated after that. All the sections of the screen have their own function to update the display. Using the separate functions should allow a more modular display. I can move the sections by changing the X and Y values in the individual functions. The loop function also has some grey rectangles seperating the sections making the screen a little more finished looking.
When messages come in on the subscribed topics, the values are updated only. The display will update using these values in the next loop() function. There are some safety checks (thermometer cannot be set below 55 or greater than 99) where it is a good idea. 

The time comes in a full json message that needs to be parsed out. For now I have it read only the CDTstring that is encoded in the message. The message is separated out into hours, minutes, seconds and month, day, year. I only send the Central time version, since I can think in local time. 

This node does not turn on the heater anywhere, nor does it compare the thermostat setting to the current temperature. This node only maintains the current settings. Node-Red will turn on the heater if needed, and check the current thermostat value comparing to current temperature. The flow will be handled next time. 

Come back next time.






Wednesday, March 13, 2019

An Input Node

In the last post I mentioned that I would build an input node. This node will be capable of monitoring 2 doors, and the temperature. I have this node out in the garage. There are magnetic reed switches near the two doors and a Dallas DS18B20 temperature sensor near the door.

I built the node with the ESP8266, using the Arduino IDE. There is a "Garage" Flow in my Node-Red environment for monitoring this node. The node code is very similar to the tone generator node, but only sends output. It has the capability to turn on the LED, but there isn't anything in the Flow that actually uses it.

Here is the code:


/**
 * Simple action node. Read sensors and send out status
 * 
 * garage/bigDoor to get status of garage door
 * garage/littleDoor to get status of garage door.
 * garage/outsideTemp to read ds18b20 temperature sensor. 
 * 
 * They are separate devices, but using Node Red can be connected.
 * 
 * 
 * TGB 9/30/18
 */

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <PubSubClientTools.h>
#include <OneWire.h>
#include <DallasTemperature.h>

const char* ssid = "raspi-webgui";
const char* password = "XXXXXXXX";
const char* MQTT_SERVER = "10.3.141.1";

// Data wire is plugged into port D3 on the ESP8266
#define ONE_WIRE_BUS D3

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

//DS18B20
#define ONE_WIRE_BUS D3 //Pin to which is attached a temperature sensor
#define ONE_WIRE_MAX_DEV 15 //The maximum number of devices

WiFiClient espClient;
PubSubClient client(MQTT_SERVER, 1883, espClient);
PubSubClientTools mqtt(client);

/*
ThreadController threadControl = ThreadController();
Thread thread = Thread();
*/

long lastMsg = 0;
int value = 0;
String s = "";

void setup() {
  Serial.begin(115200);

  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output

  pinMode(D1, INPUT_PULLUP);      // big door
  pinMode(D2, INPUT_PULLUP);      // little door
  
  // Connect to WiFi
  Serial.print(s+"Connecting to WiFi: "+ssid+" ");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(s+" connected with IP: "+WiFi.localIP());

  // Connect to MQTT
  Serial.print(s+"Connecting to MQTT: "+MQTT_SERVER+" ... ");
  if (client.connect("GarageControl")) {
    Serial.println("connected");
    digitalWrite(BUILTIN_LED, HIGH); // turn LED off

    mqtt.subscribe("garage/light", topic1_subscriber);
  } else {
    Serial.println(s+"failed, rc="+client.state());
  }

  // Start up the library
  sensors.begin();
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("GarageControl")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic","garageControl Reconnect");
      mqtt.subscribe("garage/light", topic1_subscriber);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" Resetting");
      // Wait 5 seconds before retrying
    }
  }
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  
  client.loop();
  publisher();
}

void publisher() {
  long now = millis();
  if (now - lastMsg > 5000)  // 10 second loop.
  {
    lastMsg = now;
    ++value;
    Serial.println("Publish messages: ");


    int bd = digitalRead(D1);
    int ld = digitalRead(D2);

    String bigDoor="closed";
    String littleDoor="closed";

    if (bd) {
      bigDoor = "open";
    }
    if (ld) {
      littleDoor = "open";
    }
    Serial.print(" bigDoor: ");
    Serial.println(bigDoor);
    Serial.print(" littleDoor: ");
    Serial.println(littleDoor);
            
    mqtt.publish("garage/bigDoor", bigDoor);
    mqtt.publish("garage/littleDoor", littleDoor);

    sensors.requestTemperatures(); // Send the command to get temperatures
    int temp = sensors.getTempCByIndex(0); 
    Serial.print(" temperature: ");
    Serial.println(temp);
    String tempS = String(temp);
    mqtt.publish("garage/outsideTemperature", tempS);
  }
}

void topic1_subscriber(String topic, String message) {
  Serial.println(s+"Message arrived in function 1 ["+topic+"] "+message);
  if (message.equals("on"))
  {
    digitalWrite(BUILTIN_LED, LOW);   // Turn the LED on (Note that LOW is the voltage level
  }
  else
  {
    digitalWrite(BUILTIN_LED, HIGH);
  }
}



The Dallas DS18B20 is a simple to use device that uses the Dallas 1-wire protocol for reading data. Several 1-wire devices can be added to the single pin needed, and only ground and power are needed (actually for some devices power is optional, since the devices can be powered by the serial link). The 1-wire library is all that is needed to used these devices with almost any Arduino system. 

The code starts out initializing the 1-Wire device:


#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port D3 on the ESP8266
#define ONE_WIRE_BUS D3

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);

We need to include the various libraries. I've assigned the serial data to come in on the D5 ESP8266 pin. Again, I am using the 8266's library definition to avoid having to map to the Arduino GPIO pin names. I find it easier to use the library names, since that is what is silk-screen printed on the device. 

The setup function is mostly identical to the tone generator. The only difference is setting the input mode for the two reed switches and starting the sensor monitoring that checks the ds1820. The "INPUT_PULLUP" setting for the D1 and D2 allow me to have the reed switch connected to the D1 or D2 input pins and the other side connected to ground. 

  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output

  pinMode(D1, INPUT_PULLUP);      // big door
  pinMode(D2, INPUT_PULLUP);      // little door
    // Start up the library
  sensors.begin();



Once everything is started, the main loop() just checks the WiFi connection state, and then calls publish(), a new function. The publish function checks the state of the devices and publishes the results. This is the new part that is different from the tone generator node.

void publisher() {
  long now = millis();
  if (now - lastMsg > 5000)  // 10 second loop.
  {
    lastMsg = now;
    ++value;
    Serial.println("Publish messages: ");


    int bd = digitalRead(D1);
    int ld = digitalRead(D2);

    String bigDoor="closed";
    String littleDoor="closed";

    if (bd) {
      bigDoor = "open";
    }
    if (ld) {
      littleDoor = "open";
    }
    Serial.print(" bigDoor: ");
    Serial.println(bigDoor);
    Serial.print(" littleDoor: ");
    Serial.println(littleDoor);
            
    mqtt.publish("garage/bigDoor", bigDoor);
    mqtt.publish("garage/littleDoor", littleDoor);

    sensors.requestTemperatures(); // Send the command to get temperatures
    int temp = sensors.getTempCByIndex(0); 
    Serial.print(" temperature: ");
    Serial.println(temp);
    String tempS = String(temp);
    mqtt.publish("garage/outsideTemperature", tempS);
  }
}

There is a global variable called "lastMsg" that contains the last time the sensors were checked. If the last time was more than 5000 milliseconds (5 seconds) ago, then the sensors will be checked. If less than 5 seconds ago, this function will not do anything. The two door switches are checked. If the switch is closed, the bd/ld values will be 1, otherwise those values will be 1. The MQTT payload is initialized to be "closed". If the big door switch is closed, the "bd" variable will be 1, and the bigDoor payload will be updated to "open". Once both doors have been checked, the mqtt.publish will send out the payload for the two topics, "garage/bigDoor" and "garage/littleDoor".

After the doors have had the MQTT message published, the DS1820 will be checked. The return value from the sensor.getTempCByIndex() will be the temperature in degrees C.  The temperature value will be published on the "garage/outsideTemperature" topic.


A couple thoughts about MQTT


MQTT brokers can maintain state for various topics. There are good things and bad things about maintaining state. If the state is maintained, the last payload will be kept. If a node dies, there may not be a way to determine when the last time the node reported the state. For temperature, a maintained state may not good, since it may say the value is comfortably warm, when the real temperature is very cold or hot.

Node-Red is generally stateless. That is, unless someone publishes an MQTT message, node-red will not check for state of an item. If node-red is restarted, the node states will not be known, until the next time they publish their state.

I generally have each node publish state every few seconds. Even if the state hasn't changed, I assume that node-red doesn't know. It does mean there will be messages going on the network, that may be unnecessary. MQTT messages are small, and even 802.11n WiFi is fast enough for thousands of messages a second.


Garage Flow


The garage flow shows some new concepts. There will be some programming, but should be easy to understand.




Starting on the left top, there are 2 MQTT input nodes. Both of these will use the "localhost" MQTT server. Each will be subscribed to unique MQTT topics. The littleDoor node will be subscribed to the "garage/littleDoor" topic defined above, and the bigDoor node will be subscribed to the bigDoor MQTT topic. Both MQTT nodes are connected to debug nodes, allowing me to see if the message is being received when something comes in. A third MQTT input node is subscribed to the "garage/outsideTemperature"

The temperature node is connected to another debug node, as well as the cDegToF node. The cDegToF node converts an MQTT payload containing a temperature in Celsius and converts that to a payload in degrees Fahrenheit. The output of cDegToF node is connected to a debug node, and a MQTT output node. The MQTT output node publishes the Fahrenheit temperature on the topic "outside/farenheight". Other nodes can subscribe to "garage/outsideTemperature" or "outside/farenheight" depending on their needs.

The cDegToF node is a function node (note the F on the left). This means it is a small program. Nodes can be programmed in JavaScript. The code looks like:



The msg.payload that comes in is converted using the F=9/5C+32 calculation. To edit a function, simply double click on the node after dragging it to the work area. The above dialog will appear.

The mode advanced function node in this flow is the "Door Open Alarm" node. As you can see from the cDegToF node above, functions are easy with one message coming in. For this node, there are two messages that can cause the alarm to sound. Either big door or little door messages will cause the alarm to come on. If one door is open, and the other closed, the function may not know if anything is open, if the node only looks at all msg.payloads.

I need to introduce context. State of nodes in Node-Red have several contexts. The node has a context for the instant it is running, there is a flow based context, that all nodes within a flow can know about, and there is the global node-red context that all flows share.

Code for the Door Open Alarm:

// if either door is open, after 11pm and before 6am 
// sound an alarm.

context.hour = context.global.hour || 12;
context.bigDoor = context.bigDoor || 'closed';
context.littleDoor = context.littleDoor || 'closed';

if (msg.topic === 'garage/bigDoor') {
  context.bigDoor = msg.payload;
} else if (msg.topic === 'garage/littleDoor') {
  context.littleDoor = msg.payload;
} else if (msg.topic === 'time/ISO-8601') {
    context.hour = msg.payload.hour;
}

// check the status, to know if alarm should be on
if (((context.bigDoor === 'open') || (context.littleDoor === 'open')) &&
    (context.hour > 22) || (context.hour < 6)) {
    // msg.payload = "on " + context.therm + " " + context.temp;
    msg.payload = 'on';
} else {
    msg.payload = 'off';
}
return msg;


For the "Door Open Alarm" node, I maintain state in a the node's context. The context is initialized to the current state or if unknown, to the 'closed' state. The time is global context that I have in another node (I'll talk about that in another post).

If a message comes in on a topic that the node knows about (garage big/little door), then the context for that door is updated to the current state. If either door is open during the alarm time, a new message is published containing the payload "on". This node doesn't know the recipient of the payload, only the payload.

That last node in this flow is the MQTT output node that will send the alarm payload to the tone generator. It will publish the payload passed here on the "home/alarm" topic.

This may be lots to digest. Hopefully it will give some ideas of the capabilities of a RaspberryPi and some 8266's.

More to come.