Featured

Python Remote Gate Monitor

Here was the issue.. We have three sight hounds, a Whippet and two Silken Windhounds. When we bought our house a year ago, we had fencing put up to keep said hounds safe and so they wouldn’t bother the neighbors. We installed a ten foot gate in the back of the property so I could drive various vehicles through it. We cannot see the gate from the house and thus, can’t tell quickly if the gate is opened or closed. Sometime we have contractors come work on our shop building and we always tell them to make sure the back gate is closed, but sometimes they fail to close it.

I needed a way to monitor the gates state remotely and I am a nerd so I built something…

Currently all of my monitoring and home automation is in Home Assistant. I have Zigbee Window and Door sensors in the house that work really well. This particular gate is just out of range for the Zigbee hub, so I needed another solution. Add to the complexity, I need it to run on batteries and have a solar panel for charging. The ESP32 chip has a co-processor and supports a deep sleep mode which would be very handy…

I ordered up the cheapest ESP32 boards I could find, the ESP32-S2. This will prove to be a mistake in the long run. Long story short, I did get Micropython loaded on the board, but the power reading I was getting in Deep Sleep was not great. After further reading and research, I found that the ESP32-S2 chip does not really support DeepSleep.. Sigh.. So, I needed something else. I decided on the ESP32 Mini as it was a small footprint and said it was a normal ESP32 chip which would support Deep Sleep.

ESP32 – S2 No Go!
ESP32MINI for the win!

I ordered up an ESP32 Mini board and loaded Micropython on it. I also had some Reed switches already that I planned to use at the trigger. I started coding with the thought, this should be simple. Not much logic is needed, just tell me if the gate is open and go to sleep.. Well, not so fast there smarty pants. (I never claimed I was smart).  

Here was the problem… I needed the ESP32 to:

Deep Sleep

Gate open – Connect and send the MQTT Open message

Wait for the gate to close

Gate closed – Connect and send the MQTT Closed message

Deep Sleep

This is the part of the story where I admit that I am a novice Python programmer. I know enough to waste hours on a solution someone with skills could probably do in a few minutes. The Deep Sleep function seemed to be pretty straight forward. Set a trigger and tell it to go to sleep. When it’s triggered, the ESP32 resets and runs the boot.py program. Seems easy right? 

So, I soldered up the reed switch between the IO04 pin and ground. I planned on pulling up the IO04 pin and when the gate was closed, the reed switch would be closed which would result in a 0 or LOW reading. When the gate opened, the reed switch would separate the connection from ground and provide a 1 or HIGH reading. I also connected up a LIPO charging board and the solar panel. Here is what that looked like..

Now, about that Micropython code… I went in strong. Feeling like this was going to be a fun short little session using the skills I had learned from the Pool Controller project. That is still going strong by the way. Pretty happy with that. I digress, back to this fiasco. I loaded up some libraries,  setup the WiFi network connection, set a couple of variables, setup the MQTT server IP variable, setup my Pin for IO04, setup my secrets file. Everything was going smooth as silk.. I set the variables for the MQTT topic and connection. Whew, I was feeling like straight FIRE!

I then wrote a couple of functions, one for when the gate was opened and one for when the gate was closed. Each function called the variables for MQTT, Made the connection and sent the respective 0 or 1 to the topic variable. Yep, I got this Python stuff… Then I wrote the if statement and things started falling apart. The problem was, if the ESP32 was asleep and was triggered, it just does a soft reset and runs like a reboot running the code again. So if I just used an if statement, the gate would open, the trigger would happen and re-run boot.py and the if statement said, if pin = 1 (the gate was open) than connect to the MQTT server and send “open” to the topic. If the pin was 0 (gate closed) then connect to the MQTT server and send “open” to the topic. Then go to Deep Sleep. Sounds correct right? Yeah well, the issue is that you can only trigger the wake up with a HIGH or a LOW (0 or 1) not ANY change. So I figured out I had to wait when the gate was open.

It’s the wait that turned out to be trouble..

I will spare you the countless Google searches for ESP32 wait for pin input. Just know that I lost part of my life in that hell. Life I will never get back… EVER!

I found that some were using uasyncio which basically assigns tasks and allows you to wait until a task completes before moving forward. That did the trick and I tested everything out and I was good. No smoke and the messages were being sent. I did have to use some sleep statements to slow things down a bit. Without them, the MQTT commands were not being sent. Things were happening too fast.  So here it is.. If you would like to build your very own WiFi Gate/Door sensor and you have MQTT and Home Assistant.

import network
import esp32
import uasyncio
from secrets import secrets
from machine import Pin
from time import sleep
from machine import deepsleep
from umqtt.simple import MQTTClient

switch = Pin(04, Pin.IN, Pin.PULL_UP)
esp32.wake_on_ext0(pin = switch, level=1)
ssid = secrets['ssid']
pw = secrets['pw']

#Connecting to wifi
wlan = network.WLAN(network.STA_IF)
if not wlan.isconnected():
    print('connecting to network...')
    wlan.active(True)
    wlan.connect(ssid, pw)
    while not wlan.isconnected():
        pass
print('network config:', wlan.ifconfig())
sleep(2)

#MQTT variables
mqtt_server = "xxx.xxx.xxx.xxx" #<-Replace the x's with your mqtt server IP
topic_pub = b'homemade/gate_sensor'
client = MQTTClient("Gate_Keeper", mqtt_server)

async def main():
    if switch.value() == 0:
        sleep(4)
        gate_closed()
        print("Gate closed, sleeping")
    else:
        print("Gate Open")
        sleep(4)
        gate_open()
        print("Waiting")
        await check_gate()
        print("Gate Closed")
        sleep(4)
        gate_closed()
        sleep(2)
        
def gate_open():
    client.connect()
    client.publish(topic_pub, "1")
    print("Open Published")
    print(switch.value())
    client.disconnect()
    
def gate_closed():
    client.connect()
    client.publish(topic_pub, "0")
    print("Closed Published")
    print(switch.value())
    client.disconnect()
    
async def check_gate():
    while switch.value() == 1:
        sleep(2)

uasyncio.run(main())
print("sleeping")
deepsleep()

Note: I do not know if this is the most efficient way to achieve this. If someone smarter than this old hillbilly has a better way, I would love to see the code. I am always up for a code review.

Here it is all mounted up in a custom 3D printed enclosure I designed in Fusion 360 and printed.

And here it is all mounted on the gate. Note I added a part that extended the magnet out from the gate.

Setup in Home Assistant

Problem Solved…. For now. 🙂

Featured

Double, No… Triple Dog Dare Ya!

In the immortal words of Micky O’Neil from the movie Snatch, “Do you like Dags?”

First, let me show you these little hooligans….

Sir Nigel Crazy Legs
Mia Crazy Legs

So it’s been a while and a lot has happened… We had to put our Border Collie (Isis the Terrorist) down in January 2021. Not a fun time to say the least. The Supreme Commander (my wife) and I decided very quickly that we just could not live without a dog. We talked at length about what to do. I quickly picked a breed and after many hours of research we found a puppy in California. The puppy wasn’t ready to be picked up for a few weeks so we had some time to plan. In the mean time, the Supreme Commander decided that she was going to get a puppy too. It was at this point I panicked a little. I mean, you can’t tell the Supreme Commander what to do. Not if you want to sustain blissful tolerance anyway. She picked her breeder and ordered up puppy #2. What does this all have to do with a project you say…. Well, let me tell ya.

Now, we all know that a dog can be the biggest bed hogs ever and I do not do well unless I get my beauty sleep. I came up with a plan. I would build us a double queen bed prior to the dogs arriving. I coined this new bed the, “Double Triple dog dare ya bed”. I designed it all out in Fusion360 and was ready to start building when the Supreme Commander let me know she found a bed that we could buy (two of) if I could figure out a way to join them. I looked at the specs online and decided that was probably not the most fun route, but I would go along with it.

The beds arrived, I assembled them and got them joined together side by side. The Supreme Commander is great with sewing so she took two fitted sheets and sewed them together to make one huge fitted sheet. We didn’t want puppy legs to get bent in the space between the mattresses.

And, there you have it, the Double dog dare ya bed was born!

Update in 2023… We have since moved to Arkansas from Arizona and have added yet another Silken Windhound. Also note that no matter how much bed area you have, sight hounds will find a way to position themselves in a manner that you have 1.5 inches to sleep, right on the edge. All this while the other person in bed has no dogs around them and is in effect “starfished” across their side of the bed. Sigh… Here is an updated picture of the “Hillbillies” as they are affectionately known.

