Node-Red, Pi and interfacing Adafruit hardware

This is a special forum devoted to educators using Adafruit and Arduino products for teaching.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

So, things have changed....

I had thought that the SGP30 was a good tool, it was not the tool I needed. I substituted the SCD30 breakout, but then it added some "complications":
  • 1. I would prefer to stick as close to the Adafruit python code as much as possible.
    2. This meant I had to get rid of the "Python3 Node" approach, and hopefully not modifying their libraries.
So, the question was, how to do it? There is a Node-Red Daemon which does redirect stdout (the output from Python's print command), stdin (keyboard input) and stderr (error and debugger output). It actually took me some time to use it, but now I love it.

So, once you have the SCD30 working properly, the dashboard, would look like this:
Capture6.JPG
Capture6.JPG (57.43 KiB) Viewed 4780 times
The flow looks like this:
Capture7.JPG
Capture7.JPG (76.92 KiB) Viewed 4780 times
The Daemon noe is configured like this:
Capture8.JPG
Capture8.JPG (50.09 KiB) Viewed 4780 times
The SCD30_test.py code is:

Code: Select all

The SCD30 code (requiring no modification to the Adafruit libraries) is:
# SPDX-FileCopyrightText: 2020 by Bryan Siepert, written for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense
import time
import board
import busio
import adafruit_scd30
from adafruit_extended_bus import ExtendedI2C as I2C
 
# Create library object using our Bus I2C port
# i2c = busio.I2C(board.SCL, board.SDA)
i2c = I2C(3) 
scd = adafruit_scd30.SCD30(i2c)

# Report the temp offset
# print("Temp offset ", scd.temperature_offset)
# Adjust the Temp down by 2.1 degree C
# NewOffset = 1.6
# scd.temperature_offset = NewOffset

# Set the forced_recalibration_reference
# scd.forced_recalibration_reference = 409

while True:
    # since the measurement interval is long (2+ seconds) we check for new data before reading
    # the values, to ensure current readings.
    if scd.data_available:
        CO2 = str(scd.CO2)
        print("CO2|"+CO2)
        print("Temp|"+str(scd.temperature))
        print("Humid|"+str(scd.relative_humidity))
        with open("/home/pi/SCD30.txt", 'w') as file:
          file.write(CO2 + "\n")
    time.sleep(0.5)
So, the next challenge is to turn the output, which I "standardized" to the form of <topic>|<payload", a string seperated with the unix pipe character -> "|"

I did that with the "working" Python3 function node code, it is *such* an easy hack that provides a ton of utility.

Fixes for python 3.5 by zewelor · Pull Request #3 · dejanrizvan/node-red-contrib-python3-function · GitHub
Replace contents of
/home/pi/.node-red/node_modules/node-red-contrib-python3-function/lib/python3-function.js
Line 116:
return cls(**json.loads(json_string.decode('utf-8')))
Line 158:
msg = json.loads(raw_msg.decode('utf-8'))

So, now I get a delimited string into the Python script and can turn it into a proper Python dictionary with the stdOut to Msg node (all python) :

Code: Select all

# Released under Beer License
# Clear any leading or trailing whitespace
OrigStr = msg['payload']
# get rid of carriage returnsand leading whitespace
OrigStr = OrigStr.lstrip()
OrigStr = OrigStr.replace('\r','')
if len(OrigStr) >6:
  # Save only the first line
  TmpStr = OrigStr.partition("\n")[0]
  TmpStr = TmpStr.replace('\n','')
  # Turn it into an array
  TmpArray = TmpStr.split("|")
  try:
    # Form the msg object
    msg = {
      "topic": TmpArray[0],
      "payload": round(float(TmpArray[1]),2)
    }
    # return the msg
    return [msg,None]
  except:
    # Form the msg object
    msg = {
      "topic": "Debug",
      "payload": OrigStr
    }
    # return the msg
    return [None,msg]
else:
  # return the msg
  return [None,None]

User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

So, now that this is into messages, we can direct the data to the appropriate nodes for processing. We do this with a Switch node we call "Split MSG", configured as such:
CaptureA.JPG
CaptureA.JPG (67.03 KiB) Viewed 4776 times
And after that, it's what you make it in Node Red...

User avatar
Franklin97355
 
Posts: 23910
Joined: Mon Apr 21, 2008 2:33 pm

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by Franklin97355 »

@iwbaxter thanks for all the work you have been doing, it's fantastic. Contact [email protected] ...attn phil and we will see if we can reward you for your work.

User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

Well, another weekend, and another improvement. I decided to tackle the SGP30/BME280 combination. The BME280, I took back to the orginal from Adafruit's library. I reversed one change to the SGP30 Library (putting back the commented iaq_init in the class initialization code), and left these additions:

Code: Select all

    @property
    # pylint: disable=invalid-name
    def featureset(self):
        """SGP30 Featureset"""
        return self.get_iaq_featureset()
        
@property
    # pylint: disable=invalid-name
    def inceptive_baseline(self):
        """SGP30 Inceptive TVOC Baseline"""
        return self.get_iaq_inceptive_baseline()[0]

    @property
    # pylint: disable=invalid-name
    def measure(self):
        """Get both TVOC and CO2"""
        return self.iaq_measure()         

    @property
    # pylint: disable=invalid-name
    def baselines(self):
        """TVOC and Carbon Dioxide Equivalent baseline values"""
        return self.get_iaq_baseline()

    def get_iaq_featureset(self):
        """Get the SGP30 featureset"""
        # name, command, signals, delay
        return self._run_profile(["iaq_featureset", [0x20, 0x2F], 1, 0.01])

    def get_iaq_inceptive_baseline(self):
        """Retreive the IAQ inceptive baseline for TVOC"""
        # name, command, signals, delay
        return self._run_profile(["iaq_get_inceptive_baseline", [0x20, 0xb3], 1, 0.01])

    def set_iaq_TVOC_baseline(self, TVOC):  # pylint: disable=invalid-name
        """Set the baseline for TVOC"""
        if TVOC == 0:
            raise RuntimeError("Invalid baseline")
        buffer = []
        for value in [TVOC]:
            arr = [value >> 8, value & 0xFF]
            arr.append(self._generate_crc(arr))
            buffer += arr
        self._run_profile(["iaq_set_TVOC_baseline", [0x20, 0x77] + buffer, 0, 0.01])
I then removed the Python function node and created a Node-Red Node Daemon node:
Capture1.JPG
Capture1.JPG (47.36 KiB) Viewed 4138 times
The code for the SGP30_BME280_test.py script is:

Code: Select all

# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT
""" Example for using the SGP30 and BME280 with CircuitPython and the Adafruit library"""
import os
from os import path
import time
import board
import busio
import math 
from adafruit_extended_bus import ExtendedI2C as I2C
import adafruit_sgp30
import adafruit_bme280

from datetime import datetime
# Create library object on our I2C port
# Device is /dev/i2c-3
i2c = I2C(3)
sgp30 = adafruit_sgp30.Adafruit_SGP30(i2c)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
##
# INTERACTIVE - Uncomment code if running on command line
#
# Get the featureset
#print("Featureset is: " + str(sgp30.featureset))
# Get the inceptive Baseline
#Inceptive = sgp30.inceptive_baseline
#print("Inceptive TVOC baseline is: " + hex(Inceptive))
##
#
#
# Get the SGP serial number and turn it into a string
SGPSerial = "-".join([hex(i) for i in sgp30.serial]).replace("0x","")
# Create the Serial specific Baseline file name
BaseFile = "/home/pi/SGP30_" + SGPSerial + ".txt"
# Check for and load last baseline date and values
if path.exists(BaseFile):
  with open(BaseFile, 'r') as file:
    lst = [line.rstrip() for line in file]
  # Last time we started a calibration sequence
  LastCalibration = datetime.strptime(lst[0], '%Y-%m-%d %H:%M:%S')
  # Last time was saved the baselines
  LastSave = datetime.strptime(lst[1], '%Y-%m-%d %H:%M:%S')
  # Load the existing baseline values
  eCO2Base = int(lst[2],16)
  TVOCBase = int(lst[3],16)
else:
  # Set these to invalid dates
  LastInit = datetime.strptime("1970-01-01 00:00:00", '%Y-%m-%d %H:%M:%S')
  LastCalibration = datetime.strptime("1970-01-01 00:00:00", '%Y-%m-%d %H:%M:%S')
  LastSave = datetime.strptime("1970-01-01 00:00:00", '%Y-%m-%d %H:%M:%S')
  # Set invalid baseline values
  eCO2Base = 0
  TVOCBase = 0
# Check for an old baseline
BaselineOld = datetime.now() - LastSave
# Divide by 24 Hours * 60 minutes * 60 seconds to get days
BaselineOld = BaselineOld.total_seconds() / (24*60*60)
# 7 or more days old is not valid
if BaselineOld < 7:
  # Initialize the SGP30
  sgp30.iaq_init()
  # Set the Baseline
  sgp30.set_iaq_baseline(eCO2Base, TVOCBase)
  # Do not reset the Last Calibration Date
else:
  # The SGP is going to calibrate
  LastCalibration = datetime.now()
# Begin processing
elapsed_sec = 0
while True:
  # Read the sensors
  thistemp = bme280.temperature
  thisrh = bme280.relative_humidity
  thispressure = bme280.pressure
  thisalt  = bme280.altitude
  # calc the absolute humidity in g/mg3
  """https://carnotcycle.BANNED.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/"""
  absTemperature = thistemp + 273.15;
  absHumidity = 6.112;
  absHumidity *= math.exp((17.67 * thistemp) / (243.5 + thistemp));
  absHumidity *= thisrh;
  absHumidity *= 2.1674;
  absHumidity /= absTemperature;
  # According to the chip manual and all sources, this must be g/m3
  sgp30.set_iaq_humidity(absHumidity)
  # Test to see if we can read both at the same time
  readings = sgp30.measure
  eCO2 = readings[0]
  TVOC = readings[1]
  # Every twentieth iteration, output the Baselines
  if elapsed_sec > 20:
    elapsed_sec = 0
    # Get how many hours since the last calibration
    CalibOld = datetime.now() - LastCalibration
    # Divide by 60 minutes * 60 seconds to get hours
    CalibOld = CalibOld.total_seconds() / 3600    
    # Only check and save the Baseline if it has been 12 hours since the last calibration
    if CalibOld > 12:
      # Get how many minutes since the last baseline save
      BaselineOld = datetime.now() - LastSave
      # Divide by 60 seconds to get minutes
      BaselineOld = BaselineOld.total_seconds() / 60      
      # Only retrieve and save if it's been an hour
      if BaselineOld > 59:
        # get the existing baseline from the SGP30
        readings = sgp30.baselines
        eCO2Base = readings[0]
        TVOCBase = readings[1]
        LastSave = datetime.now()
        with open(BaseFile, 'w') as file:
          file.write(datetime.strftime(LastCalibration, '%Y-%m-%d %H:%M:%S') + "\n")
          file.write(datetime.strftime(LastSave, '%Y-%m-%d %H:%M:%S') + "\n")
          file.write(hex(eCO2Base) + "\n")
          file.write(hex(TVOCBase) + "\n")
  # Output what we have
  print("Temp|"+str(thistemp))
  time.sleep(.1)
  print("RelHum|"+str(thisrh))
  time.sleep(.1)
  print("AbsHum|"+str(absHumidity))
  time.sleep(.1)
  print("Alt|"+str(thisalt))
  time.sleep(.1)
  print("Press|"+str(thispressure))
  time.sleep(.1)
  print("eCO2|"+str(eCO2))
  time.sleep(.1)
  print("TVOC|"+str(TVOC))
  # Sleep a minimum of 1 second
  time.sleep(.5)
  # Increment our counter
  elapsed_sec += 1
Now, about this code - it is set to use the device(s) on I2C Bus 3, so you may want to change that if you use the code.

There is a .1 second sleep between output so that the lines do not "bleed" together. I found that to be a problem, I may have to alter the SCD30 code for the same issue, although I have not detected it to date.

The other thing is about baselines and calibration. If no baseline, or an old baseline file exists at the time of the deployment of the flow (/home/pi/SGP30_<Serial>.txt) then the SGP30 will start auto calibration. This will take about 12 hours to complete and should be performed in a "clean" environment where there is no added CO2, etc. Once the calibration is complete, the script will update the Baselines every hour or so.

The structure of this file is simple:
Line 1 - Date and time of last calibration start
Line 2 - Date and time of last save
Line 3 - eCO2 Baseline
Line 4 - TVOC Baseline

Again, I added my little "friend", the Python3 function node "2 msg" with the following code:

Code: Select all

# Released under Beer License
# Clear any leading or trailing whitespace
OrigStr = msg['payload']
# get rid of carriage returnsand leading whitespace
OrigStr = OrigStr.lstrip()
OrigStr = OrigStr.replace('\r','')
if len(OrigStr) >6:
  # Save only the first line
  TmpStr = OrigStr.partition("\n")[0]
  TmpStr = TmpStr.replace('\n','')
  # Turn it into an array
  TmpArray = TmpStr.split("|")
  try:
    # Form the msg object
    msg = {
      "topic": TmpArray[0],
      "payload": round(float(TmpArray[1]),2)
    }
    # return the msg
    return [msg,None]
  except:
    # Form the msg object
    msg = {
      "topic": "Debug",
      "payload": OrigStr
    }
    # return the msg
    return [None,msg]
else:
  # return the msg
  return [None,None]
And of course, followed it with the "Switch" node:
Capture2.JPG
Capture2.JPG (36.78 KiB) Viewed 4138 times
This is the resulting dashboard (I had to hide the soil sensors):
Capture3.JPG
Capture3.JPG (53.58 KiB) Viewed 4138 times

User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

So, continuing, the flow looks like this:
Capture4.JPG
Capture4.JPG (83.47 KiB) Viewed 4138 times
And here is the JSON so you can import it all into Node-Red:

Code: Select all

[{"id":"6e4792e2.d1413c","type":"debug","z":"394fcece.136712","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":420,"wires":[]},{"id":"9948d897.00ab78","type":"catch","z":"394fcece.136712","name":"","scope":null,"uncaught":false,"x":740,"y":420,"wires":[["6e4792e2.d1413c"]]},{"id":"336a5d64.79aa02","type":"ui_gauge","z":"394fcece.136712","name":"","group":"54991333.36cc3c","order":3,"width":3,"height":3,"gtype":"gage","title":"Temp","label":"C","format":"{{value}}","min":"20","max":"30","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"25","seg2":"27","x":730,"y":100,"wires":[]},{"id":"8d5a4f65.5ecd4","type":"ui_gauge","z":"394fcece.136712","name":"","group":"54991333.36cc3c","order":1,"width":3,"height":3,"gtype":"gage","title":"Humid","label":"","format":"{{value}}","min":"20","max":"60","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"31","seg2":"52","x":730,"y":220,"wires":[]},{"id":"b5770088.dc9d3","type":"ui_gauge","z":"394fcece.136712","name":"","group":"df17b484.5087f8","order":1,"width":3,"height":3,"gtype":"gage","title":"CO2 PPM","label":"ppm","format":"{{value}}","min":"400","max":"2000","colors":["#e6e600","#00b500","#e60000"],"seg1":"900","seg2":"1700","x":740,"y":340,"wires":[]},{"id":"7a68b6ba.b07c68","type":"smooth","z":"394fcece.136712","name":"Calc Min","property":"payload","action":"min","count":"360","round":"2","mult":"single","reduce":false,"x":740,"y":60,"wires":[["fa5ce8a2.520e98"]]},{"id":"fa5ce8a2.520e98","type":"change","z":"394fcece.136712","name":"Min","rules":[{"t":"change","p":"topic","pt":"msg","from":"Temp","fromt":"str","to":"Min","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":60,"wires":[["b570ad02.c65f4"]]},{"id":"101b9d27.13f603","type":"smooth","z":"394fcece.136712","name":"Calc Min","property":"payload","action":"min","count":"360","round":"2","mult":"single","reduce":false,"x":740,"y":180,"wires":[["40c8e56d.30d48c"]]},{"id":"40c8e56d.30d48c","type":"change","z":"394fcece.136712","name":"Min","rules":[{"t":"change","p":"topic","pt":"msg","from":"RelHum","fromt":"str","to":"Min","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":180,"wires":[["582e9472.71b22c"]]},{"id":"ee67fbe.e613308","type":"smooth","z":"394fcece.136712","name":"Calc Max","property":"payload","action":"max","count":"360","round":"2","mult":"single","reduce":false,"x":740,"y":20,"wires":[["7b15ae1f.411f5"]]},{"id":"7b15ae1f.411f5","type":"change","z":"394fcece.136712","name":"Max","rules":[{"t":"change","p":"topic","pt":"msg","from":"Temp","fromt":"str","to":"Max","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":20,"wires":[["b570ad02.c65f4"]]},{"id":"96d969c6.be2b98","type":"smooth","z":"394fcece.136712","name":"Calc Max","property":"payload","action":"max","count":"360","round":"2","mult":"single","reduce":false,"x":740,"y":140,"wires":[["b0771170.d405c"]]},{"id":"b0771170.d405c","type":"change","z":"394fcece.136712","name":"Max","rules":[{"t":"change","p":"topic","pt":"msg","from":"RelHum","fromt":"str","to":"Max","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":140,"wires":[["582e9472.71b22c"]]},{"id":"be0dce00.c5e13","type":"smooth","z":"394fcece.136712","name":"Calc Max","property":"payload","action":"max","count":"360","round":"","mult":"single","reduce":false,"x":740,"y":260,"wires":[["bf763bc4.c0bc18"]]},{"id":"e166f98e.7d66f8","type":"smooth","z":"394fcece.136712","name":"Calc Min","property":"payload","action":"min","count":"360","round":"","mult":"single","reduce":false,"x":740,"y":300,"wires":[["2042f190.5d2bce"]]},{"id":"bf763bc4.c0bc18","type":"change","z":"394fcece.136712","name":"Max","rules":[{"t":"change","p":"topic","pt":"msg","from":"CO2","fromt":"str","to":"Max","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":260,"wires":[["d234387f.02bb28"]]},{"id":"2042f190.5d2bce","type":"change","z":"394fcece.136712","name":"Min","rules":[{"t":"change","p":"topic","pt":"msg","from":"CO2","fromt":"str","to":"Min","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":870,"y":300,"wires":[["d234387f.02bb28"]]},{"id":"24b8446a.9a74ac","type":"delay","z":"394fcece.136712","name":"5 sec","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":590,"y":300,"wires":[["be0dce00.c5e13","e166f98e.7d66f8","d234387f.02bb28"]]},{"id":"d6f0e66d.b954d8","type":"delay","z":"394fcece.136712","name":"5 sec","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":590,"y":60,"wires":[["ee67fbe.e613308","7a68b6ba.b07c68","b570ad02.c65f4"]]},{"id":"51fc4b4e.8edf44","type":"delay","z":"394fcece.136712","name":"5 sec","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"x":590,"y":180,"wires":[["96d969c6.be2b98","101b9d27.13f603","582e9472.71b22c"]]},{"id":"b2a8bc76.aacca","type":"daemon","z":"394fcece.136712","name":"SGP30 BME280","command":"/usr/bin/python3","args":"/home/pi/Documents/SGP30_BME280_test.py","autorun":true,"cr":false,"redo":false,"op":"string","closer":"SIGKILL","x":140,"y":80,"wires":[["671787f5.117a08"],["1db214d.bcbedeb"],[]]},{"id":"671787f5.117a08","type":"python3-function","z":"394fcece.136712","name":"2 msg","func":"# Released under Beer License\n# Clear any leading or trailing whitespace\nOrigStr = msg['payload']\n# get rid of carriage returnsand leading whitespace\nOrigStr = OrigStr.lstrip()\nOrigStr = OrigStr.replace('\\r','')\nif len(OrigStr) >6:\n  # Save only the first line\n  TmpStr = OrigStr.partition(\"\\n\")[0]\n  TmpStr = TmpStr.replace('\\n','')\n  # Turn it into an array\n  TmpArray = TmpStr.split(\"|\")\n  try:\n    # Form the msg object\n    msg = {\n      \"topic\": TmpArray[0],\n      \"payload\": round(float(TmpArray[1]),2)\n    }\n    # return the msg\n    return [msg,None]\n  except:\n    # Form the msg object\n    msg = {\n      \"topic\": \"Debug\",\n      \"payload\": OrigStr\n    }\n    # return the msg\n    return [None,msg]\nelse:\n  # return the msg\n  return [None,None]","outputs":2,"x":270,"y":200,"wires":[["af7974d1.62fb98"],["1db214d.bcbedeb"]]},{"id":"1db214d.bcbedeb","type":"debug","z":"394fcece.136712","name":"Debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":380,"wires":[]},{"id":"af7974d1.62fb98","type":"switch","z":"394fcece.136712","name":"Split msg","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"Temp","vt":"str"},{"t":"eq","v":"RelHum","vt":"str"},{"t":"eq","v":"eCO2","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":420,"y":180,"wires":[["d6f0e66d.b954d8","336a5d64.79aa02"],["51fc4b4e.8edf44","8d5a4f65.5ecd4"],["24b8446a.9a74ac","b5770088.dc9d3"]]},{"id":"b570ad02.c65f4","type":"ui_chart","z":"394fcece.136712","name":"Temp","group":"54991333.36cc3c","order":4,"width":9,"height":3,"label":"","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"20","ymax":"30","removeOlder":"2","removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1010,"y":40,"wires":[[]]},{"id":"582e9472.71b22c","type":"ui_chart","z":"394fcece.136712","name":"Humid","group":"54991333.36cc3c","order":2,"width":9,"height":3,"label":"","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"20","ymax":"60","removeOlder":"2","removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1010,"y":160,"wires":[[]]},{"id":"d234387f.02bb28","type":"ui_chart","z":"394fcece.136712","name":"CO2","group":"df17b484.5087f8","order":2,"width":9,"height":3,"label":"","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"400","ymax":"2000","removeOlder":"2","removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1010,"y":280,"wires":[[]]},{"id":"54991333.36cc3c","type":"ui_group","name":"Environment (BME280)","tab":"fc7ad6e.40b8028","order":5,"disp":true,"width":12,"collapse":true},{"id":"df17b484.5087f8","type":"ui_group","name":"Gasses (SGP30)","tab":"fc7ad6e.40b8028","order":6,"disp":true,"width":12,"collapse":true},{"id":"fc7ad6e.40b8028","type":"ui_tab","name":"Grow Tent","icon":"dashboard","order":2,"disabled":false,"hidden":false}]
Next weekend, I'll tackle the Stemma soil sensors, hopefully it removes some of the errors and issues I have seen. IMHO, it will.

User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

As promised... I converted the Stemma stuff in Node-Red to run using the Node-Red-Node-Daemon.

First, here is the configuration for the Daemon node:
Capture1.JPG
Capture1.JPG (47.15 KiB) Viewed 4068 times
Of course I run this through the "2 msg" python function node, it's in my previous posts so no need to expand on it.

This runs into a "Switch" node, here's the first bit of configuration:
Capture2.JPG
Capture2.JPG (57.05 KiB) Viewed 4068 times
And here is what the flow looks like:
Capture3.JPG
Capture3.JPG (66.08 KiB) Viewed 4068 times
Here's the JSON for the flow:

Code: Select all

[{"id":"24b1a656.02afaa","type":"ui_gauge","z":"394fcece.136712","name":"","group":"dba29111.3e4d1","order":1,"width":3,"height":2,"gtype":"gage","title":"Moisture","label":"","format":"{{value}}","min":"500","max":"1500","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"800","seg2":"1200","x":660,"y":400,"wires":[]},{"id":"27db4506.9fd68a","type":"ui_gauge","z":"394fcece.136712","name":"","group":"47b7ae38.45e89","order":1,"width":3,"height":2,"gtype":"gage","title":"Moisture","label":"","format":"{{value}}","min":"500","max":"1500","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"800","seg2":"1200","x":660,"y":480,"wires":[]},{"id":"df24129e.1f131","type":"ui_gauge","z":"394fcece.136712","name":"","group":"dba29111.3e4d1","order":2,"width":3,"height":2,"gtype":"gage","title":"Temp","label":"C","format":"{{value}}","min":"15","max":"35","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"22","seg2":"30","x":650,"y":360,"wires":[]},{"id":"9403a49a.00c4a8","type":"ui_gauge","z":"394fcece.136712","name":"","group":"47b7ae38.45e89","order":2,"width":3,"height":2,"gtype":"gage","title":"Temp","label":"C","format":"{{value}}","min":"15","max":"35","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"22","seg2":"30","x":650,"y":440,"wires":[]},{"id":"ff82b221.79417","type":"ui_gauge","z":"394fcece.136712","name":"","group":"d8d02957.9818d8","order":2,"width":3,"height":2,"gtype":"gage","title":"Temp","label":"C","format":"{{value}}","min":"15","max":"35","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"22","seg2":"30","x":650,"y":520,"wires":[]},{"id":"4cbfc0a0.e5c9","type":"ui_gauge","z":"394fcece.136712","name":"","group":"d8d02957.9818d8","order":1,"width":3,"height":2,"gtype":"gage","title":"Moisture","label":"","format":"{{value}}","min":"500","max":"1500","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"800","seg2":"1200","x":660,"y":560,"wires":[]},{"id":"83eae28f.89158","type":"ui_gauge","z":"394fcece.136712","name":"","group":"d3f34506.4545f8","order":2,"width":3,"height":2,"gtype":"gage","title":"Temp","label":"C","format":"{{value}}","min":"15","max":"35","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"22","seg2":"30","x":650,"y":600,"wires":[]},{"id":"d8a4dda3.7f88a","type":"ui_gauge","z":"394fcece.136712","name":"","group":"d3f34506.4545f8","order":1,"width":3,"height":2,"gtype":"gage","title":"Moisture","label":"","format":"{{value}}","min":"500","max":"1500","colors":["#ffff6e","#00b500","#ffff6e"],"seg1":"800","seg2":"1200","x":660,"y":640,"wires":[]},{"id":"e8e94eae.794e6","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"2","mult":"single","reduce":false,"x":520,"y":360,"wires":[["df24129e.1f131"]]},{"id":"4753f2fe.a1a53c","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"2","mult":"single","reduce":false,"x":520,"y":440,"wires":[["9403a49a.00c4a8"]]},{"id":"fc4d3fbf.a0637","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"2","mult":"single","reduce":false,"x":520,"y":520,"wires":[["ff82b221.79417"]]},{"id":"14608945.67ddf7","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"2","mult":"single","reduce":false,"x":520,"y":600,"wires":[["83eae28f.89158"]]},{"id":"a05772bb.fee55","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"0","mult":"single","reduce":false,"x":520,"y":400,"wires":[["24b1a656.02afaa"]]},{"id":"bd339164.261b","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"0","mult":"single","reduce":false,"x":520,"y":480,"wires":[["27db4506.9fd68a"]]},{"id":"13969075.d5af1","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"0","mult":"single","reduce":false,"x":520,"y":560,"wires":[["4cbfc0a0.e5c9"]]},{"id":"b9d5fe7d.6b5f7","type":"smooth","z":"394fcece.136712","name":"","property":"payload","action":"mean","count":"10","round":"0","mult":"single","reduce":false,"x":520,"y":640,"wires":[["d8a4dda3.7f88a"]]},{"id":"64faace2.687684","type":"daemon","z":"394fcece.136712","name":"Stemma","command":"/usr/bin/python3","args":"/home/pi/Documents/Stemma_Joined.py","autorun":true,"cr":false,"redo":false,"op":"string","closer":"SIGKILL","x":160,"y":380,"wires":[["744b8184.758cb"],["b9acf6f1.af02b8"],[]]},{"id":"293cd8b5.9fcfa8","type":"switch","z":"394fcece.136712","name":"Split msg","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"0x36temp","vt":"str"},{"t":"eq","v":"0x36moist","vt":"str"},{"t":"eq","v":"0x37temp","vt":"str"},{"t":"eq","v":"0x37moist","vt":"str"},{"t":"eq","v":"0x38temp","vt":"str"},{"t":"eq","v":"0x38moist","vt":"str"},{"t":"eq","v":"0x39temp","vt":"str"},{"t":"eq","v":"0x39moist","vt":"str"}],"checkall":"true","repair":false,"outputs":8,"x":300,"y":520,"wires":[["e8e94eae.794e6"],["a05772bb.fee55"],["4753f2fe.a1a53c"],["bd339164.261b"],["fc4d3fbf.a0637"],["13969075.d5af1"],["14608945.67ddf7"],["b9d5fe7d.6b5f7"]]},{"id":"744b8184.758cb","type":"python3-function","z":"394fcece.136712","name":"2 msg","func":"# Released under Beer License\n# Clear any leading or trailing whitespace\nOrigStr = msg['payload']\n# get rid of carriage returnsand leading whitespace\nOrigStr = OrigStr.lstrip()\nOrigStr = OrigStr.replace('\\r','')\nif len(OrigStr) >6:\n  # Save only the first line\n  TmpStr = OrigStr.partition(\"\\n\")[0]\n  TmpStr = TmpStr.replace('\\n','')\n  # Turn it into an array\n  TmpArray = TmpStr.split(\"|\")\n  try:\n    # Form the msg object\n    msg = {\n      \"topic\": TmpArray[0],\n      \"payload\": round(float(TmpArray[1]),2)\n    }\n    # return the msg\n    return [msg,None]\n  except:\n    # Form the msg object\n    msg = {\n      \"topic\": \"Debug\",\n      \"payload\": OrigStr\n    }\n    # return the msg\n    return [None,msg]\nelse:\n  # return the msg\n  return [None,None]\n","outputs":2,"x":150,"y":440,"wires":[["293cd8b5.9fcfa8"],["b9acf6f1.af02b8"]]},{"id":"dba29111.3e4d1","type":"ui_group","name":"(x36) Soil Left Rear","tab":"fc7ad6e.40b8028","order":1,"disp":true,"width":6,"collapse":true},{"id":"47b7ae38.45e89","type":"ui_group","name":"(x37) Soil Right Rear ","tab":"fc7ad6e.40b8028","order":2,"disp":true,"width":6,"collapse":true},{"id":"d8d02957.9818d8","type":"ui_group","name":"(0x38) Soil Left Front","tab":"fc7ad6e.40b8028","order":3,"disp":true,"width":6,"collapse":true},{"id":"d3f34506.4545f8","type":"ui_group","name":"(0x39) Soil Right Front","tab":"fc7ad6e.40b8028","order":4,"disp":true,"width":6,"collapse":true},{"id":"fc7ad6e.40b8028","type":"ui_tab","name":"Grow Tent","icon":"dashboard","order":2,"disabled":false,"hidden":false}]
And finally, the code for the Stemma_Joined.py script:

Code: Select all

import time
from board import SCL, SDA
import busio
from adafruit_extended_bus import ExtendedI2C as I2C
from adafruit_seesaw.seesaw import Seesaw

# Device is /dev/i2c-1
i2c = I2C(1)

# Set up the Stemmas by specifying a temp correction,
# use a 0 (or float), do not delete the entry unless the
# device does not exist.
#
TempCorrection = {
  0x36 : 0,
  0x37 : 0,
  0x38 : 0,
  0x39 : 0
}
#
# Create an empty dictionary to hold the devices
Devices = {}
# Initialize the objects
for device_address in sorted(TempCorrection):
  Devices[device_address] = Seesaw(i2c, addr=device_address)
#
# Give the devices .5 sec to stabilize
time.sleep(.5)
#
# Now we can loop and query them
while (True):
  for device_address, device in Devices.items():
    # read moisture level through capacitive touch pad
    touch = device.moisture_read()
    # read temperature from the temperature sensor
    temp = device.get_temp()
    temp = temp + TempCorrection[device_address]
    # Output what we got
    print(hex(device_address) + "temp|" + str(temp))
    time.sleep(.1)
    print(hex(device_address) + "moist|" + str(touch))
    time.sleep(.1)
  # Give the devices time to stabilize
  time.sleep(2)
  
Some notes on this Python code:
1. we can apply a temperature adjustment to each individual stemma soil sensor, it's configured in the code, just set the value as desired (decimals and negatives allowed):

TempCorrection = {
0x36 : 0,
0x37 : 0,
0x38 : 0,
0x39 : 0
}

2. This Python dictionary is used to populate and create the devices, so don't go deleting any device addresses for those you want to have reporting, just leave them 0.

# Create an empty dictionary to hold the devices
Devices = {}
# Initialize the objects
for device_address in sorted(TempCorrection):
Devices[device_address] = Seesaw(i2c, addr=device_address)
#

3. The output is a string like this:
0x36temp|20.05
0x36moist|1014

Of course, this gets turned into a message with the topic "0x36temp" or "0x36moist" and the value as the payload.

4. The sleep(.1) between the two print statements is to make sure each line of data is seperated from the last.

User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

Well, I guess you were wondering where I was going to go next... Automation....

Three automated processes can now be added:
  • Automated watering
    Heat (on)
    CO2 Enrichment
All of these can be done with a IOT Relay, and although I have a different relay I am using in the grow tent, I found a similar (and IMHO better) product from Adafruit, the FeatherWing relay: https://www.adafruit.com/product/3191

Wiring the Featherwing, I ran GPIO 4 (Pin 7) to the Signal, GPIO 3.3 V (Pin 17) to the 3V and Ground (Pin 8) to GND. Although bringing the signal up to 3.3V will light the LED, the relay requires more power than the GPIO Signal can provide, hence the need for the 3V connection. Bringing the GPIO pin high activates a transistor that supplies 3.3V to the relay.

The HL-52S on the other hand is a bit more complicated. You have to power the relay with a 5VDC adapter, and then 5V from the GPIO to the relay header, and a GPIO pin (normally high) will switch the relay when you pull it low (ground.)

Personally, I think the Featherwing is the way to go.

The flow looks like this:
Capture1.JPG
Capture1.JPG (31.61 KiB) Viewed 4016 times
JSON for the flow:

Code: Select all

[{"id":"ffbfa9f9.2650c8","type":"daemon","z":"bc1ec26.6c70c4","name":"Heater Relay","command":"/usr/bin/python3","args":"/home/pi/Documents/Featherwing_Temp.py","autorun":true,"cr":false,"redo":false,"op":"string","closer":"SIGKILL","x":430,"y":480,"wires":[["2c82f321.42c88c"],[],[]]},{"id":"2c82f321.42c88c","type":"python3-function","z":"bc1ec26.6c70c4","name":"Stdout to MSG","func":"# Released under Beer License\n# Clear any leading or trailing whitespace\nOrigStr = msg['payload']\n# get rid of carriage returnsand leading whitespace\nOrigStr = OrigStr.lstrip()\nOrigStr = OrigStr.replace('\\r','')\nif len(OrigStr) >6:\n  # Save only the first line\n  TmpStr = OrigStr.partition(\"\\n\")[0]\n  TmpStr = TmpStr.replace('\\n','')\n  # Turn it into an array\n  TmpArray = TmpStr.split(\"|\")\n  try:\n    # Form the msg object\n    msg = {\n      \"topic\": TmpArray[0],\n      \"payload\": TmpArray[1]\n    }\n    # return the msg\n    return [msg,None]\n  except:\n    # Form the msg object\n    msg = {\n      \"topic\": \"Debug\",\n      \"payload\": OrigStr\n    }\n    # return the msg\n    return [None,msg]\nelse:\n  # return the msg\n  return [None,None]\n","outputs":2,"x":600,"y":480,"wires":[["6f254ccd.9ffeb4"],[]]},{"id":"bba34ec2.a563e","type":"ui_button","z":"bc1ec26.6c70c4","name":"Heat On","group":"54991333.36cc3c","order":4,"width":"2","height":"1","passthru":false,"label":"Heat On","tooltip":"","color":"","bgcolor":"","icon":"","payload":"ON","payloadType":"str","topic":"Control","topicType":"msg","x":90,"y":460,"wires":[["eaf3f4ea.a66a48"]]},{"id":"eaf3f4ea.a66a48","type":"exec","z":"bc1ec26.6c70c4","command":"/usr/bin/python3","addpay":false,"append":"/home/pi/Documents/Control_ON.py","useSpawn":"false","timer":"","oldrc":false,"name":"Control ON","x":250,"y":460,"wires":[["ffbfa9f9.2650c8"],[],[]]},{"id":"e113b338.14fc6","type":"ui_button","z":"bc1ec26.6c70c4","name":"Heat Off","group":"54991333.36cc3c","order":5,"width":"2","height":"1","passthru":false,"label":"Heat Off","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":90,"y":520,"wires":[["b006e5e3.a178e8"]]},{"id":"b006e5e3.a178e8","type":"exec","z":"bc1ec26.6c70c4","command":"/usr/bin/python3","addpay":false,"append":"/home/pi/Documents/Control_OFF.py","useSpawn":"false","timer":"","oldrc":false,"name":"Control OFF","x":250,"y":520,"wires":[["ffbfa9f9.2650c8"],[],[]]},{"id":"6f254ccd.9ffeb4","type":"ui_text","z":"bc1ec26.6c70c4","group":"54991333.36cc3c","order":6,"width":"3","height":"1","name":"Heat Label","label":"Heat","format":"{{msg.payload}}","layout":"row-left","x":790,"y":480,"wires":[]},{"id":"54991333.36cc3c","type":"ui_group","name":"Environment (SCD30)","tab":"fc7ad6e.40b8028","order":5,"disp":true,"width":"12","collapse":true},{"id":"fc7ad6e.40b8028","type":"ui_tab","name":"Living Room","icon":"dashboard","order":2,"disabled":false,"hidden":false}]
Now all you have to do is connect the STDOUT from either the previously posted SCD30 Daemon node, or the SGP30/BME280 daemon Node:
Capture2.JPG
Capture2.JPG (91.01 KiB) Viewed 4016 times
In order to allow a button for "Heat On" and "Heat Off", I had to use the Exec Node:
Capture3.JPG
Capture3.JPG (33.31 KiB) Viewed 4016 times
Since the Heat Relay daemon node processes input from StdIn (basically just like entering the data from the keyboard), I needed to create two simply Python scripts to send a message via their StdOut to the Heat Relay for manual heat control like this:

Control On:

Code: Select all

print("Control|ON")
Of course, the "Control Off" is:

Code: Select all

print("Control|OFF")
Note the "common" layout of "topic"|"payload" that I seem to be using a lot.

The daemon node, Heat Relay, uses the following code:

Code: Select all

# 
import RPi.GPIO as GPIO
from datetime import datetime
from datetime import timedelta
from os import path
# GPIO Pin the relay is connected to
Relay_channel = 4
# Set the min and max temperatures
TempMin = 22
TempMax = 26

# Set up the GPIO Pin
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(Relay_channel, GPIO.OUT, initial=GPIO.LOW)

# Set up the state variable to "Off"
GPIO.output(Relay_channel, GPIO.LOW)
CurrentState = 0
NewState = 0
# Send the state out via STDOUT
print("State|OFF")

# Processing Loop
while True:
	# Read the input from stdin
	DataIn = input()
	#
	# Process the input
	#
	# Save only the first line
	TmpStr = DataIn.partition("\n")[0]
	TmpStr = TmpStr.replace('\n','')
	# Turn it into an array
	TmpArray = TmpStr.split("|")
	# is it a Temp message?
	if TmpArray[0] == "Temp":
		# Save the temperature value
		temp = round(float(TmpArray[1]),2)
		# if it's less than the MinTemp, turn it on
		if temp < TempMin:
			NewState = 1
		# if it's more than the MaxTemp, turn it off
		elif temp > TempMax:
			NewState = 0
	elif TmpArray[0] == "Control":
		# Save the command value
		command = TmpArray[1]
		# Process the command
		if command == "ON":
			NewState = 1
		elif command == "OFF":
			NewState = 0
	# If the new state is not the current state, change it
	if NewState != CurrentState:
		# Only process if the relay is on
		if CurrentState == 1:
			# Turn the relay off
			GPIO.output(Relay_channel, GPIO.LOW)
			# Update the current state
			CurrentState = 0
		# Only process if the relay is off
		else:
			# Turn the relay on
			GPIO.output(Relay_channel, GPIO.HIGH)
			# Update the current state
			CurrentState = 1
	# Output the current state
	if CurrentState == 0:
		# Send the state out via STDOUT
		print("State|OFF")
	else:
		# Send the state out via STDOUT
		print("State|ON")
So, basically what happens is that the python script starts, then goes into an endless loop, reading StdIn and expecting (or at least handling) messages that start with "TEMP|" or "Control|". The "Humid" and "CO2" messages from the SCD30 daemon get ignored.

The output from the Heat Relay daemon is either "State|ON" or "State|OFF", these get sent through the previously posted StdOut 2 msg" python script so that the Dashboard Text node can properly display if the heat is on or not.

User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

For those of you using the HL-52S, (or not, but want to keep a seperate day/night temperature cycle), I have this code:

Code: Select all

# 
import RPi.GPIO as GPIO
from datetime import datetime
from datetime import time
from datetime import timedelta
from os import path

def isNowInTimePeriod(startTime, endTime, nowTime): 
    if startTime < endTime: 
        return nowTime >= startTime and nowTime <= endTime 
    else: 
        #Over midnight: 
        return nowTime >= startTime or nowTime <= endTime 

# GPIO Pin the relay is connected to
Relay_channel = 17
# Set the min and max temperatures
DayStart = time(4,00)
DayEnd = time(20,00)
DayMin = 25.75
DayMax = 26.3
NightMin = 24.5
NightMax = 25.0

# Set up the GPIO Pin
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(Relay_channel, GPIO.OUT, initial=GPIO.HIGH)

# Set up the state variable to "Off"
GPIO.output(Relay_channel, GPIO.HIGH)
CurrentState = 0
NewState = 0
# Send the state out via STDOUT
print("State|OFF")

# Processing Loop
while True:
	# Read the input from stdin
	DataIn = input()
	#
	# Process the input
	#
	# Save only the first line
	TmpStr = DataIn.partition("\n")[0]
	TmpStr = TmpStr.replace('\n','')
	# Turn it into an array
	TmpArray = TmpStr.split("|")
	# is it a Temp message?
	if TmpArray[0] == "Temp":
		# Save the temperature value
		temp = round(float(TmpArray[1]),2)
		# Are we in day mode?
		if isNowInTimePeriod(DayStart, DayEnd, datetime.now().time()):
			TempMin = DayMin
			TempMax = DayMax
		else :
			TempMin = NightMin
			TempMax = NightMax
		# if it's less than the MinTemp, turn it on
		if temp < TempMin:
			NewState = 1
		# if it's more than the MaxTemp, turn it off
		elif temp > TempMax:
			NewState = 0
	elif TmpArray[0] == "Control":
		# Save the command value
		command = TmpArray[1]
		# Process the command
		if command == "ON":
			NewState = 1
		elif command == "OFF":
			NewState = 0
	# If the new state is not the current state, change it
	if NewState != CurrentState:
		# Only process if the relay is on
		if CurrentState == 1:
			# Turn the relay off
			GPIO.output(Relay_channel, GPIO.HIGH)
			# Update the current state
			CurrentState = 0
		# Only process if the relay is off
		else:
			# Turn the relay on
			GPIO.output(Relay_channel, GPIO.LOW)
			# Update the current state
			CurrentState = 1
	# Output the current state
	if CurrentState == 0:
		# Send the state out via STDOUT
		print("State|OFF")
	else:
		# Send the state out via STDOUT
		print("State|ON")
Note that if you are going to use this code with a Featherwing, you need to reverse the GPIO logic, HIGH becomes LOW and vice versa.

User avatar
iwbaxter
 
Posts: 22
Joined: Fri Feb 19, 2021 12:49 am

Re: Node-Red, Pi and interfacing Adafruit hardware

Post by iwbaxter »

I forgot to add one last point on the daemon nodes.

There is an option to restart if there is an error or the process exits. Normally when I am testing, I leave this unchecked. This is so Node-Red (which doesn't handle these well) doesn't lock up and become unresponsive.

Once the daemon node is working fine, I suggest re-enabling the option:
Capture4.JPG
Capture4.JPG (47.72 KiB) Viewed 4016 times
From time to time the Stemma Soil sensors will have issues, usually relating to being unable to get a valid moisture reading. This will ensure that if that happens, the daemon will restart and you can continue to gather data from them. Otherwise, you have to re-deploy the flows to get them all started again.

Locked
Please be positive and constructive with your questions and comments.

Return to “For Educators”