Showing posts with label Dashboard. Show all posts
Showing posts with label Dashboard. Show all posts

Monday, April 8, 2019

Heater Flow

As promised, I am going to present the flow used for the thermostat node. This flow has a nice dashboard, so it can be controlled from my phone, or from the thermostat, or any other flow input.

The flow:



We can start from the center, the "Check Thermostat" function node. This function take inputs from the heater/state, sunroom/thermostat and sunroom/temperature MQTT topics. The heater/state is published by the nodeMCU attached to the heater, and the payload is either "on" or "off" depending on the state of the heater. The sunroom/thermostat and sunroom/temperature topics are from the thermostat nodeMCU from the last post. The sunroom/temperature payload is a number indicating the temperature measured by the DHT-22 sensor converted to degrees Fahrenheit. The sunroom/thermostat payload is a number representing the setting the thermostat was last set to.

 // if temp is less than thermostat turn on heater.
flow.temp = flow.temp || 0.0;
flow.therm = flow.therm || 70.0;
 if (msg.topic === 'sunroom/thermostat') {
  flow.therm = parseFloat(msg.payload);
} else if (msg.topic === 'sunroom/temperature') {
  flow.temp = parseFloat(msg.payload);
} else if (msg.topic === 'heater/state') {
    flow.state = msg.payload;
}
// add a dead band, if on turn off 1 degree late
if (flow.temp < flow.therm) {
    // msg.payload = "on " + flow.therm + " " + flow.temp;
    msg.payload = "on"
} else if ((flow.temp > flow.therm) &&
           (flow.state === 'on')) {
    //msg.payload = "off " + flow.therm + " " + flow.temp;
    msg.payload = "off";
}
return msg;

There is only one thing output from this, and it indicates if this wants the heater "on" or "off". The output of this is determined if the temperature is less than the thermostat (flow.therm) the payload is sent as "on". If the temperature is greater than the the thermostat setting, and the heater is currently on (flow.state from the heater/state topic) then the payload is set to off. If the temperature matches, it does nothing.

Sometimes the sensor is right on the edge 72degrees/73degrees and it jumps back and forth, so the heater turns on and off frequently. Leaving the sensor to not do anything if the temperature matches the thermostat setting allows the heater to have some hysteresis, and take maybe a minute to change states.

The output of the check thermostat goes to the heater/gas topic, listened to by the nodeMCU connected to the heater itself. There is also a debug output and it is handy to have while debugging things. The function code allows additional information in the payload to see what the current temperature and thermometer settings are when changing state, but that will only work with the debug turned on.

The blue boxes are for the dashboard. The current temperature is a gauge_ui element, and is at the top.


The slider under the gauge is the current thermostat. This is a pass through UI element. It will adjust based on messages heard on the "sunroom/thermostat" MQTT topic, and will send values through to the heater thermostat topic.

The settings look like:



There is also a text output that will show the current thermostat setting. The UI is a relative bar, and no other indicators. I typically add the text items so I know what the slider is pointing at.

I have two switches, both pass through. The current heater state is indicated by values coming in on the heater/state MQTT topic. Clicking this button on or off will output the state to the heater/gas MQTT topic turning the heater on or off (manually). The light on/off is similar sending on the heater/light topic and indicating the debug on/off buttons.

There is a filler UI element. Without the filler the UI sometimes randomly wrapped funny.
That is something to play with to get the widgets. to align properly.



A keen eye might notice a connector widget in the middle of the flow that I didn't mention. This is for the voice input. Yes, from my phone, a Google assistant or other device I can control my house by voice. That is for a future post.



Monday, April 1, 2019

Something Didn't Work

I am writing about another input node. I have a water softener in my house, it goes with the well, since the water is hard, and there is a bit of iron. Every month or so I need to put about 200lbs of salt in the tank. The tank is large and opaque, so I don't actually end up opening the tank, to check on it as often as I should.

I read a story on hackaday about someone who built a water softener monitor. I thought I could do something similar. I thought I could use a simple HC-SR04 ultrasonic module to measure how far down the salt was from the sensor. Connecting one of these sonar modules is dirt simple, and then enclosing the sensor in a 3D printed box would allow measuring to the salt.




Everything seemed to work, the first month. The device is in the tank, I have a USB charger cable going out the the slot in the tank where the brine flushes, so no real modifications. The sensor box is zip tied to the larger tube in the tank. It measures a distance, that seems consistent even though the surface isn't uniform.