The Hillbillies, Sir Nigel Crazy Legs (top), Mia Crazy Legs(left) and Ever Crazy Legs(right)
Featured

Golfball feet

New 3D Printer and designing noise reducing feet

I have been wanting a new 3D printer for a couple of years now. My first delve into the world of 3d modeling and design was when I purchased the cheapest piece of junk printer on the market, the ANET A8. Straight from the hands of the Chinese. The initial warnings I read about the ANET said that the printer had a tendency to catch fire. So, what seemed like $10,000 in upgrades later and countless hours, I had it printing pretty dang good. The problem was, it was LOUD! So, time to upgrade to a new (cheap) printer.

After a fair bit of research, I decided on the Tevo Tarantula Pro. It had a larger build plate and the frame is constructed from extruded aluminum (I always say it AL-YOU-MIN-U- IUM like our friends across the pond). It also has rollers instead of linear bearings. Of course I did some upgrades as I was building it, duh!

It came completely disassembled which is exactly the way I like it. I planned on upgrading the motor drivers to the TMC2208 trinamic servo drivers for a couple reasons. One, they are super quiet and two the stock drivers only support 16 microsteps where the TMC2208’s support 256 microsteps. No brainer. I added a BLTouch inductive bed leveler which I have on my POS printer. Lastly, I added a MOSFET for the heated bed so that there is less chance of it catching on fire.

After getting everything assembled, I noticed that the feet that came with it were hard plastic. Being someone who is well a-tune to sonic harshness, I thought there had to be another way. I started thinking about how I could isolate the printer from the table it’s will be sitting on. Generally, the tables act as an amplifier for any vibrations coming from the printer. I came to the conclusion that a harder foam would probably be best to dampen the vibrations. First I looked for a deal on stress balls. People must be really stressed out because those things are pricy. What I ended up with were foam practice golf balls. for about $0.25 a piece.

On to Fusion 360 to design some stuff up. This is what I came up with… Note that I printed a couple versions to test and this was my 3rd version.

After installing the feet, the printer is darn near silent (except for the fans) I am pretty pleased with these feet and I will put them on the POS printer soon. I will link to my Thingiverse post if you would like the files to print some for yourself. They can be adapted to almost anything.

https://www.thingiverse.com/thing:4676290

Here are some more pics of the feet installed.

Featured

T5 light fixture mounts

(3D Printing)

In my advancing age, vision is becoming more of a hinderance than a benefit. Working on seemingly microscopic electronic parts is a struggle and proper illumination is becoming a requirement. Don’t worry kids, you will get here someday and see what I mean.

Recently, I bought these cheap LED T5 lights for my garage from Amazon.

https://www.amazon.com/gp/product/B07DNPL2VC/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1

I mounted them in the garage using the really shitty mounts that come with the lights which worked okay I guess.

With one spare light left over and needing to increase my visual ability at my bench, it just made sense to utilize the extra photons. The only thing is, I did not want to make any holes in the wall. The light housing itself is made of aluminum and the entire light weighs less than a pound. I wanted to make a hanger that would use command strips to hold it up.

I got to work in Fusion 360 and designed a bracket that would slip over the light fixture. I went to measuring away. With that completed I went off to the 3D printer and a couple hours later, I had a mount. DOH! The end caps of the fixture were slightly larger than the rest of the light and the newly printed piece of garbage would not fit over the end cap. Back to Fusion 360 and increased the ID of the part to match the end caps.

Below is the STL file if’n you feel the need to mount your own T5 light fixture you can just print this. It will save you the time of ef’ing up the initial design.

https://www.thingiverse.com/thing:4669533

A couple more hours later I had two new mounts in glorious white PLA.

I slid the new mounts onto the fixture and placed command strips on the mounts and pressed it to the wall. Boomshakalaka! It works great, I can see much better and there are no further perforations in my walls.

Featured

Raspberry Pi Pool Controller

It’s been a while since my last project. I have been working on something a bit more complicated this time. Here is the story…

Once upon a time there were three nerdy friends. Two of these nerdy friends owned homes with pools and over the years accumulated a plethora of information about how they work, how they are controlled, etc. Let’s call them Jorge and Scott for the purposes of this build. These two nerdy homeowners had hacked their own pool controller systems and connected at least some control to Home Assistant. So, when nerdy friend number three (let’s call him Jemal for the purposes of this build) bought a new house with a pool, naturally, the other two nerds were called in to assist. Jemal wanted to be like the cool kids and have his pool controlled by HA as well because the majority of his other electronics were already controlled by HA.

Here is the run-down of Jemal’s current pool. It’s very basic for a saltwater pool. It uses a mechanical timer to control power to the pump and salt system. That’s about it… The salt system is a knock off of a Goldline system that we couldn’t find much documentation on.

After reviewing the current pool and seeing how we could automate it as is (not much) a link for a new controller was sent to Jemal. Upon seeing the price for the new controller, Jemal proclaimed he was faint, and he had a significant amount of pain coming from his backside. Presumably because of the cost of the new system and the lack of lubrication.

several conversations later, Scott (me) interjected, “you know, we could just build a controller that does all of the same things the expensive one does for a fraction of the cost”. Have you ever said something and then instantly asked yourself, “Why did I just suggest that?”. Okay, I am not alone.

So, it had been decided. We were building a pool controller. My mind started swirling with the logic involved and all the questions about how this could be accomplished. Everyone who knows me also knows that I am not a programmer. I do not have the brain for it. I started by concentrating on the electronics involved. Gathering requirements didn’t take long. I compared what we would build to what Jorge and I already had. The majority of a pool controller is just relay circuits. A lot of relay circuits….

I started some research and came across this chip called the ULN2803 Darlington Transistors Eight NPN Array. I knew that a relay circuit required a diode to stop run away when latching the relay. It sounded promising as I didn’t really want to put diodes on the board if I didn’t have to. I do not know why. So, along with some connectors, this chip and some relays, I started designing the circuit board. I added the Raspberry Pi, the UNL2803 and I picked the TE Connectivity Potter & Brumfield Relays / T9AS1D12-12 because they have voltage rating of 277V on the contact side and powered by 12V DC for the coil. I finished all 8 of those circuits and then moved to the pool and air temperature. For that I wanted to allow the air temp sensor to live outside any enclosure since my air temperature often reads over 125 degrees F in the summer when the real air temp is ONLY around 115. I built the circuit using common DS18B20 probes with stainless steel ends. Jorge suggested I add a way to turn on each channel manually, so I added eight toggle switches for the ULN2803. I made sure that the contact side traces were as big as possible as the pump was rated for 10 – 12 amps. This is the only thing I am worried about. If it burns we will go with DIN Rail mounted industrial relays. More on that later when we see if we let the smoke out. I received the ULN2803 chips and I threw together a test using some other spare parts I had laying around. Sure enough, the ULN2803 didn’t fry and the relay didn’t catch on fire. I ordered up the parts and sent the boards to be made.

This is where we stop and talk about how hard it is, for me anyway, to order the correct parts. I think I spend a lot of time researching what’s available to me via several electronics distributors and have become a master of making the new device sketches and footprints in EASYEDA. The fact is that, just like Home Depot, you can never just go once for what you need for a project. Maybe I am not that good at sourcing. I digress.

Here is the circuit and the board(Rev3) see below…Sigh

If you would like the gerber file please email me. I also have a few spare boards for purchase to help a brother out with his project addiction. Shhh, don’t tell my wife.

We decided that we would use a Raspberry Pi as our micro-controller (GPIO) backbone. We chose this because it’s less(meaning none) C coding than using an ESP32 based controller and Python would be more flexible. A choice that later was confirmed to be the right decision.

While the parts were on their way, I started on the software. Since we were using the Raspberry Pi, I started with Python and used the Flask framework to build an API. My thought was that it would be easy to control using a REST API. I used the RPi.GPIO module to control the eight GPIO pins, one for each channel. I create a toggle function and a status function for each pin. I used the Flask jsonify module to return the state of the pin in JSON format. Then I thought, MQTT! It would be sweet to control it via MQTT as well as an option. So I added the Flask_mqtt module and coded the MQTT toggle and status functions. Using a separate Python script (because of the for loop) I created the MQTT temperature script. I used the 1Wire port on the Raspberry Pi to connect both sensors using a 4.7k resistor between 3.3V and the data bus. Then I used the W1ThremSensor module to read the temp every minute and publish to a MQTT topic using the paho mqtt module. After that I added some variables to make it easier to customize things later and added logging.

