If you experience a power or network outage - your Dashboard's power toggle may not be accurate.

While the PyPortal will re-connect to the network, Adafruit IO toggling the button may not actually be turning the appliance on or off. You will need to add some way of measuring feedback (such as the power status) from the appliance.

A simple way of adding a feedback mechanism is by placing a light sensor over the appliance's power indicator LED to detect if the appliance is actually turned on or off. 

Plug a BH1750, a small inexpensive light sensor, into your PyPortal to measure the light value from the appliance's power indicator LED. Then, create a new Adafruit IO Feed to hold the light sensor's value.

Add some new code to the PyPortal to measure the light sensor's value at a predefined interval, evaluate if the appliance is turned on or off, and send the status back to Adafruit IO. 

To wrap it all up, you'll add an Indicator Block to your Adafruit IO Dashboard to display the appliance's actual power status.

Parts

We'll need two additional parts to complete this section

Adafruit BH1750 Light Sensor with STEMMA QT / Qwiic
This is the BH1750 16-bit Ambient Light Sensor from Rohm. Because of how important it is to humans and most other living things, sensing the amount of light in an...
$4.50
In Stock

You'll also need a JST PH to JST SH cable to connect the BH1750 sensor to the PyPortal's I2C port.

Angled shot of 4-pin JST PH to JST SH Cable.
Are you a maker in the midst of a STEMMA dilemma? This 200mm long 4-wire...
$0.95
In Stock

Add Feed

You will need a feed to hold the sensor's value. Navigate to your Adafruit IO Feeds page and click Actions -> Create New Feed. Name the feed status and click create.

Add Block to Dashboard

Navigate to the dashboard you created earlier. Click Create New Block (+ icon).

Select the indicator block

Select the feed you just created, status

Click Create Block

Your dashboard should look like the screenshot below:

Wiring

Plug the BH1750 Light Sensor into the Stemma JST PH connector. 

Then, plug the JST SH connector into the I2C port on your PyPortal.

Locate the Power LED on your appliance and attach the light sensor above the power LED using tape.

We suggest using black tape, such as gaffer's tape to avoid light leakage.

CircuitPython Installation of BH1750 Library

You'll need to install the Adafruit CircuitPython BH1750 library on your CircuitPython board.

First make sure you are running the latest version of Adafruit CircuitPython for your board.

Next you'll need to install the necessary libraries to use the hardware--carefully follow the steps to find and install these libraries from Adafruit's CircuitPython library bundle.  Our CircuitPython starter guide has a great page on how to install the library bundle.

Before continuing make sure your board's lib folder or root filesystem has the adafruit_bh1750.mpy file and adafruit_bus_device folder copied over.

CircuitPython Code

Copy code_light_sensor.py to your PyPortal's CIRCUITPY drive and rename the file to code.py.

# SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time
import board
import busio
from digitalio import DigitalInOut
import neopixel
import adafruit_bh1750
from adafruit_esp32spi import adafruit_esp32spi
from adafruit_esp32spi import adafruit_esp32spi_wifimanager
import adafruit_esp32spi.adafruit_esp32spi_socket as socket

import adafruit_minimqtt.adafruit_minimqtt as MQTT

### Sensor Calibration ###
# Appliance power LED's light level, in Lux
APPLIANCE_ON_LUX = 30.0
# How often the light sensor will be read, in seconds
SENSOR_READ_TIME = 10.0

### WiFi ###

# Get wifi details and more from a secrets.py file
try:
    from secrets import secrets
except ImportError:
    print("WiFi secrets are kept in secrets.py, please add them there!")
    raise

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
"""Use below for Most Boards"""
status_light = neopixel.NeoPixel(
    board.NEOPIXEL, 1, brightness=0.2
)  # Uncomment for Most Boards
"""Uncomment below for ItsyBitsy M4"""
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=0.2)
# Uncomment below for an externally defined RGB LED
# import adafruit_rgbled
# from adafruit_esp32spi import PWMOut
# RED_LED = PWMOut.PWMOut(esp, 26)
# GREEN_LED = PWMOut.PWMOut(esp, 27)
# BLUE_LED = PWMOut.PWMOut(esp, 25)
# status_light = adafruit_rgbled.RGBLED(RED_LED, BLUE_LED, GREEN_LED)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

# Set up a pin for controlling the relay
power_pin = DigitalInOut(board.D3)
power_pin.switch_to_output()

# Set up the light sensor
i2c = board.I2C()  # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
sensor = adafruit_bh1750.BH1750(i2c)

### Feeds ###
# Set up a feed named Relay for subscribing to the relay feed on Adafruit IO
feed_relay = secrets["aio_username"] + "/feeds/relay"

# Set up a feed named status for subscribing to the status feed on Adafruit IO
feed_status = secrets["aio_username"] + "/feeds/status"

### Code ###