To get familiar with the setup, I had the node email me the measurement every night about 11PM. This seemed to go great. A couple measurements were way out of spec, but mostly it was going down a few centimeters every couple days. The email reminded me to check on the tank a couple times a week. After it got down to a level where I thought I should fill it, I changed the code to only email me when it got more than a few centimeters of that level.

Here is the code for the node:

/** 
 *  Sensor that measures salt level in water softener, and sends result over MQTT (node-red)
 *    Sonar distance measured at regular intervals sent over MQTT.
 *
 * The board selected in the IDE is WeMos D1 R2 & Mini. Standard everything 
 * 
 * 
 * TGB 2/20/19
 */

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

// Update these with values suitable for your network.

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

#define echoPin 12 // Echo Pin
#define trigPin 13 // Trigger Pin

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

long lastMsg = 0;
char msg[50];
int value = 0;
String s = "";

int maximumRange = 200; // Maximum range needed
int minimumRange = 0; // Minimum range needed
long duration, distance; // Duration used to calculate distance

void setup() 
{
  // put your setup code here, to run once:
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  Serial.begin(115200);
  setup_wifi();


  // Connect to MQTT
  Serial.print(s+"Connecting to MQTT: "+MQTT_SERVER+" ... ");
  if (client.connect("ESP8266Client")) {
    Serial.println("connected");
  } else {
    Serial.println(s+"failed, rc="+client.state());
  }
  
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

  setup_wifi();
}

void setup_wifi() 
{
  // 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());
   digitalWrite(BUILTIN_LED, HIGH); // turn LED off

  // Connect to MQTT
  Serial.print(s+"Connecting to MQTT: "+MQTT_SERVER+" ... ");
  if (client.connect("SaltReader")) {
    Serial.println("connected");
  } 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("SaltReader")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic","saltReader Restart");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      // Wait 5 seconds before retrying
    }
  }
}


//////////////////////////////////////////////////////////////////////////
////////// Sonar Sample //////////////////////////////////////////////////
long sample() 
{
/* The following trigPin/echoPin cycle is used to determine the
 distance of the nearest object by bouncing soundwaves off of it. */ 
 digitalWrite(trigPin, LOW); 
 delayMicroseconds(2); 

 digitalWrite(trigPin, HIGH);
 delayMicroseconds(10); 
 
 digitalWrite(trigPin, LOW);
 duration = pulseIn(echoPin, HIGH);
 
 //Calculate the distance (in cm) based on the speed of sound.
 distance = duration/58.2;
 
 if (distance >= maximumRange || distance <= minimumRange)
 {
   /* Send a negative number to computer and Turn LED ON 
   to indicate "out of range" */
   Serial.println("-1");
 }
 else
 {
   /* Send the distance to the computer using Serial protocol, and
   turn LED OFF to indicate successful reading. */
   Serial.println(distance);
 }
 return distance;
}


void publisher() {
  long now = millis();
  if (now - lastMsg > 30000) {  // 30 seconds
    lastMsg = now;
    long distance=sample();
    ++value;
    
    snprintf (msg, 75, "%ld", distance);
    Serial.print("Publish message: ");
    Serial.println(msg);
    mqtt.publish("salt/level", msg);
  }
}

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


This node sends a MQTT message on the "salt/level" topic with the measured distance as the payload. The time in the loop is 30 seconds, so twice a minute this will publish the message. That is all it needs to do.

The flow in Node Red is:



The receiver listens for messages on the "salt/level" topic. The output of that node goes to a debug node, an email formatter function and two dashboard items, slider and text. The email formatter also goes to an email node and another debug node.

The function that formats the email is:


context.saltLevel = 0;

if (msg.topic === 'salt/level') {
  context.saltLevel = msg.payload;
}


if ((context.global.minute == 32) && 
    (context.global.hour == 23) && 
    (context.saltLevel > 30))
{
    msg.payload = "Subject: Salt Level\n salt level "+context.saltLevel;
    return msg;  
}

msg.payload = context.global.hour+":"+context.global.minute 

The node context maintains the current salt level. I have another flow that outputs time, that I promise to show soon. The level is compared to a level of 30 centimeters, and if greater formats the email and sends the mail if the time is 23:32. Since the MQTT message is published every 30 seconds, I usually get 2 emails with the salt level. For me that is fine, it allows cross checking.

For the Dashboard, I have a "Salt Tank" group. This creates a tab just for the salt tank. The tab display is equally simple, and quick to determine the status:


Setting up email


If you want to use the email output node for alerts and such, and you use gmail, I suggest sending from some rarely used email address. Get a sub-gmail address to send from and use that as your send address. For the most part the node is easy to configure.