I found some potential issues with my original code, no surprise there. I also got a code review from a friend that gave me some suggestions for some improvement.

The first issue I had is that the Salt system should NEVER be turned on without the pump running. So I figured it would be simple to add that logic into the code. Well, for some maybe. I started with the MQTT portion as I would have to edit both the REST and MQTT logic. After an hour or so I came up with something that worked. I grabbed the pump state and read the MQTT data provided by the request to the topic and used that in the if else statement. If the pump state was off(0) and the MQTT data was “on” I set the SALT state to OFF(LOW), verified the SALT state and published that back to the Salt State topic. The I sent a warning to the log
Saying that you can’t turn on the salt without the pump running. Else if the pump state was on(1) and the MQTT data was “off” then I set the Salt to LOW (0), verify the SALT state and publish that state to the SALT State topic and finally log that the Salt was turned off. Else if the pump state is on(1) and the MQTT data is “on” then turn the SALT to HIGH(1), verify the SALT State and publish that state to the SALT State topic. WHEW! My brain hurts. Step 1, complete.

I also added code that turns on and off the Salt when the pump is triggered so that when the pump is running, the pool is getting chlorine. I did that for both the REST and MQTT functions.

Now onto the logic for the REST SALT function. Using the same If else logic I used in the MQTT function I came up with this. First I check the state of both the PUMP and the SALT and sent that those states to their respective variable.

salt_status=int((GPIO.input(SALT)))
pump_status=int((GPIO.input(PUMP)))

Then, I started the if else statement. If both of these were HIGH(1), then change the SALT state to LOW(0), verify the SALT state and publish the SALT state to the SALT status MQTT topic. I did this so both the REST and the MQTT would know what the states were so that there could be no confusion about the states if changes were made to any state. Then I logged the SALT state.

Then a few else if statements, if the pump status is on(1) and the salt status is off(0) then turn on the salt, verify the state, publish that state to the MQTT topic and log it. Else if the pump state was off(0) and the salt state if off(0) send a warning and do nothing. The salt can’t be turned on without the pump running and log it. Else if the pump is off(0) and the salt is on(1), turn the SALT off and send a warning that the salt was running without the pump and to check your system for damage. It should never happen, but I thought it was a good idea to put it in there as a fail safe. Then logged that as a warning.

The last piece was the two script issue. One for the pool control and the other for the temperature probes. After reviewing the code, a friend suggested that I use the module apscheduler to schedule a function to check the temperature probes. I installed that module, moved the code from the temperature script to a new function in the pool control program and setup the scheduler. Holy crap it worked!

With my new one script solution I moved on to setting the program to start as a service at system boot and we were off to the races.

Here is the code.

from flask import Flask, request, jsonify
import RPi.GPIO as GPIO
from flask_mqtt import Mqtt
import time
from w1thermsensor import W1ThermSensor
import logging
from apscheduler.schedulers.background import BackgroundScheduler

app = Flask(__name__)

logging.basicConfig(filename='/var/log/Frosty_app.log', level=logging.INFO,
                    format='%(asctime)s    %(levelname)s   %(message)s' ,
                    datefmt='%d-%m-%Y %H:%M:%S') 

temp_sensor1_ID = '3c01d607e0e2' #change this to be an ID from one of you 1-wire devices in /sys/bus/w1/devices 
temp_sensor2_ID = '3c01d6075170' #change this to be an ID from one of you 1-wire devices in /sys/bus/w1/devices
sensor1_offset = 3 # This is where you calibrate the temperature sensor1 to assure accuracy 
sensor2_offset = 3 # This is where you calibrate the temperature sensor2 to assure accuracy 
temp_sched = 3600 # time period between tempeature readings for scheduler


# Setup MQTT broker connection
app.config['MQTT_BROKER_URL'] = '192.168.10.115'
app.config['MQTT_BROKER_PORT'] = 1883
app.config['MQTT_USERNAME'] = ''
app.config['MQTT_PASSWORD'] = ''
app.config['MQTT_REFRESH_TIME'] = 1.0  # refresh time in seconds
mqtt = Mqtt(app)


# Get inital temperature reading before scheduler starts
temp_sensor1 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor1_ID)
temp_sensor2 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor2_ID)
sensor1_temp = int(temp_sensor1.get_temperature(W1ThermSensor.DEGREES_F) + sensor1_offset)
sensor2_temp = int(temp_sensor2.get_temperature(W1ThermSensor.DEGREES_F) + sensor2_offset)
mqtt.publish('temp/sensor1', sensor1_temp)
mqtt.publish('temp/sensor2', sensor2_temp)
log_sensor1 = str(sensor1_temp)
logging.info("sensor1: " + log_sensor1)
log_sensor2 = str(sensor2_temp)
logging.info("sensor2: " + log_sensor2)


# Define the GPIO ports that each channel will use.
PUMP = 26
SALT = 19
LIGHT = 13
AERATOR = 6
AUX1 = 5
AUX2 = 22
AUX3 = 27
AUX4 = 17

#Inital set all MQTT to off
mqtt.publish('state/pump', 0)
mqtt.publish('state/salt', 0)
mqtt.publish('state/light', 0)
mqtt.publish('state/aerator', 0)
mqtt.publish('state/aux1', 0)
mqtt.publish('state/aux2', 0)
mqtt.publish('state/aux3', 0)
mqtt.publish('state/aux4', 0)

# Setup each of the GPIO Ports
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(PUMP, GPIO.OUT, initial=0)
GPIO.setup(SALT, GPIO.OUT, initial=0)
GPIO.setup(LIGHT, GPIO.OUT, initial=0)
GPIO.setup(AERATOR, GPIO.OUT, initial=0)
GPIO.setup(AUX1, GPIO.OUT, initial=0)
GPIO.setup(AUX2, GPIO.OUT, initial=0)
GPIO.setup(AUX3, GPIO.OUT, initial=0)
GPIO.setup(AUX4, GPIO.OUT, initial=0)


# ########################    MQTT   ########################## 


def mqtt_sensor_publish():
     temp_sensor1 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor1_ID)
     temp_sensor2 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor2_ID)
     sensor1_temp = int(temp_sensor1.get_temperature(W1ThermSensor.DEGREES_F) + sensor1_offset)
     sensor2_temp = int(temp_sensor2.get_temperature(W1ThermSensor.DEGREES_F) + sensor2_offset)
     mqtt.publish('temp/sensor1', sensor1_temp)
     mqtt.publish('temp/sensor2', sensor2_temp)
     log_sensor1 = str(sensor1_temp)
     logging.info("sensor1: " + log_sensor1)
     log_sensor2 = str(sensor2_temp)
     logging.info("sensor2: " + log_sensor2)

scheduler = BackgroundScheduler()
scheduler.add_job(func=mqtt_sensor_publish, trigger="interval", seconds=temp_sched)
scheduler.start()

mqtt.subscribe('toggle/pump')
@mqtt.on_topic('toggle/pump')
def handle_mytopic(client, userdata, message):
   app = Flask(__name__)    
   with app.app_context():
    status=int(GPIO.input(PUMP))
    if status == 0 and message.payload.decode() == "on":
        GPIO.output(PUMP, GPIO.HIGH)
        GPIO.output(SALT, GPIO.HIGH)
        verify_pump_state = GPIO.input(SALT)
        verify_salt_state = GPIO.input(PUMP)
        mqtt.publish('state/pump', verify_pump_state)
        mqtt.publish('state/salt', verify_salt_state)
        logging.info("MQTT - Pump turned on")
        logging.info("MQTT - Salt turned on")
    elif status == 1 and message.payload.decode() == "off": 
        GPIO.output(PUMP, GPIO.LOW)
        GPIO.output(SALT, GPIO.LOW)
        verify_pump_state = GPIO.input(PUMP)
        verify_salt_state = GPIO.input(SALT)
        mqtt.publish('state/pump', verify_pump_state)
        mqtt.publish('state/salt', verify_salt_state)
        logging.info("MQTT - Pump turned off")
        logging.info("MQTT - Salt turned off")
    else:
        logging.info("MQTT - Pump - Nah Bruh")