# Define callback methods which are called when events occur
# pylint: disable=unused-argument, redefined-outer-name
def connected(client, userdata, flags, rc):
    # This function will be called when the client is connected
    # successfully to the broker.
    print("Connected to Adafruit IO!")


def disconnected(client, userdata, rc):
    # This method is called when the client is disconnected
    print("Disconnected from Adafruit IO!")


def subscribe(client, userdata, topic, granted_qos):
    # This method is called when the client subscribes to a new feed.
    print("Subscribed to {0}".format(topic))


def unsubscribe(client, userdata, topic, pid):
    # This method is called when the client unsubscribes from a feed.
    print("Unsubscribed from {0} with PID {1}".format(topic, pid))

def on_message(client, topic, message):
    # Method callled when a client's subscribed feed has a new value.
    print("New message on topic {0}: {1}".format(topic, message))


def on_relay_msg(client, topic, value):
    # Called when relay feed obtains a new value
    print("Turning Relay %s"%value)
    if value == "ON":
        power_pin.value = True
    elif value == "OFF":
        power_pin.value = False
    else:
        print("Unexpected value received on relay feed.")

# Connect to WiFi
print("Connecting to WiFi...")
wifi.connect()
print("Connected!")

MQTT.set_socket(socket, esp)

# Set up a MiniMQTT Client
client = MQTT.MQTT(
    broker="io.adafruit.com",
    username=secrets["aio_username"],
    password=secrets["aio_key"],
)

# Setup the callback methods above
client.on_connect = connected
client.on_disconnect = disconnected
client.on_subscribe = subscribe
client.on_unsubscribe = unsubscribe
client.on_message = on_message
# Add a callback to the relay feed
client.add_topic_callback(feed_relay, on_relay_msg)

# Connect the client to Adafruit IO
print("Connecting to Adafruit IO...")
client.connect()

# Subscribe to all updates on relay feed
client.subscribe(feed_relay)

# Holds previous state of light sensor
prv_sensor_value = 0
# Time in seconds since start
start_time = time.monotonic()

while True:
    try:
        # Poll for new messages on feed_relay
        client.loop()
        now = time.monotonic()
        if now - start_time > SENSOR_READ_TIME:
            # Read light sensor
            print("Reading light sensor")
            sensor_value = sensor.lux
            print("%.2f Lux" % sensor.lux)
            if sensor_value != prv_sensor_value:
                # Light sensor value changed between readings
                if sensor_value > APPLIANCE_ON_LUX:
                    # Appliance is ON, publish to feed_status
                    print("Appliance ON, publishing to IO...")
                    client.publish(feed_status, 1)
                    print("Published!")
                else:
                    # Appliance is OFF, publish to feed_status
                    print("Appliance OFF, publishing to IO...")
                    client.publish(feed_status, 2)
                    print("Published!")
                prv_sensor_value = sensor_value
            start_time = now
    except (ValueError, RuntimeError, ConnectionError, OSError) as e:
        print("Failed to get data, retrying\n", e)
        wifi.reset()
        client.reconnect()
        continue
    time.sleep(0.5)

Code Usage

Connect up your PyPortal's STEMMA cables and turn on the relay outlet. Once the PyPortal is connected to the internet, navigate to the Adafruit IO Dashboard and tap the toggle switch block to turn the appliance on.

The appliance should turn on and the indicator block should light up green, indicating it is turned on.

Toggling the switch to the off position should turn the appliance off and the indicator block should turn red.

Light Sensor Calibration

We wrote this guide for a LG Air Conditioner. Other appliances use different LEDs which provide different illuminance measurements. If the status indicator block is not responding, you may need to calibrate the light sensor for your appliance.

In the embedded code element below, click on the Download: Project Zip link, and save the .zip archive file to your computer.

Then, uncompress the .zip file, it will unpack to a folder named bh1750_simpletest.

Copy bh1750_simpletest.py to your PyPortal's CIRCUITPY drive and rename the file to code.py.

# SPDX-FileCopyrightText: 2020 Bryan Siepert, written for Adafruit Industries

# SPDX-License-Identifier: Unlicense
import time
import board
import adafruit_bh1750

i2c = board.I2C()  # uses board.SCL and board.SDA
# i2c = board.STEMMA_I2C()  # For using the built-in STEMMA QT connector on a microcontroller
sensor = adafruit_bh1750.BH1750(i2c)

while True:
    print("%.2f Lux" % sensor.lux)
    time.sleep(1)

Connect your PyPortal to power. With the light sensor placed over the light source, turn the PyPortal on. Turn the appliance on. The display will show the ambient light level in lux, the SI derived unit for measuring illuminance.

Write this value down. You may wish to average some of the readings together.

In the code_light_sensor.py, modify the line APPLIANCE_ON_LUX = 30.0 to the light value you recorded.

This guide was first published on Sep 22, 2020. It was last updated on Sep 22, 2020.

This page (Going Further: Add a Feedback Indicator) was last updated on Sep 23, 2023.

Text editor powered by tinymce.