Setup IoT Gateway on a Pi 4

Continuing from my last post, this part covers how I setup docker-compose on the Pi 4 OS64 to help me recover from a system failure.

Learning Docker Compose required some effort. Using the scripts at IOTStack works for the most part but one is no better equipped to troubleshoot if something goes wrong.

The file used configures Node-RED, InfluxDB 2, Grafana, MQTT, Portainer, and Telegraf can be found at Telegraf was not needed but included anyway to demonstrate that it does work by pushing system info to influx.

Docker compose was run on both the Pi OS64 and Windows 10. This setup is not meant for production environment as it falls short on security. A dev-ops mess. The github repo has pem, .env files which are an anti-patterns. Please refer to sources like this to properly manage secrets.

Back of Napkin Requirements


  • SSL via self-signed certificate
  • user/passwords configuration as default
  • Config, flows, additional components saved outside of docker container
  • Parked – SSL certificate by trusted authority
  • Parked – Reverse Proxy


  • Use Version 2
  • Config is preset so when docker compose runs, no additional configuration required
  • Test with python and push three sinusoidal signals at different frequencies
  • Parked SSL


  • Accept any connection with no authentication
  • Parked – Config should allow for authentication
  • Parked – SSL
  • Parked – Authentication


  • Work without configuration and publish system metrics to Influxdb
  • Parked – SSL


  • Work with InfluxDB V2
  • Parked – SSL
  • Parked SAML
  • Parked – Reverse Proxy

Getting Started


  • Pi OS 64 installed ready for ssh. How-to found here; scroll to bullseye section and follow instructions. Latest images here. My system is on an SSD and how-to can be found here.
  • Docker via convenience scripts run from home directory and running as a service. Could work with Debian Arm64 but did not try that the time of writing.
curl -fsSL -o
sudo sh
sudo systemctl enable docker 
 pip3 install docker-compose


Under your home directory. e.g. cd ~ under Linux or c:/user/theuser/ under Windows 10

git clone

or if you don’t like the Rasperry-Docker-Compose folder as the name, pass the desired folder name to the git command. e.g. to clone into folder XYZ or c:\sandbox

git clone XYZ
git clone c:\sandbox

Note your will have to edit the .env file under the cloned repo directory reflect the non-default folder. BIND_VOLUME_ROOT=~/Raspberry-DockerCompose


Docker compose will create the host volumes automatically and keeps existing data intact if they exist. e.g. the config files.

Edit ~Raspberry-DockerCompose/.env or wherever you cloned to repo and change the INFLUX_IP to the device that is running docker-compose. e.g.


From a terminal run the following. In my case I was in ~ Raspberry-DockerCompose

docker-compose up 

Docker will download the required images and display something like the following on the terminal.

Smoke Test


Open your browser and navigate to https://<ip>:9000. You will prompted to change the admin password. Provide one and proceed to login.

Click on stacks and following the raspberry-dockercompose link and the six container will show up. Explore as you see fit.


Open your browser and navigate to https://<ip>:1880 to log into Node-RED. Change the <ip> to the address reflect your device IP. The repo has a self-signed certificate that really is not useful beyond getting images up an running with SSL. The certificate is untrusted since it was self-signed.

Login with admin/changeOnInstall

To generate a your own certificate cd into ~Raspberry-DockerCompose/volumes/nodered/data and run

sudo openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem

Since it is self-signed, most of the information can be defaulted since nobody outside will trust it anyway. Note Common Name (e.g. server FQDN or YOUR name) is set to your devices IP. e.g.

Changing the admin password. This requires running a node-red command which can be done from the terminal or via Portainer.

From a terminal run

docker exec -it node-red /bin/bash

Or from Portainer select console, then connect.

Then run.

node-red admin hash-pw

You will be prompted a password, provide on and a hash will be created to copy into setup.js. Copy the hash.

Assuming you are still in Raspberry-DockerCompose/volumes/nodered/data, edit setting.js and change the admin entry’s password to the new hash. (copy and paste)

Restart node-RED to implement changes. Flows and installed libraries reside in the data folder. If you have a new docker-compose environment, copy this folder and below to the new host-volume if you want to preserve settings.