mqtt.subscribe('toggle/salt')
@mqtt.on_topic('toggle/salt')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        salt_status=int(GPIO.input(SALT))
        pump_status=int(GPIO.input(PUMP))
        if pump_status == 0 and message.payload.decode() == "on":
            GPIO.output(SALT, GPIO.LOW)
            verify_state = GPIO.input(SALT)
            mqtt.publish('state/salt', verify_state)
            logging.info("MQTT - WARNING - You can't turn on the Salt system without the pump running") 
        elif pump_status == 1 and message.payload.decode() == "off":
            GPIO.output(SALT, GPIO.LOW)
            verify_state = GPIO.input(SALT)
            logging.info("MQTT - Salt turned off")
            mqtt.publish('state/salt', verify_state)
        elif pump_status == 1 and message.payload.decode() == "on":
            GPIO.output(SALT, GPIO.HIGH)
            verify_state = GPIO.input(SALT)
            logging.info("MQTT - Salt turned on")
            mqtt.publish('state/salt', verify_state)
        else:
            logging.info("MQTT - Salt - Nah Bruh")

mqtt.subscribe('toggle/light')
@mqtt.on_topic('toggle/light')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        status=int(GPIO.input(LIGHT))
        if status == 1 and message.payload.decode() == "off":
            GPIO.output(LIGHT, GPIO.LOW)
            verify_state = GPIO.input(LIGHT)
            mqtt.publish('state/light', verify_state)
            logging.info("MQTT - Light turned off")
        elif status== 0 and message.payload.decode() == "on":
            GPIO.output(LIGHT, GPIO.HIGH)
            verify_state = GPIO.input(LIGHT)
            mqtt.publish('state/light', verify_state)
            logging.info("MQTT - Light turned on")
        else:
            logging.info("MQTT - Light - Nah Bruh")

# -- status/update is used when Home assistant restarts. 
# -- It re-publishes the current state of all channels and temperature probes

mqtt.subscribe('status/update')
@mqtt.on_topic('status/update')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        light_status=int(GPIO.input(LIGHT))
        pump_status=int(GPIO.input(PUMP))
        salt_status=int(GPIO.input(SALT))
        aerator_status=int(GPIO.input(AERATOR))
        aux1_status=int(GPIO.input(AUX1))
        aux2_status=int(GPIO.input(AUX2))
        aux3_status=int(GPIO.input(AUX3))
        aux4_status=int(GPIO.input(AUX4))

        mqtt.publish('state/light', light_status)
        mqtt.publish('state/pump', pump_status)
        mqtt.publish('state/salt', salt_status)
        mqtt.publish('state/aerator', aerator_status)
        mqtt.publish('state/aux1', aux1_status)
        mqtt.publish('state/aux2', aux2_status)
        mqtt.publish('state/aux3', aux3_status)
        mqtt.publish('state/aux4', aux4_status)

        temp_sensor1 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor1_ID)
        temp_sensor2 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor2_ID)
        sensor1_temp = int(temp_sensor1.get_temperature(W1ThermSensor.DEGREES_F) + sensor1_offset)
        sensor2_temp = int(temp_sensor2.get_temperature(W1ThermSensor.DEGREES_F) + sensor2_offset)
        mqtt.publish('temp/sensor1', sensor1_temp)
        mqtt.publish('temp/sensor2', sensor2_temp)

        logging.info("MQTT Updated state info")

mqtt.subscribe('toggle/aerator')
@mqtt.on_topic('toggle/aerator')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        status=int(GPIO.input(AERATOR))
        if status == 1 and message.payload.decode() == "off":
            GPIO.output(AERATOR, GPIO.LOW)
            verify_state = GPIO.input(AERATOR)
            logging.info("MQTT - Aerator turned off")
            mqtt.publish('state/aerator', verify_state)
        elif status== 0 and message.payload.decode() == "on":
            GPIO.output(AERATOR, GPIO.HIGH)
            verify_state = GPIO.input(AERATOR)
            logging.info("MQTT - Aerator turned on")
            mqtt.publish('state/aerator', verify_state)
        else:
            logging.info("MQTT - Aerator - Nah Bruh")

mqtt.subscribe('toggle/aux1')
@mqtt.on_topic('toggle/aux1')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        status=int(GPIO.input(AUX1))
        if status == 1 and message.payload.decode() == "off":
            GPIO.output(AUX1, GPIO.LOW)
            verify_state = GPIO.input(AUX1)
            logging.info("MQTT - Aux1 turned off")
            mqtt.publish('state/aux1', verify_state)
        elif status== 0 and message.payload.decode() == "on":
            GPIO.output(AUX1, GPIO.HIGH)
            verify_state = GPIO.input(AUX1)
            logging.info("MQTT - Aux1 turned on")
            mqtt.publish('state/aux1', verify_state)
        else:
            logging.info("MQTT - Aux1 - Nah Bruh")

mqtt.subscribe('toggle/aux2')
@mqtt.on_topic('toggle/aux2')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        status=int(GPIO.input(AUX2))
        if status == 1 and message.payload.decode() == "off":
            GPIO.output(AUX2, GPIO.LOW)
            verify_state = GPIO.input(AUX2)
            logging.info("MQTT - Aux2 turned off")
            mqtt.publish('state/aux2', verify_state)
        elif status== 0 and message.payload.decode() == "on":
            GPIO.output(AUX2, GPIO.HIGH)
            verify_state = GPIO.input(AUX2)
            logging.info("MQTT - Aux2 turned on")
            mqtt.publish('state/aux2', verify_state)
        else:
            logging.info("MQTT - Aux2 - Nah Bruh")

mqtt.subscribe('toggle/aux3')
@mqtt.on_topic('toggle/aux3')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        status=int(GPIO.input(AUX3))
        if status == 1 and message.payload.decode() == "off":
            GPIO.output(AUX3, GPIO.LOW)
            logging.info("MQTT - Aux3 turned off")
            verify_state = GPIO.input(AUX3)
            mqtt.publish('state/aux3', verify_state)
        elif status== 0 and message.payload.decode() == "on":
            GPIO.output(AUX3, GPIO.HIGH)
            verify_state = GPIO.input(AUX3)
            logging.info("MQTT - Aux3 turned on")
            mqtt.publish('state/aux3', verify_state)
        else:
            logging.info("MQTT - Aux - Nah Bruh")

mqtt.subscribe('toggle/aux4')
@mqtt.on_topic('toggle/aux4')
def handle_mytopic(client, userdata, message):
    with app.app_context():
        status=int(GPIO.input(AUX4))
        if status== 1 and message.payload.decode() == "off":
            GPIO.output(AUX4, GPIO.LOW)
            verify_state = GPIO.input(AUX4)
            logging.info("MQTT - Aux4 turned off")
            mqtt.publish('state/aux4', verify_state)
        elif status == 0 and message.payload.decode() == "on":
            GPIO.output(AUX4, GPIO.HIGH)
            verify_state = GPIO.input(AUX4)
            logging.info("MQTT - Aux4 turned on")
            mqtt.publish('state/aux4', verify_state)
        else:
            logging.info("MQTT - Aux4 - Nah Bruh")


#########################    REST TOGGLE   #############################


# PUMP Toggle 
@app.route('/toggle/pump/', methods=['GET'])
def pump_toggle():
    status = GPIO.input(PUMP) 
    if status == 1:
        GPIO.output(PUMP, GPIO.LOW)
        GPIO.output(SALT, GPIO.LOW)
        verify_pump_state = GPIO.input(PUMP)
        verify_salt_state = GPIO.input(SALT)
        mqtt.publish('state/pump', verify_pump_state)
        mqtt.publish('state/salt', verify_salt_state)
        logging.info("REST API - Pump turned off")
        return jsonify({"message": "Pump successfully turned off"})
        return jsonify({"message": "Salt successfully turned off"})
    elif status == 0:
        GPIO.output(PUMP, GPIO.HIGH)
        GPIO.output(SALT, GPIO.HIGH)
        verify_pump_state = GPIO.input(PUMP)
        verify_salt_state = GPIO.input(SALT)
        mqtt.publish('state/pump', verify_pump_state)
        mqtt.publish('state/salt', verify_salt_state)
        logging.info("REST API - Pump turned on")
        return jsonify({"message": "Pump successfully turned on"})
        return jsonify({"message": "Salt successfully turned on"})
    else:
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Pump Toggle")
        return jsonify({"message": "Boom! Something blew up, check your settings and try again"}, {"GPIO state": status})