Gmail is very well protected. Google does a good job of filtering spam both from gmail senders and gmail receivers. To get around some of these protections, you need to turn down the safety of the gmail account that node-red will send from. In advanced settings set the sender to enable less secure access:



What is wrong


The dashboard is showing 38cm of salt. It should be sending email, and it does, every night. The trouble is, the salt tank is still about 1/3 full. Actually, the measurement is no longer consistent. A few times, this sent me email about the level being 109cm, and other times it sends email that the tank is at 77cm.

The basic fault might be that the box is in a high concentration of potentially moist salt dust. I am suspecting some form of corrosion, or possibly something conducting across something it shouldn't be.

Going back to the hackaday example, that person used a LIDAR instead of sonar. Lidar, being optical should allow the sensor box to be sealed, hopefully preventing humid salt from damaging the system.

For now the system mostly works. It is reminding me to look in the tub every few days, and we haven't run out of salt two months in a row.



Tuesday, March 26, 2019

Other Inputs

The beauty of Node-Red is it's flexibility. If you have looked on the list of nodes, you may have noticed there are several inputs besides MQTT. Some of the inputs include GPIO, TCP, UDP and Web sockets. The GPIO inputs are the pins on the RaspberryPI. Sockets need an external server.

In previous posts, I mentioned using a DS18B20 temperature sensor. This sensor works great, but has a limitation in where I put it. The garage node I connected it to is physically too close to the house, so the temperature it reads is about 15-20 degrees warmer than it really is. The house is radiating heat, and the sensor is picking up the heat.

To get around this, I could have run a longer wire and put the sensor out in the yard farther. I could have built a temperature node, and mounted it in the yard. Or I could get the input from another source. Running wires farther out in the yard becomes a challenge since people walk in the yard, burying the cable would be possible, but having the sensor close to the ground will measure the radiated ground temperature instead of the air temperature. Getting power out to a remote sensor is a problem, wires would be similar to the remote sensor, solar would be the best, but that adds complexity.

I chose to get the temperature from an external source. I am using openweathermap. There is a node red openweathermap node available. Install the node like the previous post, using the top right hamburger menu, select manage palette, install tab. Enter openweathermap in the search, and press install. A new section will be created in the left node selector section called "weather".

Follow the instructions in the openweathermap Usage section. To use the node, the openweathermap people want to control the volume of queries made against their servers, so they require users to get a key. The normal node-red usage pattern is pretty light against their servers, so it seems they tolerate this volume of use. Follow the directions on the appid page, and check your email for the appid needed.