Open your browser and navigate to https://<ip>:8086 to log into influxdb. Change the <ip> to the address reflect your device IP. The username/password is theUser/changeOnInstall as defined in the dev-influxdb.env.


Navigate into Data and then into API Tokens. You will notice the token for a “theUser”. Drill down in that token. The value is set to makeMeComplicated.. This is the same token used for telegraf which is not a good practice but good ok for learning.

Click on explore. Select the theBucket, filter name=sda, and _field=io_time and submit. A plot will appear implying that telegraf is running ok.

From the terminal, change to ~Raspberry-DockerCompose or whatever you cloned the repo into. On a Pi OS64, pip3 is already installed. Run

 pip3 install matplotlib
 pip3 install influxdb_client

If you ssh’d in matplotlib won’t do anything useful. Either way , run and wait about 20s.

python ./

Got back to influxDB url and select explore. You may have to refresh your browser. Select filter=tag and check carrier, message, modulatedAM. .

Select past 15m, go into custom window period and change to 1s. The python code injects samples at the 1s resolution. Click submit and a waveform like the following should appear.


Open your browser and navigate to https://<ip>:3000 to log into influxdb. Change the <ip> to the address reflect your device IP. Default user/password is admin/admin. Change the password when prompted. Select Data Sources then select InfluxDB

Select Flux as the query language and turn off Basic auth. Ensure the URL points to your device IP. Port defaults to 8086.

As per dev-influxdb.env, set Organization to theOrg, Token to makeMeComplicated, DefaultBucket to theBucket. Save & Test.

Go to Explore, ensure InfluxDB source and paste the following for the query. Run the Query. Depending how long ago you did the you may have to change the period on the dropdown.

from(bucket: "theBucket")
  |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
  |> filter(fn: (r) => r["tag"] == "carrier" or r["tag"] == "message" or r["tag"] == "modulatedAM")
  |> aggregateWindow(every: 1s, fn: mean, createEmpty: false)
  |> yield(name: "mean")

If all works, three signals should appear.


On Windows 10, install, start it up and create a new entry with the proper IP address to your device, and connect.

On Linux, run

sudo apt install -y mosquitto mosquitto-clients

In one terminal, run for the subscriber

mosquitto_sub -d -t YourTopic

In another terminal, run the publisher

mosquitto_pub -d -t YourTopic -m "Hello world!"

Hello World! will appear in the subscriber terminal.

System Restart

Since docker is a service and the the restart-policy for each docker compose serve is set to restart: unless-stopped, all that is required to run restarts on system startup. Tested under Pi OS 64.

Posted in IoT

IoT Gateway on the Raspberry Pi4

Twelve years ago, I built a power monitoring system acting as a Modbus slave device. It has been running 24×7 since. The hydroponic monitoring and control system built four years ago provides me with lettuce and herbs for the winter. Both systems integrated with version 3 of mango; an open source SCADA/HMI. Mango ran on old PCs then on a repurposed Mac Mini with Ubuntu 20.04. Still, something was missing. I wanted to run on even more low power 24×7. Enter the trusty Raspberry Pi.

Edge Computing

Having worked in both control system engineering and pure tech, I can see the appeal of the IoT buzzwords mania. Some PLC vendors are bolting on MQTT on their product and rebranding it as IoT ready, while some larger legacy vendors are updating their bloated ecosystem of products even more bloated.

Mango and atvise have architected their solutions to be cloud ready.

Sadly, a lot of the “home'” examples with Node-RED and InfluxDB are overly simplistic and do not reflect the reality of the workflow involved in designing, commissioning, and maintenance of large industrial control systems.

I tried the MQTT to InfluxDB via Node-RED and would not want to maintain a large system with that approach. I am not a fan of flow-chart programming as it just becomes a pain to “refactor”. Weidmueller now has Node-RED baked into some of their controllers and I briefly looked at it. What I do like about Node-RED, though, it is built on node.js and provides engineers options to develop custom components to implement recurring design patterns. This simplifies the flow chart hell.

