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.



No comments:

Post a Comment