Once you have your appid, start a new flow. The first node should be openweathermap in node (the one with only a connector on the right). Drag the node to your flow. Double click on the new node, to get to the edit dialog. Enter your appid in the top box, set your node to get the "Current Weather For" and select coordinates for location. (I couldn't get the city, country to work for my location). Give your location a name. Press done.

Enter a debug node next, and connect the two nodes. Press deploy. In the debug tab on the right, the payload will display. The current weather is pretty detailed, including temperature, cloud cover, and other items. The payload looks like:
{ weather: "Clouds",
   detail: "overcast clouds",
   icon: "04n",
   tempk: 281.56,
   tempc: 8.4,
   temp_maxc: 9.4,
   temp_minc: 7.7,
   humidity: 57,
   pressure: 1023,
   maxtemp: 282.59,
   mintemp: 280.93,
   windspeed: 4.1,
   winddirection: 200,
   location: "Eden Prairie",
   sunrise: 1553601964,
   sunset: 1553646776,
   clouds: 90,
  description: "The weather in Eden Prairie at coordinates: 44.84, -93.47 is Clouds (overcast clouds)."}

The openweathermap page has details of things like the icon. The temperature in Kelvin isn't useful to most people, but there are simple conversions to Fahrenheit. Some people can work in millibars but most people, in the US at least, think in terms of inches of mercury for the barometric pressure. 

For me, I just needed the temperature. So I created a function that can strip out just the temperature. The Javascript for this function is:

context.tempC = msg.payload.tempc;
msg.payload = context.tempC;
return msg;

The function reads the messages payload.tempc, and creates a new message that only contains that in the payload. The function can be modified to return any or all of the payload desired. If the dashboard was to show clouds and summary that can be done as well. 

I pass that temperature to a copy of the degCtoF that I wrote for the garage node already, and then I send that value out to the graph widget in the garage tab of the UI. The flow looks like:


This will show the nice graph of the last hour or so, without any database or special storage. 

Many external sites work on the token userid concept. It is related to your account on the server side. Since you aren't sending your name, login or anything special it keeps prying eyes out as that value goes across the internet. Using SSL should ensure the value isn't easily seen either. 

Next time, I'll show another external service, allowing voice control of node red. 

Come on back.

Monday, March 18, 2019

More Node-Red


Node-Red is amazingly powerful, and flexible. Maybe in a future post, I set some recipies, including recurring timers, Google Assistant integration and notifications.



Dedicated System Setup


One advantage of using Node-Red in your home is you don't have to worry about security too much. You aren't publishing your daily habits to anyone in the cloud. One disadvantage, you need to give the system all it's smarts. It doesn't mean you can't integrate with the cloud, but it doesn't happen automatically.

Using a RaspberryPI as the dedicated controller will allow the 'PI to be the WiFi hub, DHCP server, MQTT server agent, and provide all the flows without worrying about tying up another machine (home office computer), or having that system walk out the door (laptop).

In my example ESP8266 nodes, I always hard coded the IP address of the server. That works great, since the RaspberryPI is providing all the DHCP and WiFi for the various nodes in the house. If I want to move the system somewhere else, I don't have to worry about reprogramming the nodes server IP address.



The above diagram shows how the home WiFi is separate from the IoT WiFi. To connect to the rest of the internet, a wired connection can be made to the home Cable Mode. Connecting the RaspberryPI to the internet will allow updates and other features to be easier. It doesn't have to be connected to the internet, it is up to you. Since the RaspberryPi connected to the modem will NAT all internet traffic, unless routes are set up in the modem, the RaspberryPI is not visible from the outside network.

For my network, I run the IoT network on WiFi Channel 11 since my home network is on channel 1. Keeping them on separate channels should prevent IoT traffic from being picked up on the home WiFi devices theoretically making things faster. Radio is radio, so if the devices are close enough, they may interfere anyway. (For some reason the Sony PS4 seems to interfere with WiFi devices near it, so if the kids bring the PS4 into the sun room, the IoT devices perform poorly).

My WiFi config file on the RaspberryPI looks like:

pi@raspberrypi:~ $ cat /etc/hostapd/hostapd.conf
driver=nl80211
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
beacon_int=100
auth_algs=1
wpa_key_mgmt=WPA-PSK
ssid=raspi-webgui
channel=11
wpa=2
hw_mode=g
wpa_passphrase=XXXXXXXX
interface=wlan0
#wpa_pairwise=TKIP
rsn_pairwise=CCMP
country_code=US


The dns config file is the default, really no changes. 

Node-Red Dashboard

This will be a gentle introduction, just barely covering what it is capable of. This is really the big win for Node-Red. 

First there you need to install the dashboard nodes. From the Node-Red main window, hamburger menu on the far right. Left click on that to bring up the configuration menu. Select the "Manage Palette". A dialog with two tabs will appear, select the "Install" tab. Type in "dashboard" in the "Filter nodes box" There will be a list of dashboard items available. For now, we only want the default "node-red-dashboard". Select "install" and give it a couple minutes. After the update completes, scroll toward the bottom of the nodes on the left. 

There will be a section of nodes labled "dashboard" with nodes like "button", "slider", "dropdown", etc. These items can be added to a flow, and used to set settings or view values available in the flow. The updated garage flow with simple outputs for the garage state and temperature graph looks like:

The light blue nodes are dashboard nodes. The garage door ones are the current state as text (open/closed) and the temperature is a graph showing the current trend, with degrees in Fahrenheit. To set the node display, use the payload to get the value:


Create a group, as you wish. For my house these nodes are in the Garage, so I created a Garage group. I also have a Sunroom Group and a Basement group. They are all part of the House, as a hidden group. The payload is enclosed in "{{" and "}}" I'll expand on why that later. Alignment can be changed as desired, the top row above is left, center and right aligned. The next is fill, and the bottom right is label over value. The look that this allows is:


The temperature trend for the last 20 minutes or so is shown, getting colder. Both doors are closed. Pretty simple, but a quick look and you can see it. Note the URL. I chose the home network, since my RaspberryPI is plugged into the home network. Node Red is running on port 1880, the default. But after that there is a /ui, so I don't see the whole Node Red flow builder, but just the dashboard. 

How about if you want to see this on your phone? No problem, that is built in:

Enter the same url (like 192.168.0.35:1880/ui in chrome, safari or whatever browser you prefer. All the dashboard features are there. No special apps, or knowledge of programming needed. 

There is so much more that can be done in Node-Red. Start playing, and you should find ideas that I never thought of. More next time.