TO-BE Context Diagram

Flashback a few months ago. I installed Node-RED, Mosquito MQTT, and InfluxDB on my Pi4 8Gb. The Node-RED polled the power monitor via Modbus and pushed into Influx. All was humming along until I did something stupid and could not longer boot my PI.

Guess what. I forgot what I did and only had a handful of config backups. Time to think about containerizing software using Docker and Docker Compose avoid this quagmire next time.

Note the architecture below increased the number of failure points compared to the Modbus to SCADA integration. I would not architect something for real control systems without security and fault tolerance baked in. For home it is more that good enough.

Modbus is simple. Floats, integers, etc. flow nicely through a simple protocol. Many devices implement it. MQTT, on the other hand, adds more overhead and in this scenario overkill. I need to explore how best to define a “tag” and propagate through the system without much coding. Just like traditional PLC/HMI systems. Over the Air Updates help in that situation and remains TBD.

Plans to push the Influx/Grafana to the cloud are in the works. On-premise is good enough to repurpose the existing controllers to be edge devices. The hydroponics once contained the CO2/ temperature/relative humidity sensors and are not used for controls. They have now moved to two new boards that just does air quality.

I like the fact that Python gives me access to data science, ML, signal processing tools and opted to use it to handle MQTT messages and marshal them to Influx. Migrating to a custom Node-RED component is in the backlog but I can crank out code faster with python than Javascript.

Dev Environment

My environment contains the standard oscilloscope/logic analyzer, signal generator, power supply. On the software side, I spend most of my time in Jupyter or Visual Studio code. The following outlines some of the software I use.

Next Steps

  • Dockerize InfluxDB 2, Node-RED, Grafana, and Portainer
  • Learn Flux
  • Create the Grafana dashboards
  • Write the MQTT to Influx logic in Python
  • Refactor the board logic to communicate via MQTT
  • Create basic control screens in Node-RED

Posted in IoT

Skeleton Tracking

I’ve been working with python, Tensorflow, and OpenCV along with a couple of RealSense cameras for a project. As a break from it all, I wanted to test out the skeleton tracking SDK from Cubemos. Their SDK allows the tracking of 18 joints per person for up to 5 people in a given frame.

Using their trial license and one of my RealSense cameras the process was rather painless.

Lately, most of my development revolves around python using Conda as my python package manager and Docker to contain specific environments from which to deploy the resulting solutions.

I do revert to C++ and on the windows environment, Microsoft Visual Studio Community 2019 is used to compile stuff from source such as OpenCV. The Cubemos installer generates sample solutions for VS17. In my case, the installation failed and I had to resort to generating them by hand.

The error is because the OpenCV cmake file that comes bundled with Cubemos, does not recognize a VS19 environment. Fortunately, I have OpenCV 4.3 and built a version specific to my needs.

Alternatively, one can download OpenCV 4.3.0 and copy the required files to the Cubemos samples folder.

OpenCV build folder contents.
Cubmeos OpenCV dependency folder contents

Configure and Generate work as expected. OpenCV was built using VS19 thus the OpenCV Runtime is vc16. If you downloaded OpenCV 4.3.0 then the runtime would be vc15

Although the binaries for the demos come installed, I felt compiling for specific environment was a must, even though most of development is in python.

Sample Outputs

Each estimated joint location has a corresponding x,y,z coordinate from which one can use to provide specific solutions.

The demo app does not perform any smarts such as verifying if a person is standing or just a an picture of a person. In this example, it got confused with the tools hanging on the pegboard. The potential applications are plenty and limited by your imagination. A permanent licence costs $75US.

Visual Analytics using OpenCV and RealSense Camera


The thought of using computer vision on a couple of projects has been bouncing in the back of my mind for a few years. I wanted to expand things and include more deep learning elements as well as evolve my use of  OpenCV from simple projects like counting objects to something with more challenging.

Why not add some smarts to hydroponics to monitor plant characteristics such as height and root health?


I purchased Intel’s RealSense D435 Depth camera and chose the D435 over the D415 because of the global shutter feature.  Stereo and IR features became must-have features to future proof the development. By completing a survey, a $25US coupon provides enough incentive to purchase it from Intel’s site.

