Help with Enviro pHAT related project


#1

I’ve been using the Enviro pHAT to check how bright it is in our front room, then (using a hacky bit of Python code) tell another Pi (which has the PiMote connected) to turn on two sockets (that are connected to lamps), thus making it brighter!

It works, but the problem I have is that the lux level I set is great, but sometimes the lights coming on raise the lux just enough to trip the other command, which turns off the sockets if it gets too bright in the room.

This is my current iteration written in Python (found on my GitHub page):

#!/usr/bin/env python

# Import the necessary modules, including the Enviro pHAT module
import os
import time
from envirophat import light

# Variables used defined below

state = 0       # Logic to tell whether the light is on or off.
period = 15     # Time (in seconds) between checks. Default is 15 seconds.

def lights_on():
  os.system('curl -s "http://username:password@PI-IP-ADD-RESS/api-all-on/" > /dev/null')
  print('Lights on! Room luminence is {0:.0f} lux.'.format(light.light()))

def lights_off():
  os.system('curl -s "http://username:password@PI-IP-ADD-RESS/api-all-off/" > /dev/null')
  print('Lights off! Room luminence is {0:.0f} lux.'.format(light.light()))

lux = 250       # Defines when the lights will come on or off.

while True:
  if light.light() < lux and state != 1:
      lights_on()
      state = 1
  elif light.light() >= lux and state == 1:
      lights_off()
      state = 0
  time.sleep(period)

So the code above runs every 15 seconds and I control when this runs through a cron job.

I’m trying to prevent the “disco mode” caused by the darkness dropping enough to turn the lights on, but the act of turning the lights on then triggers it to turn them off again, but I’m struggling to do this.

My initial thoughts are that I need to associate something with the state, so if the lights are on (state = 1), then wait longer before the next check. However, I don’t want to change the overall delay (15 seconds) unless this is the most obvious way, as I did this initially and then it became too dark before the lights would eventually turn on.

I appreciate some of the code there isn’t fantastic, but it works well so far. If anybody could help with some suggestions on what to try to achieve what I’m after, that would be great!


#2

Typically with things like this you want to introduce hysteresis, which is to say rather than having one switching point you have two values (either side of the 250 you currently have). The one you use depends on whether the lights are currently on or off. I.e. when the lights are on, use the upper value to determine whether to turn them off. When the lights are off, use the lower value to determine whether to turn them on.


#3

Thanks @major_tomm (cool surname BTW, mine’s also Archer!). I tried initially to use different thresholds - ie if the lux is less than or equal to 250 turn the lights on, if the lux is above 250 turn the lights off, but I couldn’t figure out how to implement that properly.

Do you have any examples you could share to help?

EDIT: I realise my code does try and include that already, but I wanted to trigger the lights at a specific lux level otherwise it either gets too dark before the lights come on, or it is too light and they come on.


#4
#!/usr/bin/env python

# Import the necessary modules, including the Enviro pHAT module
import os
import time
from envirophat import light

# Variables used defined below

state = 0       # Logic to tell whether the light is on or off.
period = 15     # Time (in seconds) between checks. Default is 15 seconds.

def lights_on():
    global state
    state = 1
    os.system('curl -s "http://username:password@PI-IP-ADD-RESS/api-all-on/" > /dev/null')
    print('Lights on! Room luminence is {0:.0f} lux.'.format(light.light()))

def lights_off():
    global state
    state = 0
    os.system('curl -s "http://username:password@PI-IP-ADD-RESS/api-all-off/" > /dev/null')
    print('Lights off! Room luminence is {0:.0f} lux.'.format(light.light()))

# Defines when the lights will come on or off.
lower_lux = 240
upper_lux = 260       

while True:
    if state == 0:
        # if lights are off, turn them on when the lower level is reached
        if light.light() < lower_lux:
            lights_on()
    else:
        # the lights are on, turn them off if the level reaches the upper level
        if light.light() > upper_lux:
            lights_off()

    time.sleep(period)