# SALT Toggle
@app.route('/toggle/salt/', methods=['GET'])
def salt_toggle():
    salt_status=int((GPIO.input(SALT)))
    pump_status=int((GPIO.input(PUMP)))
    if pump_status == 1 and salt_status == 1:
        GPIO.output(SALT, GPIO.LOW)
        salt_state=GPIO.input(SALT)
        logging.info("REST API - Salt turned off")
        mqtt.publish('state/salt', salt_state)
        return jsonify(message="Salt system successfully turned off")
    elif pump_status == 1 and salt_status == 0:
        GPIO.output(SALT, GPIO.HIGH)
        salt_state=GPIO.input(SALT)
        logging.info("REST API - Salt turned on")
        mqtt.publish('state/salt', salt_state)
        return jsonify(message="Salt system successfully turned on")
    elif pump_status == 0 and salt_status == 0:
        GPIO.output(SALT, GPIO.LOW)
        salt_state=GPIO.input(SALT)
        logging.info("REST API - Warning - Can not turn on Salt system without the pump running. Salt system off")
        mqtt.publish('state/salt', salt_state)
        return jsonify(message="Warning - Can not turn on Salt system without the pump running. Salt system off")
    elif pump_status == 0 and salt_status == 1:
        GPIO.output(SALT, GPIO.LOW)
        salt_state=GPIO.input(SALT)
        logging.info("REST API - Warning - Your salt system was found to be on without the pump. Salt off, check you system for possible damage")
        mqtt.publish('state/salt', salt_state)
        return jsonify(message="Warning - Your salt system was found to be on without the pump. Salt off, check you system for possible damage")
    else:
        salt_state=GPIO.input(SALT)
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Salt Toggle")
        return jsonify(message="Boom! Something blew up, check your settings and try again", Salt_state=salt_state)

# LIGHT Toggle
@app.route('/toggle/light/', methods=['GET'])
def light_toggle():
    status = GPIO.input(LIGHT)
    if status == 1:
        GPIO.output(LIGHT, GPIO.LOW)
        light_state=GPIO.input(LIGHT)
        logging.info("REST API - Light turned off")
        mqtt.publish('state/light', light_state)
        return jsonify(message="Light successfully turned off")
    elif status == 0:
        GPIO.output(LIGHT, GPIO.HIGH)
        light_state=GPIO.input(LIGHT)
        logging.info("REST API - Light turned on")
        mqtt.publish('state/light', light_state)
        return jsonify(message="Light system successfully turned on")
    else:
        light_state=GPIO.input(LIGHT)
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Light Toggle")
        return jsonify(message="Boom! Something blew up, check your settings and try again", Light_status=light_state)

# AERATOR Toggle
@app.route('/toggle/aerator/', methods=['GET'])
def aerator_toggle():
    status = GPIO.input(AERATOR)
    if status == 1:
        GPIO.output(AERATOR, GPIO.LOW)
        aerator_state=GPIO.input(AERATOR)
        logging.info("REST API - Aerator turned off")
        mqtt.publish('state/aerator', aerator_state)
        return jsonify(message="AERATOR successfully turned off")
    elif status == 0:
        GPIO.output(AERATOR, GPIO.HIGH)
        aerator_state=GPIO.input(AERATOR)
        logging.info("REST API - Aerator turned on")
        mqtt.publish('state/aerator', aerator_state)
        return jsonify(message="AERATOR successfully turned on")
    else:
        aerator_state=GPIO.input(AERATOR)
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Aerator Toggle")
        return jsonify(message="Boom! Something blew up, check your settings and try again", Aerator_state=aerator_state)

# AUX1 Toggle
@app.route('/toggle/aux1/', methods=['GET'])
def aux1_toggle():
    status = GPIO.input(AUX1)
    if status == 1:
        GPIO.output(AUX1, GPIO.LOW)
        aux1_state=GPIO.input(AUX1)
        logging.info("REST API - Aux1 turned off")
        mqtt.publish('state/aux1', aux1_state)
        return jsonify(message="AUX1 successfully turned off")
    elif status == 0:
        GPIO.output(AUX1, GPIO.HIGH)
        aux1_state=GPIO.input(AUX1)
        logging.info("REST API - Aux1 turned on")
        mqtt.publish('state/aux1', aux1_state)
        return jsonify(message="AUX1 successfully turned on")
    else:
        aux1_state=GPIO.input(AUX1)
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Aux1 Toggle")
        return jsonify(message="Boom! Something blew up, check your settings and try again", Aux1_state=aux1_state)

# AUX2 Toggle
@app.route('/toggle/aux2/', methods=['GET'])
def aux2_toggle():
    status = GPIO.input(AUX2)
    if status == 1:
        GPIO.output(AUX2, GPIO.LOW)
        aux2_state=GPIO.input(AUX2)
        logging.info("REST API - Aux2 turned off")
        mqtt.publish('state/aux2', aux2_state)
        return jsonify(message="AUX2 successfully turned off")
    elif status == 0:
        GPIO.output(AUX2, GPIO.HIGH)
        aux2_state=GPIO.input(AUX2)
        logging.info("REST API - Aux2 turned on")
        mqtt.publish('state/aux2', aux2_state)
        return jsonify(message="AUX2 successfully turned on")
    else:
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Aux2 Toggle")
        return jsonify(message="Boom! Something blew up, check your settings and try again", Aux2_state=aux2_state)

# AUX3 Toggle
@app.route('/toggle/aux3/', methods=['GET'])
def aux3_toggle():
    status = GPIO.input(AUX3)
    if status == 1:
        GPIO.output(AUX3, GPIO.LOW)
        aux3_state=GPIO.input(AUX3)
        logging.info("REST API - Aux3 turned off")
        mqtt.publish('state/aux3', aux3_state)
        return jsonify(message="AUX3 successfully turned off")
    elif status == 0:
        GPIO.output(AUX3, GPIO.HIGH)
        aux3_state=GPIO.input(AUX3)
        logging.info("REST API - Aux3 turned on")
        mqtt.publish('state/aux3', aux3_state)
        return jsonify(message="AUX3 successfully turned on")
    else:
        aux3_state=GPIO.input(AUX3)
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Aux3 Toggle")
        return jsonify(message="Boom! Something blew up, check your settings and try again", Aux3_state=aux3_state)

# AUX4 Toggle
@app.route('/toggle/aux4/', methods=['GET'])
def aux4_toggle():
    status = GPIO.input(AUX4)
    if status == 0:
        GPIO.output(AUX4, GPIO.HIGH)
        aux4_state=GPIO.input(AUX4)
        logging.info("REST API - Aux4 turned on")
        mqtt.publish('state/aux4', aux4_state)
        return jsonify(message="AUX4 successfully turned on")
    elif status == 1:
        GPIO.output(AUX4, GPIO.LOW)
        aux4_state=GPIO.input(AUX4)
        logging.info("REST API - Aux4 turned off")
        mqtt.publish('state/aux4', aux4_state)
        return jsonify(message="AUX4 successfully turned off")
    else:
        logging.info("REST API - Boom! Something blew up, check your settings and try again - Aux4 Toggle")
        return jsonify(message="Boom! Something blew up, check your settings and try again", Aux4_state=aux4_state)


######################    REST STATUS    ########################


# # Temperature Sensor 1
@app.route('/temp/sensor1', methods=['GET'])
def temp_1():
    temp_sensor1 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor1_ID)
    sensor1_temp = int(temp_sensor1.get_temperature(W1ThermSensor.DEGREES_F) + sensor1_offset)
    return jsonify(temp_sensor1=int(sensor1_temp))

# Temperature Sensor 2
@app.route('/temp/sensor2', methods=['GET'])
def temp_2():
    temp_sensor2 = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, temp_sensor2_ID)
    sensor2_temp = int(temp_sensor2.get_temperature(W1ThermSensor.DEGREES_F) + sensor2_offset)
    return jsonify(temp_sensor2=int(sensor2_temp))

# PUMP State
@app.route('/state/pump/', methods=['GET'])
def pump_state():
    status = GPIO.input(PUMP)
    return jsonify(pump_state=status)

# SALT State
@app.route('/state/salt/', methods=['GET'])
def salt_state():
    status = GPIO.input(SALT)
    return jsonify(salt_state=status)

# LIGHT State
@app.route('/state/light/', methods=['GET'])
def light_state():
    status = GPIO.input(LIGHT)
    return jsonify(light_state=status)