The camera does not take much space and one may want to use something else rather than the tripod it comes with as it hard to keep stable.


The Windows docker installation uses hyper-V and somewhere along the way, the Ubuntu VM got corrupted. So for this exercise, Windows remains the dev OS with python as the dev language.

Python Steps

  1. Download and install Anaconda I use it to create python sandboxes and prefer it to virtualenv. I did not have a 3.7 python on this PC, so I let Anaconda set it all up.  If you open a plain old command prompt, conda will not be found. Use the anaconda command prompt as it sets all the paths.
  2. Optional but recommended – create a conda environment. e.g. conda create -n opencvdev
  3. Optional – activate opencvdev
  4. Install OpenCV using conda install -c conda-forge opencv
  5. Download and install the Intel RealSense SDK 2.0
  6. Test the camera with the standard utilities. It needs USB 3.0 and won’t work under USB 2.0
  7. Under the conda opencvdev env, run pip install pyrealsense2
  8. run python from the command line
(opencvdev) C:\Dev\source\python\vision>python
Python 3.6.6 | packaged by conda-forge | (default, Jul 26 2018, 11:48:23) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2 as cv
>>> cv.__version__
>>> import pyrealsense2 as rs
>>> rs.intrinsics()
width: 0, height: 0, ppx: 0, ppy: 0, fx: 0, fy: 0, model: None, coeffs: [0, 0, 0, 0, 0]
  • cv.__version__ returns a string with the opencv version
  • rs.instrinsics() returns the device info such as focal points, distortion coefficients. Nothing has been set up but the test is to see if the libraries are set up ok

Run one of the python wrapper examples. e.g. to get something like this.

Finally, install Jupyter  for some interactive what-if development.

#matplotlib works well and a good substitute for imshow from opencv. 
conda install -c matplotlib 
conda install -c anaconda jupyter 
#Jupyter did not see my conda env and the following fixed it
python -m ipykernel install --user --name visionenv

#run jupyter
jupyter notebook

And Jupyter does job of running the  as a notebook.

Problems Encountered

The dreadful Intel MKL FATAL ERROR: Cannot load mkl_intel_thread.dll kept rearing its ugly head.  I tried with no avail to mix and match package versions and abandoned the troubleshooting ship.

What worked is one of the two as they were performed at once before re-configuring the conda env.

  • Removing the paths to anacoda. Initially paths set  and ran everything from a plain old command prompt. I reverted to using the anaconda prompt.
  • Removed a bunch of apps including visual studio 2017 and a bunch of soft-synth plugins assuming something was found on the system path that conflicted with the newer conda dll version.

Automatic Control of Nutrient and pH

A day after installing the hardware and upgrading the firmware to control the pH and EC using an Arduino, the results are as expected; the steady state trends just above the setpoint.

Keeping the pH and EC to a desired set point uses a simple on/off approach. The use of a a PID controller was briefly considered and in the spirit of the minimum viable product, I opted for something simple for now.

Armed with three peristaltic pumps and another on way, some scrap wood, and additional code, a functional setup connected to the Arduino based control panel led to the following setup.

Nutrient Controller

Nutrient Controller








Diluting Fertilizers and pH Down solutions

The peristaltic pumps used have a max flow rate of 70 mL/min. Note I received one in the batch that could only pump 27 mL/min so I ended up using that one to control pH.

I opted for constant speed control rather than variable speed and avoided having to deal with PWM and filters to generate the analog output. Based on this information and erring on the safe side, a minimum dispensed volume of 4 ml was assumed. Using trial and error a dilution ratio of 4:1 water:fertilizer and 3:1 water:vinegar make up source to feed the nutrient tank (100 L).  Without dilution, the EC and pH spiked as shown below.

EC Trend






Control Philosophy

As stated early, on/off control frames the approach to keeping the pH and EC at the desired setpoint.


Hand – Dispense fixed volume (mL) and stop. Volume to dispense set from HMI and saved in the Arduino’s non-volatile memory.

Off – turn off pump