I’d try something like this, you may still need to tweak the lux values - I may be way off the mark.


#5

I see you’re also from Derby as well!


#6

Thanks I’ll try this.

And yes, I am from Derby too!


#7

Seems to have done the trick, thanks! So obvious now you highlighted it!


#8

@major_tomm, I’ve been trying to make my code better by (attempting) to calculate the average lux level in the room, then triggering an action - the idea being that an average value will be better and should prevent “disco mode” when the light drops enough for the lights to come on, only to be turned off again by the rise in lux. Also, I wanted to run the code all the time, as at the moment I use cron to run the code at set times (based on when I think it will start to get darker) which is not very efficient.

So far, I’ve managed to calculate the average lux (borrowing the example in the BME680 breakout code that calculates the gas average for a baseline), but when I attempt to use this in my code, it only triggers the turn_on() function irrespective of the light level (i.e. even if it is blindingly bright in the room, the turn_on() function is called).

I feel that I’m very close, but I spent several hours last night trying different options and I was hoping either you or another Pimoroni expert (@sandyjmacdonald / @gadgetoid / @RogueM ?) could help me over the line…

Here’s a link to my Gist with the full code - comments included which I hope will help!

Any feedback would be greatly appreciated.


#9

From the looks of it you’ll want a return average_light at the end of average_lux().


#10

Thanks, I’ve just added it and will give it a go later on. Fingers crossed!


#11

Ok, so that definitely helped @major_tomm thanks! I’ve tweaked a few things and it seems to work quite nicely now:

#!/usr/bin/env python

import requests
import time
import datetime
import os
from envirophat import light

# Get the current time
def whats_the_time():
    now = datetime.datetime.now()
    return (now.strftime("%H:%M:%S"))

# The function to turn off the lights. Sends a webhook to IFTTT which
# triggers Hue.

def turn_off():
    requests.post("https://maker.ifttt.com/trigger/{TRIGGER_WORD}/with/key/{TOKEN_GOES_HERE}")
    print("Lights off!")

# The function to turn on the lights. Sends a webhook to IFTTT which                   
# triggers Hue.

def turn_on():
    requests.post("https://maker.ifttt.com/trigger/{TRIGGER_WORD}/with/key/{TOKEN_GOES_HERE}")
    print("Lights on!")

# Check the light level and determine whether the lights need to 
# be turned on or off.

def average_lux():
    # Variables for calculating the average lux levels
    start_time = time.time()
    curr_time = time.time()
    collect_light_time = 15 # Maybe use 60?
    collect_light_data = []

    # Calculate the average lux level over 15 seconds
    print("Calculating average light level...")
    while curr_time - start_time < collect_light_time:
	    curr_time = time.time()
	    avg = light.light()
	    collect_light_data.append(avg)
	    time.sleep(1)
    average_light = sum(collect_light_data[-10:]) / 10.0
    now = whats_the_time()
    print("{} {} {} {} {} {} {} {}.".format("Average over", collect_light_time, "seconds", "is:", average_light, "lux.", "Last checked at", now))
    print("{} {} {} {}.".format("Waiting", "45", "seconds", "before trying again"))
    return average_light

try:
    # Local variables.
    state = 0	# Sets the state for the lights.
    low = 260	# Low value for light level (lux).
    high = 300	# High value for light level (lux).
    period = 45	# Delay, in seconds, between calls.
    while True:
	# Get the average lux level first,
	room_light = average_lux()
	# Now check if the room is dark enough then turn on the lights.
	if room_light < low and state != 1:
	    turn_on()
	    state = 1
	# Or if it is bright enough, turn off the lights.
	elif room_light > high and state == 1:
	    turn_off()
	    state = 0
	time.sleep(period)
except KeyboardInterrupt:
    pass

This now calculates an average of the light levels then determines whether the lights need turning on or off via webhook. Feedback welcomed!