# AERATOR State
@app.route('/state/aerator/', methods=['GET'])
def aerator_state():
    status = GPIO.input(AERATOR)
    return jsonify(aerator_state=status)

# AUX1 State
@app.route('/state/aux1/', methods=['GET'])
def aux1_state():
    status = GPIO.input(AUX1)
    return jsonify(aux1_state=status)

# AUX2 State
@app.route('/state/aux2/', methods=['GET'])
def aux2_state():
    status = GPIO.input(AUX2)
    return jsonify(aux2_state=status)

# AUX3 State
@app.route('/state/aux3/', methods=['GET'])
def aux3_state():
    status = GPIO.input(AUX3)
    return jsonify(aux3_state=status)

# AUX4 State
@app.route('/state/aux4/', methods=['GET'])
def aux4_state():
    status = GPIO.input(AUX4)
    return jsonify(aux4_state=status)

My next tale of woe came when the PCB boards were delivered.

My first board arrived with the incorrect footprint for the relays. Problem number 1. I
somehow ordered momentary switches and not toggle switches so that wasn’t going to work. I corrected the relay footprint and sent it off for manufacturing. A few days later they arrived. Dag-gum-it if I didn’t draw the footprint backwards. Ugh. The good news was that the relays fit now, but only on the back of the board. Not cool.. I also somehow removed the common ground between the power supplies, so changes made and off Rev 3 went to production. Finally, it was right.
While we were waiting for the boards to arrive, Jemal and I found a bit of a problem. If the pool controller was on and Home assistant restarted, it didn’t know what the state of the relays were and would report wrong. I added a piece of code that would send out the states to their respective MQTT topics. I used yet another MQTT call to trigger that and we placed that in an automation in Home assistant which said, on start, it would call the MQTT topic to update the states.

With Rev3 in hand, I was able to assemble and test the board. Worked like a champ. Then I realized, the pump motor is 220V and requires 2 connections for hot. Deep down I was afraid of the relays not being able to handle the 220V anyway so the solution was easy. I added a SPDT contactor that had 110V AC coil voltage and was rated for over 220V AC. I wired up relay to complete the 110V circuit, which would in-turn, activate the contactor and boom, the two loads were controlled by a single relay.

Here is what is looked like in it’s enclosure.

Above you can see what it looks like in Home assistant. I didn’t go into how to configure it in Home assistant as that seems like another completely different topic. If you would like me to write up something on HA integration, hit me up on email.

I will post some more pics and update when we install this bad boy.

Featured

WiFi Line Level Volume Control

Update 10-2020

So, there was a bit too much current coming from the TV and it was causing the chip to be overdriven. I put a resister inline with the input and that solved the issue. I have updated the drawings and the images. I used a variable resistor as I had to try a few values before I got it right. Carry on..

So I have these studio monitors… I also have a TV with crappy speakers. What could go wrong?

I wanted to use a set of studio monitors for my bedroom TV. The speakers in the TV suck and these monitors always sounded great. Here is the problem, the TV that I have will not allow me to control the line level volume out. I know what you are going to say, “get off my ass, get up and change the volume”. To that I say, “What is this 1970”? This is 2020, and there is no good reason to have to walk across the room to adjust the volume. The monitors have volume control as they are active, but a pain to get too.

So, I started by looking to see if there was already something on the market. I was thinking there had to be an inline IR remote controlled line level controller. Well, there is, but they all look cheeezzeee and had poor reviews. I know, I will just build something. Yeah, I know it will cost much more than a new set of speakers or a sound bar, but what fun is that?

I started to think and plan. What technology should I use? Should I build an IR receiver and program that to take commands from my existing remote? Could I use something else that might have other uses for home automation? HECK YES!

After some research, I found a digital potentiometer that looked promising. What should I use to control it? Should I design an embedded JSON based web app? That would require me to open a web page every time I want to change the volume. Nah.. I know, connect it to my existing Home Assistant server. Yeah. And I will use an ESP32 DevKitC board to control it. Yeah yeah. And I can use MQTT to publish the volume topic using a slider in Home Assistant and subscribe to the topic on the new device. yeah yeah yeah. It’s at this point that I try to hide my excitement from my wife. I know if she sees me geeking out, she will just laugh at me and tell me it will cost too much. Better to just keep this to myself for now…

So, here it is. In all of it’s glory.

Note, I am not going over setting up Home assistant or and MQTT server. Just know you will need at least one Raspberry Pi for that.

Here is the configuration.yaml and automations.yaml entires to create the device, the slider and publish to the MQTT server.

# ---- Add this section to the automations.yaml file in Home Assistant ----

# ---- Volume slider automation ----

- id: '1590287899999'
  alias: Bedroom Volume
  trigger:
  - entity_id: input_number.slider1
    platform: state
  action:
  - data_template:
      payload: '{{ states.input_number.slider1.state | int }}'
      retain: true
      topic: audio/volume
    service: mqtt.publish
  initial_state: true
# ---- Add these elements to the congiguration.yaml file ----


# -- Configure mqtt broker --
mqtt:
# The IP of your mqtt server goes below
  broker: xxx.xxx.xxx.xxx

# -- Setup slider --
input_number:
  slider1:
    name: Volume
    icon: mdi:speaker
    initial: 30
    min: 1
    max: 127
    step: 1
    unit_of_measurement: "%"  

That will do it for Home Assistant configuration.

I used C to write the code for the microcontroller. I have been learning Micropython, and I may re-code it at some point as a fun project(I did give it a try, but it was taking to long to learn and code). As I mentioned, I used an ESP32 DevKit C Ver4 for the microcontroller board.

Here is the Schematic:

I used RCA jacks as they match the TV and the monitors have RCA as well.

Here is the code:

//ESP32 Code

//* This code is for a line level Wi-Fi volume controller to be used with Home Assistant *//

#include <WiFi.h>
#include <PubSubClient.h>
#include <SPI.h>

// Update these variables.

const char* ssid = "........."; // Your SSID 
const char* password = "........."; // Your Wi-Fi password
const char* mqtt_server = "..........."; // Your MQTT broker
const char* mqtt_topic = "............"; // You can leave this topic or change if you would like If you change, you will need to update your Home Assistant automation topic
const char* mqtt_client_id = ".............."; // Name for this device on your MQTT network
int ChipSelect = 5; // SS or CS Pin (GPIO) on your board. On the ESP32 DEVKIT C V4, the CS pin in GPIO5

WiFiClient espClient;
PubSubClient client(espClient);

void setup_wifi() {

  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  randomSeed(micros());

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.println();
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");

   payload[length] = '\0'; // Add a NULL to the end of the char* to make it a string.
   int volume = atoi((char *)payload);
   
  Serial.print(volume);
  writeMCP4241(0,volume);
  writeMCP4241(1,volume);
}