Auto – Dispense a fixed volume (mL) every interval (s) . Volume and Interval set from HMI and saved in the Arduino’s non-volatile memory.

  1. Dispense if ( error = (setpoint – present value)) < 0 and trend to control on rising
  2. Dispense if (error = (setpoint – present value)) > 0 and the trend to control on is falling
  3. Stop dispensing when PV is 5% above or below SP depending on the trend to control. This minimizes unnecessary on/off chatter around the SP.


EC – Desired trend is falling trend. e.g. when below SP control apply control

  • SP = 1050, PV = 1020
  • error = 1050 – 1020 = 30 > 0
  • trend desired: falling
  • Result: control pump
  • Stop control when PV = 1050 * 1.05 ~ 1100

pH – Desired trend is rising trend. e.g. when above SP control apply control

  • SP = 6, PV = 6.2
  • error = 6 – 6.2 = -0.2 < 0
  • trend desired: rising
  • Result: control pump
  • Stop control when PV = 6* .95 = 5.7

The following charts illustrates the result. The left side is when I first moved the controller into Auto and was not tuned to the given process. Both pH and EC respond to drastically to the step change. The right side highlights the EC needing control and the bump in process is not as drastic. One day I may revert to implementing the P part of the PID and dispense the volume based magnitude of the error. So far there is no need for that.

ph ec control

Peristaltic Pumps for Hydroponic Nutrient Dispenser

Nutrient Containers and Pumps

Time to mock up some hardware and write some code to drive the pumps using the nutrient containers and pumps now on hand.


  1. Use the transistors available spare parts bin
  2. No need for speed control so PWM not required
  3. Dispense a specific volume (ml) of liquid
  4. Pump a volume (ml) liquid over a period of time (s)
  5. Periodically dispense a volume (ml) of liquid every interval (s)

Wiring Diagram

I did not have any MOSFETs on hand and the BJTs available could easily drive small loads.  The measured current draw of the pump came in at around 160mA. The max current the Arduino can source is 40 mA per pin with a max of 200mA combined. Given that the hydroponics drives several circuits, I opted to limit the driving current to 4mA resulting to a 1kΩ resistor and would drive the transistor into saturation. (switch mode).









The flyback diode is to prevent any back-emf to harm the transistor.  The before and after reveal  shows the effects of shunting the excess voltage when turning off the pump.






The software is built on top of what is currently written. The partial class diagram with public methods highlights the gist of the PeristalticPump class.









Dispensing fluids becomes quite simple. I’ve tested the outputs using an oscilloscope. The one issue the cheap pumps and hoses is the max flow rate degrades over time. e.g. hoses don’t contract and expand the same way. I will deal with that later but there is a method to set the maxflowrate which could be part of the calibration process.

DA_PeristalticPump XY_001 = DA_PeristalticPump(XY_001_PIN, HIGH);
XY_001.dispenseVolume(50); // dispense 50ml 
XY_001.dispenseVolumeEvery(10, 15); // dispense 10 ml every 15 seconds
XY_001.dispenseVolumeOver(150, 600 ); // dispense 150 over 10 minutes (600 seconds)

Now that this is unit tested, it is time to replicated the setup 2 more times, and come up with a control philosophy for pH/EC.

Home Hydroponic Control System

Hydroponic – 4 Months Later

It has been 4 of months since the Arduino-based hydroponic control system has been in operation and I’ve since harvested arugula and lettuce. I’ve added a couple of columns to to grow cherry tomatoes. For the most part it has been a good experience.








I’ve finally added EC and pH measurement from Atlas Scientific  and programmed it using I2C rather than serial. I also added voltage isolation between the probe and the rest of circuitry to reduce changes of noise interference.  Data collected is via modbus over xbee to my SCADA host as before. The updated wiring includes the two extra sensors as shown below.







The spike in measurement resulted from adding more nutrient and pH down to the nutrient tank. More about that later.





New Development Environment

Most noteworthy, I migrated to using PlatformIO as the dev environment given that it supports multiple boards, has a command line interface, integrates nicely with the Atom editor and github.


  1. Inconsistent distribution of flow. The drip lines where sitting at the top and some plants would not get enough water. Fix: added 2″ caps (orange in pic) ensuring proper alignment of drip line.
  2. Water leaking to the floor. I could have done a better job in making the holes to host the net pots. When harvested, I would have to keep an empty net pot to avoid dripping of water. Fix: Replaced with 2″ Wye. It holds a 2″ net pot nicely. I could not find white wyes that did not cost and arm and a leg. I opted for the black drainage type.  The photo shows a trial test.
  3. 3″ net pots to hold cherry tomatoes is throwaway. The holes where made into 4″ pipe and it caused all kinds of issues. Fix: Replace with TODO wyes.
  4. Drain pump. The one I bought is too slow and noisy. Fix: I use a wet vac to drain the water. It is a lot faster and helps with cleaning the tank.
  5. Level Sensor: The sensor got destroyed with the splashing of the nutrient mixture over time. Also, the readings on average were correct but I did not like the range in level during operation.  Fix: Removed and looking for different sensor.
  6. Topping off nutrients is ok a the start but after a while, I just want to system to take care of it all. One has to add it over a period of time rather than in one shot. Otherwise, spikes in pH/EC occur. Fix: purchased nutrient containers  and some peristaltic pumps. With some TODO driver circuitry and code, managing nutrients should be mostly automated.


The following plot represents nutrient temperature and growing chamber temperature. The nutrient temperature is on the low end of the range and I’m going to test to see if keeping the temperature around 21C (70F) impacts the growing cycle.





Next Steps

The fun stuff begins and it is machine vision. One to assess how well photosynthesis is occurring and the other is to measure growth.

Home Hydroponics


After experimenting with LED plant lighting, I finally got to the point of building an Arduino based hydroponic controller and ready to start planting things. As usual, functional requirements frames the project and consist of the following:

  •  Lighting control by On Hour/Minutes and Off Hour Minutes
  • Circulation pump and Fan control via on time/off time
  • Hand-Off-Auto for lighting and circulation pump
  • Measure Mixture Temperature
  • Measure Chamber temperature and humidity
  • Drain pump
  • Circulation Pump
  • H2O Inlet valve
  • pH and Electrical Conductivity
  • LCD display of key parameters
  • SCADA integration via modbus
  • xbee connectivity since I have several of those lying around
  • Nice to Have – CO2


A simple P&ID of the system the building of the system from which the tag list was created along with the corresponding modbus addresses.

Device Communications

The system consists of a mash-up of several technologies which communicate over different protocols. I like I2C and 1Wire as it simplifies the wiring.

Control Panel

The control panel components came from Digi-Key  as they provide competitive pricing and quick delivery. Note I chose Phoenix terminal blocks for the cheap price and quality.  Enclosures ended up too expensive leading to the din rails, terminal blocks, etc. mounted on wood. It does the job and came out ok.

A simple food container houses the LCD/RTC and switch enclosure and the future EC/pH sensor electronics as this is where the I2C devices reside. Labeling is not the nicest but it is good enough for now. The xbee device is left hanging there for now as well.


Inspiration for the setup came from this site and a chat with the folks at Quick Grow. I was told that 2.5″ piping would be sufficient to grow lettuce and thus ended up using that size for the first phase. I left a space for future expansion for 3″ pipe for cherry tomatoes.

Note this was built in an un-sused shower space in the basement and the folks at Quick Grow stated that the white will reflect the light so no need for reflective material. I also purchased the LED lighting from them for both the growing chamber and seeding area to support the local business.

General Parts List

A partial parts list used for the project.

The Schedule 40 piping and connectors as well as the sharkbite products came from good old Home Depot.

Stripping wire and terminating them became quite easy with these tools


There is a lot of libraries available to connect the various sensors. I also wrote my own to handle discrete inputs and outputs, timer outputs, Hand-Off-Auto, etc. This made things easier to maintain and can be found at and the main code at