void writeMCP4241(byte address, char value) {
  digitalWrite(ChipSelect, LOW); 
  SPI.transfer(address << 4);
  SPI.transfer(value & 127);
  digitalWrite(ChipSelect, HIGH);
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = mqtt_client_id;
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      client.subscribe(mqtt_topic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup() {
  pinMode(ChipSelect, OUTPUT); 
  Serial.begin(115200);
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
  Serial.println(SS);
  Serial.println(MISO);
  Serial.println(SCK);
  SPI.begin();
}

void loop() {

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

I created the PCB Gerber files and sent them off to JLCPCB to make. I still can’t believe how fast and how cheap it is to have them made. If you would like a copy of the Gerber file to make your own, just let me know and I will email it to you. I can’t post zip files on WordPress. Ugh

And that’s it! Now I can control my bedroom TV volume with a slider in the Home Assistant app on my phone. Works like a champ. The only issue I have run into is that my wife can also control the volume at the same time. There are some “limitations”. 🙂

Featured

THE TIMMY

I haven’t been happy with my guitar tone for years now. I have an amazing Dr. Z amp. Of course I play a Groove Custom Tele (shameless plug for my brand). It was my overdriven tone I wasn’t happy with. I have tried new distortion and overdrive pedals, but they all just seem to end up with distortion and loss of my guitar’s tone. People in, “The Biz” call it transparency. A transparent overdrive kind of simulates driving the guitars signal into a tube amplifier and pushing the preamp tubes to a point they start to clip and distort. Normally, you need to turn the amp up to an ear piercing level for this to happen and it’s just not practical. Mostly for the drummer whom you can’t even hear over your blasting amp.

So, that was the dilemma, but what do do? Mere mortal men do not have the means to sample every single overdrive for an extended period. It turns out a friend at work use to build remakes of vintage guitar pedals. After speaking with him, he leant me about 10 of his overdrives to try out. They were reproductions of vintage pedals so the thought was that if I found one that I liked, I would find the true version in the wild and snatch it up. I gave each pedal five minutes. I figured if I didn’t like it in five minutes, it wasn’t for me. I made two piles of pedals, one was discards, thrown away like yesterdays jam. The second was the pedals that I liked. I ended up with three out of the ten. I tested the remaining three the same way and ended up with one… The Timmy. I had never heard of this pedal before and when I asked my buddy at work, he gave me the story.

The Timmy was created by a man named Paul Cochrane. He designed the circuit and started producing these pedals for sale online. They went for pretty big money for an overdrive, in the $250 range and were all hand made. The DIY community were intrigued with his design and how good this pedal sounded and would post in the forums speculating about his design. Paul, being the lurker that he was (unverified), was watching. He posted on a forum that if people wanted the schematic, all they had to do was ask. He went on to say that he would send it to whomever wanted it with the stipulation that people do not make them to sell.

So, I got my greedy paws on the schematic and drew it out in EasyEDA.

Paul Cochrane’s Timmy

So then it was on to designing the circuit boards. Also in EasyEDA.

The circuit board layout

Now it was time to create the Gerber files. The gerber files are used by the board manufacturers to build the circuit boards.

Next was to get them manufactured. For this I used JLCPCB.com. This company is amazing. They manufacture your custom PCB’s and they are delivered within four days. The best part, it’s less than $18 with shipping for 10 boards. How is that even possible? It’s not my place to question…

In four days time I had my new boards in hand. Time to stuff these bad boys and get started. I ordered a case and a foot switch on Amazon and the rest of the parts on mouser.com. All in for enough parts to build at least 5 pedals (for my friends of course), it was about $30.

Here is a list of the components

R1 8.2k
R2 10k
R3 2.2M
R4 510k
R5 3.3k
R6 3.3k
R7 1.5k
R8 3.3k
R9 3.3k

C1 47u
C2 47u
C3 47n
C4 100p
C5 39n
C6 1u
C7 10n
C8 1u

D1 1N5817 – COM-08589
D2 1N4148
D3 1N4148
D4 1N4148
D5 1N4148
D6 1N4148
D7 1N4148
D8 1N4148

Bass A50k Pot
Gain A1M Pot
Treble A50k Pot
Volume A10k Pot

Sw1 SPST on/off/on (foot switch)

IC1 JRC4559

You will need two phono jacks for the input and output and a power connector as well.

And ended up with something exactly like this..

Now it was on to the case and wiring the Timmy up. I measured and drilled the holes for the pots, switch, power jack, and audio jacks. Now…how do I make it stand out on my pedal board. Hmmm. I know, I will laser engrave the name and pot names on the chassis. And that turned out a little something like this… Yeah, I know the foot switch hole looks off. It is just a tad. During drilling I realized that my drill bits are garbage. Something that I will have to remedy.

And here it is All built on my board. I played my first gig with it a couple of weeks ago and I think I have it dialed in. I no longer sound like I play in an 80’s metal band, just nice transparent overdrive and on the cheap. Total cost was around $50. Not bad.. Thanks to my friend John M for making all this possible.

Hit me up if you want more details or have questions. I have enough parts to build another so if you are interested in building one…

Featured

Well, it’s come to this…

Here we are, or at least I am.. I have no time, too many projects going on at once, a full time job, running a business, playing in a band… I know, I will start a blog. Sigh.

I have always been fascinated by other people’s DIY blogs. Their projects seem to be done in an instant and fully documented with pictures and full descriptions. I am equally fascinated by learning and doing new projects. People say that I have too many hobbies and projects, but I can’t stop learning. I have to be doing something. So, why not share these with other people? Right!

My hope for this blog is to share some of my projects and work with others and hopefully spark some interest in learning. Also, I would love to collaborate on projects, so any makers that find this blog interesting, feel free to hit me up with your ideas.

I will leave you with a short collection of just some of the projects I have worked on in the last six months.

Private LORA sensors and server

Well, it’s been a hot minute and I have been busy busy busy…

Where were we? I think the last time we spoke we left off with a functioning gate sensor. Right… Well, I gave it an UPGRADE! I started reading about LORA which stands for Long Range (Wireless communication). The thing that caught my eye was the low power consumption, but long range aspect. They claim it has a range of around ten miles. What? 10 MILES? The thing that I didn’t like is that they all seem to be centered around IOT devices which connect and store data to some cloud. I am no tin foil wearing, UFO fearing freak, but ain’t no one needs to know when my gate is opened except my wife and me, and I certainly don’t need to track it online when I can track it at home.

So I went down the rabbit hole. I ordered some cheap LORA modules, connected them with a Raspberry Pi Pico and tried a point to point Micropython program, one sending data and the other receiving data. That worked, pretty simple. Then I started looking in to LORAWan. What I found was, the products like Chirpstack say they are used for local only but, like I said above, they all require some connection to a cloud and that wouldn’t work for me. There is only one thing to do I guess…

Yeah, I may have went a bit overboard on the enclosure.

Building a private LORA server… Right… That’s what you thought wasn’t it?

Actually, not as difficult as I thought it would be. The Adafruit LORA modules were cheap, but not as cheap as I wanted. I like for my projects to be as cheap as possible in case my wife finds out what I am tinkering with. She thinks everything I build is to spy on her for some reason. Maybe I have given her a complex by saying her name through the cameras while she is out weed eating. Wait, what were we talking about? oh yeah, LORA servers! So I found the LORA chips way cheaper and I designed a board to mount the chip, a Raspberry Pi Pico and an antenna jack. I added a couple of GPIO pin connections for 5V LEDs because who doesn’t like flashy LED lights on DIY electronic projects? Am I right?

I added a couple of current limiting resistors for the LEDs but I had some 3.3v LEDs in my stash of misfit electronic toys so I just jumperd those suckers.

Okay, I have a platform, lets get coding… I had to get the Micropython library for that chip which was really the hardest part of this project. https://github.com/wybiral/micropython-lora I got that and started coding. I created a server that listens for signals at 915Mhz which is the open (free) range for LORA in the USA. When it receives a packet, it inspects the ID of the source and if it matches an ID I have in the database, it processes it based on that ID. This way, I could have several sensors that all get processed differently for different applications (weather, gate sensor, motion sensor, water sensor, temperature sensor)

It then processes the data either sending it to MQTT or writing it to a database. One thing I found is that the sensor sends the data three times to assure it was received at long distances. I really didn’t need it to be sent three times so I edited the LORA python module to only send it once. Here is the code for the server.

from pyLoraRFM9x import LoRa, ModemConfig
import time
import paho.mqtt.client as mqtt
import mariadb
import json
import sys

###### Make connection to the testing DB ######
try:
    conn = mariadb.connect(
        user="xxxxxxx", # your database user ID
        password="xxxxxxx", # your database password
        host="xxxxxxxxxxxxxx", # your database IP or FQDN
        port=3306,
        database="xxxxxx") # your database name
except mariadb.Error as e:
        print(f"Error connecting to MariaDB Platform: {e}")
        sys.exit(1)
###### Get Cursor for the testing DB ######
cur = conn.cursor()

###### MQTT Broker info ######
broker = 'xxx.xxx.xxx.xxx' # your MQTT server IP or FQDN
client = mqtt.Client(client_id="LORA-GATEWAY")

###### Connect to mqtt Server ######
client.connect(broker)

###### Pull MQTT Topics and sensor ID's from the DB ######
data = "SELECT * FROM LORA"
cur.execute(data)
result = cur.fetchall()
print(result)


###### Function that parses payloads based on received header info and processes according to sensor ID ######
def on_recv(payload):
    print("Got packet from:", payload.header_from)
    if payload.header_from == 4:
        ###### Pull the correct MQTT topic from the DB ######
        get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"
        header = payload.header_from,
        cur.execute(get_client_data, header)
        result = str(cur.fetchone())
        format_result = result.replace("'",'').replace("(",'').replace(")",'').replace(",",'')
        
        ###### Extract JSON data ######
        office_temp_json = json.loads(payload.message)
        office_temp = office_temp_json["Temperature"]

        ###### record temperature to the DB ######
        insert_temp = "INSERT INTO OfficeTemp (temperature) VALUES (?)"           
        cur.execute(insert_temp, [office_temp], None)
        conn.commit()

        ###### Publish temperature to MQTT ######
        client.publish(format_result, payload=payload.message)
        print("published to:", format_result, payload)
    elif payload.header_from != 4:     

        if payload.header_from == 3:
            ###### Convert bytes temp data to str and port to the DB ######
            message_data = str(payload.message, encoding='utf-8')
            insert_temp = "INSERT INTO Laser (temperature) VALUES (?)"           
            cur.execute(insert_temp, [message_data], None)
            conn.commit()
            get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"
            ###### Publish to MQTT topic ######
            header = payload.header_from,
            cur.execute(get_client_data, header)
            result = str(cur.fetchone())
            format_result = result.replace("'",'').replace("(",'').replace(")",'').replace(",",'')
            print(format_result)
            client.publish(format_result, payload=payload.message)
            print("published to:", format_result, payload)
        elif payload.header_from == 1:
            ###### Convert bytes temp data to str and port to the DB ######
            message_data = str(payload.message, encoding='utf-8')
            insert_state = "INSERT INTO Gate (state) VALUES (?)"           
            cur.execute(insert_state, [message_data], None)
            conn.commit()
            get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"
            ###### Publish to MQTT topic ######
            header = payload.header_from,
            cur.execute(get_client_data, header)
            result = str(cur.fetchone())
            format_result = result.replace("'",'').replace("(",'').replace(")",'').replace(",",'')
            print(format_result)
            client.publish(format_result, payload=payload.message)
            print("published to:", format_result, payload)
        elif payload.header_from == 5:
             ###### Write rain tip data to weather database ######
            print(payload.message)
            insert_state = "INSERT INTO Rain_Data (tips) VALUES (?)"           
            cur.execute(insert_state, [payload.message], None)
            conn.commit()
            print("published to DB: Rain_Data", payload)
        
def on_recv_main():
#    time.sleep(15)
    lora.on_recv=on_recv

#LORA Setup
lora = LoRa(0, 1, 5, 4, reset_pin=25, receive_all=True, freq=915, modem_config=ModemConfig.Bw125Cr45Sf128, tx_power=14, acks=False)

# set to listen continuously
lora.set_mode_rx()

# loop and wait for data
while True:
    on_recv_main()
    time.sleep(15)

I started the code by creating a table in the DB with each sensor ID and it’s corresponding MQTT topic. So, I could change those or add any sensors just by adding a few lines of code and one row in the DB. That table looks like this..

So the whole process looks like this… A packet is received from sensor with ID of 1.

I used this line to assign the MQTT topic:

if payload.header_from == SENSOR ID
       get_client_data = "SELECT mqtt_topic FROM LORA WHERE client_id=%s"

Then I process the message data in different ways.. I can either write it to the database or publish to an MQTT topic or both. BOOM!

The code on the sensor looks like this (just a simple temp probe taking the temp reading, converting from C to F and sending every ten seconds. Then it flashes the LEDs to let me know it sent it. (See, they have some functionality)

import time
from ulora import LoRa, ModemConfig, SPIConfig
import machine
from machine import Pin
import ds18x20
import onewire

# Lora Parameters
RFM95_RST = 9
RFM95_SPIBUS = SPIConfig.rp2_0
RFM95_CS = 8
RFM95_INT = 22
RF95_FREQ = 915.0
RF95_POW = 20
CLIENT_ADDRESS = 4
SERVER_ADDRESS = 2

ds_pin = machine.Pin(26)
ds_sensor = ds18x20.DS18X20(onewire.OneWire(ds_pin))
led1 = machine.Pin(12, Pin.OUT)
led2 = machine.Pin(13, Pin.OUT)
roms = ds_sensor.scan()
print('Found DS devices: ', roms)
    
# initialise LoRa radio
lora = LoRa(RFM95_SPIBUS, RFM95_INT, CLIENT_ADDRESS, RFM95_CS, reset_pin=RFM95_RST, freq=RF95_FREQ, tx_power=RF95_POW, acks=True)

# This is the send function that takes the temp from the probe, converts it to F and sends to the LoRa server
def send_temp():
    ds_sensor.convert_temp()
    time.sleep_ms(750)
    for rom in roms:
        print(rom)
        print(ds_sensor.read_temp(rom))
        temperature_c = ds_sensor.read_temp(rom)
        temperature_f = str(round(temperature_c * (9/5) + 32, 0))
        print(temperature_f)
    lora.send_to_wait(temperature_f, SERVER_ADDRESS)
    led1.on()
    time.sleep(.1)
    led2.on()
    time.sleep(.1)
    led1.off()
    time.sleep(.1)
    led2.off()
    print("sent")
    time.sleep(10)

# loop and wait for data
while True:
    send_temp()

I have converted my gate sensor, a couple temp probes and my rain gauge (more to come on that project) to LORA now and it’s all working flawlessly. I have solar panels connected to LIPO batteries in each enclosure and since it’s low power consumption, they never get below 80% charged.

The gate sensor code looks like this… basically just sends a binary number to the server via LORA when the gate is opened or closed.

import time
from ulora import LoRa, ModemConfig, SPIConfig
import machine
from machine import Pin

# Lora Parameters
RFM95_RST = 9
RFM95_SPIBUS = SPIConfig.rp2_0
RFM95_CS = 8
RFM95_INT = 22
RF95_FREQ = 915.0
RF95_POW = 20
CLIENT_ADDRESS = 1
SERVER_ADDRESS = 2

led1 = machine.Pin(12, Pin.OUT)
led2 = machine.Pin(13, Pin.OUT)
gate_pin = machine.Pin(14, Pin.OUT)
    
# initialise LoRa radio
lora = LoRa(RFM95_SPIBUS, RFM95_INT, CLIENT_ADDRESS, RFM95_CS, reset_pin=RFM95_RST, freq=RF95_FREQ, tx_power=RF95_POW, acks=True)

# This is the send function that will send a 1 or 0 to the server if the gate sensor is open or closed.
def send_gate_status():
    if gate_pin == 0:
        lora.send_to_wait("0", SERVER_ADDRESS)
    elif gate_pin ==1:
        lora.send_to_wait("1", SERVER_ADDRESS)

# loop and wait for data
while True:
    send_gate_status()
    time.sleep(10)

There are plenty of great applications for this technology on a local level like a farm or even a small factory. Like all technology, it can be used for good or evil and I choose good. I hope you do too.

DIY MANIFESTO

We live in a time of greatness. A time where information is a Google search away. We also live in a time where taking the time to learn doesn’t seem to be hip or groovy. With so much potential and opportunity, one would think that, as a society, we would be collectively more intelligent. That does not seem to be the case and it’s concerning.

I am not going to go on about, “These kids today”, but there is something to be said about how the younger generation views the opportunities for learning in the internet age. I am sometimes awestruck by the lack of common knowledge young people have about the world they live in. Practical knowledge about basic physics, chemistry, and even computers seems to be lacking in this amazing world of instant data.

So why are young people not interested in learning these basic concepts? Am I just weird? Maybe I am… I just see so much opportunity in learning and I don’t think I will ever stop. The funny thing is that the reason I want to learn isn’t for concrete personal gain. I just want to know. Is curiosity not a thing now? Is wanting to know how and why something works the way it does not important? To me, it’s the most important.

I will leave you with this real life story from my life about fifteen years ago.

My wife needed to have her gal bladder removed. She was in horrible pain and we were in the ER. We didn’t have very good health insurance at the time and we were both worried about how much this was all going to cost. Of course, it didn’t matter, we both wanted her to feel better. Her probably more so than me.

I pulled up youtube.com and searched for gal bladder surgery. What do you know, there it was. A doctor walking through the operation step by step, describing the anatomy, performing the necessary cuts, stopping the bleeding when needed, pulling back this gal bladder and removing it in less than ten minutes. I had a solution! Obviously, it can’t be that hard, I SHOULD be able to do this myself. I had a friend that was a surgeon, I could borrow some, “tools”, and have this puppy pulled out and have her home in no time, with way less cost. I carefully pitched this idea to my wife who was in agony. Even the thought of having it pulled out as soon as possible did not outweigh her hate for me at that moment. I knew, at that very minute, my dreams of DIY surgery were never going to happen.