// Discrete Outputs
DA_DiscreteOutput DY_102 = DA_DiscreteOutput(31, LOW); // Seeding LED 120 VAC 
DA_DiscreteOutput DY_103 = DA_DiscreteOutput(32, LOW); // Growing Chamber LED 120 VAC 
DA_DiscreteOutputTmr PY_001 = DA_DiscreteOutputTmr(33, LOW, 
Discrete Inputs DA_DiscreteInput HS_003B = DA_DiscreteInput(26, 
   DA_DiscreteInput::RisingEdgeDetect, true); // LCD display previous
DA_DiscreteInput HS_003C = DA_DiscreteInput(27, DA_DiscreteInput::RisingEdgeDetect, true); // LCD display Enter DA_
HOASwitch HS_001AB = DA_HOASwitch(7, 0, 8); // Circulation Pump Hand Status : HOA :Hand/Auto 
DA_HOASwitch HS_102AB = DA_HOASwitch(52, 0, 53); // Seeding Area LED : HOA :Hand/Auto

Changes in switch values are detected and invoke a callback function. For example, the HOA class invokes a callback function passing the new switch state detected.

void on_GrowingChamberLED_Process(DA_HOASwitch::HOADetectType state)

  *tracePort << "on_FlowingLED_Process HS_103AB" << endl;
  HS_103AB.serialize(tracePort, true);

  switch (state)
    case DA_HOASwitch::Hand:
      DY_103.forceActive(); // force the light on
    case DA_HOASwitch::Off:
    case DA_HOASwitch::Auto:

Median Filters

Noisy Liquid and CO2 levels was observed over time through the SCADA system. I exported the data in question and explored ways to deal with the extreme fluctuations. Note I did put an oscilloscope and the CO2 signal was clean. After some Excel plotting, I ended up with wanting a median filter with with a window size of 5. Fortunately, a library was written for it which saved me some time. The content of the signal on the left is the before filtering there is about 3000 samples in that image.


Nutrient Tank Level

Data Acquisition

The flow and CO2 use interrupts. Polling rates of the switch are set up at 500ms unless overridden. e.g.

  HS_002.setPollingInterval(500); // ms
  LSHH_002.setDebounceTime(1000); // float switch bouncing around
  LSHH_002.setPollingInterval(500); // ms
  HS_002.setOnEdgeEvent(& on_InletValve_Process);
  LSHH_002.setOnEdgeEvent(& on_InletValve_Process);

Flow rates are calculated every 1second based on the pulses from the interrupt handler. The 1-wire and DHT-22 reading is performed every 5 seconds.

The ultrasonic level sensor was “calibrated” to read 100% at 100 L as well as the hi level switch.


  • Ultrasonic sensor – no special circuitry required to connect to the Arduino. Example here
  • DHT-22 sensor – added a 10k resistor between vcc and signal. Example here
  • 1Wire Temperature sensor -added 4.7k resistor between vcc and data
  • Input and level switches – enabled internal pull-up resistor in Arduino.
  • CO2 – no special circuitry required. Data sheet here
  • Flow sensor – no special circuitry required. How-to here
  • Added 1000uF electrolytic capacitor between 5v and DC ground handle potential inrush currents
  • Added 100nF ceramic capacitor between 5V and DC ground to filter out high frequencies

Next Steps

Installing a camera to take time-lapsed photos of the plants in the growing chamber as well moving to a smaller fan rather than the larger 12″ one in place is on the radar. Creating  mobile friend UI on the SCADA system is also in the queue.

Arduino based Plant LED Lighting – Iteration 1

After years of procrastination, the itch to get into hydroponics needed attention. Before jumping headfirst into the unknown, a quick experiment to see how the plants responded to neopixel LED strips was in order. As such, I’ve put the MEAN stack exploration on hold.


Can the neopixel LED strips provide enough lighting to grow herbs and other leafy vegetables?

Materials Used

Putting it Together

The following diagram illustrates the wiring.  The LM35 when used with other analog inputs leads to erratic readings. The capacitor stabilizes things.

The software is straight forward with the xbee operating using AT mode rather than API mode.  For now, I used modbus to communicate to Mango and for giggles VT-Scada. More on that in a future post as the IIoT speak I hear from certain vendors — not the two mentioned–make me cringe knowing what they have under the hood.

Software Feature List

  • set time from host via modbus  or terminal console
  • set lights on time via modbus or terminal console (default 18 hrs on)
  • set lights off time via modbus or terminal console (default 6 hrs off)
  • set duty cycle via modbus or terminal console
  • set duty cycle period via via modbus or terminal console
  • get temperature via via modbus or terminal console
  • get soil moisture via via modbus or terminal console
  • force the lights on or off via modbus or terminal console
  • save/load/restores settings to/from EEPROM

Modbus was used as I already had a SCADA host running. It could have been xbee API or bluetooth. Having done both, this is relatively easy to refactor the code later.

The code can be found at Not the prettiest code yet it it does the job for this experiment.

Periodically changing the red/blue ratio aka duty cycle between 70-95% red with the remaining in blue light tainted the experiment. Regardless, it is logged in the SCADA/HMI host for further analysis.  Interestingly, the research around  LED-based plant lighting is growing along with plenty of do-it-yourselfers experimenting.

Lessons Learned

On the Mega front, the Chinese knock-off ended up with causing more trouble that they’re worth. Problems included the following:

  •  voltage regulator fried
  • TX1 via the header pin did not work
  • headers were loose
  • finding a driver took extra goggling

Needless to say,  I ended up purchasing the real one.

Wiring xbees on breadboards gets old fast. The current setup consists of switches to commission/reset and  a potentiometer to vary the input voltage for testing a device. Nevertheless, I  purchased the wireless connectivity kit  (S2C) and the pro version of the xbee  to facilitate the configuration and program some custom functionality in the xbee in the future. Highly recommended if xbee development is on the radar. BTW, digikey Canadian or US site offer great service and fast delivery. I’ve ordered from them several times.



The basil and oregano took a couple of weeks to germinate followed with a slow growth rate.  In contrast to what others are doing, the growth rate falls far short with expectation.

Leafy Vegetables

The kale and arugula germinated in 3 days and grew relatively fast. The weak stems could be attributed to the LED’s . I’ve planted some outside as well and will compare the stem sizes with the indoor ones.

Minor Changes

The addition of a fan to create a light  breeze led to stronger stems. After a couple of weeks of circulation, the arugula and kale stems seemed stronger. The basil grew and looked healthy yet remained small. When compared to their outdoor counterparts, the healthier looking indoor basil prevailed.

Next Steps

There seems to be some confusion out there between lumens and pars. I read about people only measuring lumens for plants and scratch my head.  Consequently,  I like ChilLED‘s pitch in positioning their lighting products as well an intro-101 from Lush Lighting.

Incidentally, a buzz exists stating the effects of UV could lead  to ‘certain’ plants to produce more THC. Note, I am not interested growing those plants and just want to grow edibles all year round.  At any rate,  I think the root cause revolves around the low LED pars and power rather than the effects of different soil, nutrients, and seeds.

In short, I’m considering using ChilLED for sourcing my lighting needs provided that  controlling the output of the various channels without using their controller remains feasible.  Note  growmay5 provides some interesting vlogs on this as well as other topics around LED plant lighting.

Altogether, I’m satisfied with experiment and how quickly I could mash up a solution. Hydroponics is the next step with better LED lighting and queued for later this year as a project.



Temporary setup


Slapped together hardware



Bike LED Vest Swift 3

Earth Day is is here. Tonight, organizers set up a night time a bike ride encouraging creative ways to make yourself seen.  My daughter wanted to wear my LED vest and assumed it was just a matter of lighting it up. Needless to say, the iOS app crashed. Considering I  never tested it out with iOS 10.3, it was high time to start troubleshooting.

Because of the fact I used the objective-C BLE libs, the crash precipitated the move 100% swift solutions. For the purpose of this exercise, I forked the current the swift version and patched it to use XCGLogger, compiled it for swift 3.0, and added a couple of delegates for my own app. The changes can be found  at the forked site.

Incidentally, the crash was attributed parsing JSON results from a YML query to yahoo. The service URL changed and I cleaned up the code so it would not crash in the future should it change again.