Enviro+ and Cayenne for Monitoring

I purchased Enviro+ and the particle sensor and wanted to see how you would go about integrating this with Cayenne. There doesn’t seem to be that much information easily available so thought after a few days pulling my hair out and cracking it you might find this guide to be of some use.

If you don’t know of Cayenne its a free home automation software for the Raspberry Pi which looks to be very promising indeed. I am not affiliated with Cayenne but after using the software it looks a brilliant way of combining various off the shelf sensors and combining these in a fairly easy setup. Benefits can include logging and triggering to create events if for example the temperature reading exceeds a specific value indicating a possible fire. The beauty of Cayenne is the very modern web interface allowing you to have widgets and multiple devices all connected under one responsive interface. Useful if you want to place a number of Raspberry Pi Zero’s with WiFi around your home.

After spending a good week researching Cayenne and purchasing various sensors from Pimoroni I thought you might find this useful if you wish to pursue a project of your own.

Step 1 - Work out what areas of your home or office you want to monitor. Then decide what sensors you want to make use of. After experimenting with a few off Pimoroni I can recommend these inside sensors:

SGP30 - Air Quality Sensor TVOC and equivalent CO2
BME280 - Temperature, Pressure, Humidity
BME680 - Temperature, Pressure, Humidity, Air Quality

These can be combined easily using a Break out Garden Mini if you wanted to combine the BME280 and SGP30.

For the outside I fancied trying out the Enviro+ monitor and the additional particle sensor. Note: using a suitable waterproof housing if placing this outside. After testing the Enviro+ inside in my lounge with the particle sensor I ran some experiments to see how many pollutants were coming from my log burner. The particle sensor did a great job in detecting some very low levels of PM2.5 and PM10 so im happy my log burner isn’t going to kill me just yet. Have a Google and see what these represents. I would highly recommend buying this kit as its a good bit of fun. I will be using it combined with the Luftdaten project.

I would highly recommend playing with the code supplied with these sensors and following the instructions that come with them before moving on to Cayenne.

Step 2 - Getting this working with Cayenne

You will first need to build a new Raspbian image onto your Raspberry Pi. I always like to start from a clean slate. Once Raspbian is up and running change your hostname of your Pi to something memorable such as ‘LoungePi’. I also installed Remote Desktop as its handy at a later stage to gain access to the Pi especially if its going to be housed within some form of outside enclosure.

Open up a browser on your freshly installed Pi and go to:

http://cayenne.mydevices.com

Register for a new account. Follow the instructions to install Cayenne onto your Pi.

Step 3 - Getting Enviro+ working with Cayenne.

Cayenne allows for lots of sensors to be added even third party ones such as Z Wave, however not all are supported yet particularly the Enviro+ especially when multiple sensors are located on a single board. Basically you have two options of adding sensors. One if its directly supported by Cayenne you can simply add it by choosing from a list. If the sensor is not supported there is an alternative approach but to get this to work you need to know some Python and jump through some extra procedures. I will explain the steps that I went through below.

Step 3a - Installing additional Python libraries for Cayenne.

On your Pi follow this link and install the additional Python libraries:

https://github.com/myDevicesIoT/Cayenne-MQTT-Python

There are also examples which come with these Python libraries. The one I modified was ‘SendData.py’.
Before we can send our sensor data from the Enviro+ to Cayenne we need to create a new Device but by clicking on ‘Bring Your Own Thing’ under Add Device. Its a blue button. When you click on this it goes to Step 2 - Connect your Device. Before you can do this you will need to name your new device. I set the device name as ‘EnviroPlusOutside’. Take note of the other fields above. MQTT username, password and Client ID. These are important as each needs to be copied into the Python ‘SendData.py’ script.
Save your Python script and run this from the Terminal as ‘python SendData.py’.

At first I had a problem where the script failed to work and it stopped. Restarting by running ‘sudo service myDevices restart’ seemed to solve this problem.

Give it a few minutes for the script to run and to transmit the test data to Cayenne. the test data is just dummy text consisting of 3 sensor readings to see if your script is talking to Cayenne. You can check out this code by inspecting the Python code within the While True statement. This consists of ‘client.celsiusWrite’ etc. The Terminal should also show these readings.

If successful Cayenne will automatically progress past stage 2 and you should then see your devices populated within your Cayenne dashboard.

OK we are getting there! We now need to update the dummy text sensor code within our Python script so that we are getting our sensor readings from Enviro+. For this example I am merely going to show you how to get the temperature reading and to pass this to Cayenne. You can replicate this for pressure, humidity and pretty much anything else. Stop the script in the Terminal from running.

To get the temperature working I merely did some Frankenstein surgery and took the temperature code from the example code supplied when you installed the Enviro+ on your Pi. The one I used was ‘weather.py’. This version of the code does not compensate for the temperature variation so it will give a higher reading. I will be using jumper wires to separate the Enviro+ from my Pi zero when its housed in a weatherproof box with some holes drilled in the bottom for ventilation. On eBay I found a great outside weatherproof box with a clear front window. Male to Female Jumper wires are also easy to find.

Open up ‘weather.py’ and copy pretty much everything to your ‘SendData.py’ Python script avoiding any duplicate code. I also modified the updated script in ‘SendData.py’ and disabled the dummy sensor text. A copy of the modified code is shown at the bottom of this posting.

You will see that I have used ‘client.virtualWrite(1, temperature, “temp”, “c”)

There are various ways of sending various data types to Cayenne. This link will help explain what these are:

±-----------------------------------±-----------------±--------------------±-----------±--------------------+
| Data Type | Type Value | Unit | Unit Value | Widgets |
±-----------------------------------±-----------------±--------------------±-----------±--------------------+
| Digital Actuator | digital_actuator | Digital (0/1) | d | Button |
| Analog Actuator | analog_actuator | Analog | null | Slider |
| Digital Sensor | digital_sensor | Digital (0/1) | d | 2-State |
| Analog Sensor | analog_sensor | Analog | null | gauge, value, graph |
| Acceleration | accel | Acceleration | g | gauge, value, graph |
| Barometric pressure | bp | Pascal | pa | gauge, value, graph |
| Barometric pressure | bp | Hectopascal | hpa | gauge, value, graph |
| Battery | batt | % (0 to 100) | p | gauge, value, graph |
| Battery | batt | Ratio | r | gauge, value, graph |
| Battery | batt | Volts | v | gauge, value, graph |
| Carbon Dioxide | co2 | Parts per million | ppm | gauge, value, graph |
| Carbon Monoxide | co | Parts per million | ppm | gauge, value, graph |
| Counter | counter | Analog | null | gauge, value, graph |
| Current | current | Ampere | a | gauge, value, graph |
| Current | current | Milliampere | a | gauge, value, graph |
| Energy | energy | Killowatt Hour | kwh | gauge, value, graph |
| External Waterleak | ext_wleak | Analog | null | gauge, value, graph |
| Frequency | freq | Hertz | hz | gauge, value, graph |
| Gyroscope | g | Rotation per minute | rpm | gauge, value, graph |
| Gyroscope | g | Degree per second | dps | gauge, value, graph |
| Liquid | liquid | Liter | l | gauge, value, graph |
| Liquid | liquid | Milliliter | ml | gauge, value, graph |
| Luminosity | lum | Lux | lux | gauge, value, graph |
| Luminosity | lum | Volts | v | gauge, value, graph |
| Luminosity | lum | % (0 to 100) | p | gauge, value, graph |
| Luminosity | lum | Ratio | r | gauge, value, graph |
| Magnetometer | mag | Gauss | ga | gauge, value, graph |
| Magnetometer | mag | Tesla | tesla | gauge, value, graph |
| Motion | motion | Digital (0/1) | d | twostate |
| Nitrogen Dioxide | no2 | Parts per billion | ppb | gauge, value, graph |
| Ozone | o3 | Parts per million | ppm | gauge, value, graph |
| Particle Matter | pm | Micrograms | mg | gauge, value, graph |
| Power | pow | Watts | w | gauge, value, graph |
| Power | pow | Kilowatts | kw | gauge, value, graph |
| Proximity | prox | Centimeter | cm | gauge, value, graph |
| Proximity | prox | Meter | m | gauge, value, graph |
| Proximity | prox | Digital (0/1) | d | gauge, value, graph |
| Rain Level | rain_level | Centimeter | cm | gauge, value, graph |
| Rain Level | rain_level | Millimeter | mm | gauge, value, graph |
| Received signal strength indicator | rssi | dBm | dbm | gauge, value, graph |
| Relative Humidity | rel_hum | Percent (%) | p | gauge, value, graph |
| Relative Humidity | rel_hum | Ratio | r | gauge, value, graph |
| Resistance | res | Ohm | ohm | gauge, value, graph |
| Signal Noise Ratio | snr | Decibels | db | gauge, value, graph |
| Soil Moisture | soil_moist | Percent (%) | p | gauge, value, graph |
| Soil pH | soil_ph | Analog | null | gauge, value, graph |
| Soil Water Tension | soil_w_ten | Kilopascal | kpa | gauge, value, graph |
| Soil Water Tension | soil_w_ten | Pascal | pa | gauge, value, graph |
| Tank Level | tl | Analog | null | gauge, value, graph |
| Temperature | temp | Fahrenheit | f | gauge, value, graph |
| Temperature | temp | Celsius | c | gauge, value, graph |
| Temperature | temp | Kelvin | k | gauge, value, graph |
| Voltage | voltage | Volts | v | gauge, value, graph |
| Voltage | voltage | Millivolts | mv | gauge, value, graph |
| Wind Speed | wind_speed | Kilometer per hour | kmh | gauge, value, graph |
±-----------------------------------±-----------------±--------------------±-----------±--------------------+

When you are done editing the Python script head back to Terminal to restart it. After a few minutes you should see your updated widget in Cayenne displaying the temperature from your Enviro+ hat.

You should now be able to add more sensors quite easily just by modifying the python code. You will need to take the code from the additional Enviro+ examples and adapt this as needs be into your ‘SendData.py’ Cayenne script.

Have fun!!

[modified ‘SendData.py’]

#!/usr/bin/env python
import cayenne.client
import time
import logging

from bme280 import BME280

try:
    from smbus2 import SMBus
except ImportError:
    from smbus import SMBus

# Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
MQTT_USERNAME  = "XXXXX"
MQTT_PASSWORD  = "XXXXX"
MQTT_CLIENT_ID = "XXXXX"


client = cayenne.client.CayenneMQTTClient()
client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID, loglevel=logging.INFO)
# For a secure connection use port 8883 when calling client.begin:
# client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID, port=8883, loglevel=logging.INFO)

i=0
timestamp = 0


# our code from Enviroplus weather.py ----

logging.basicConfig(
    format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
    level=logging.INFO,
    datefmt='%Y-%m-%d %H:%M:%S')

logging.info("""weather.py - Print readings from the BME280 weather sensor.

Press Ctrl+C to exit!

""")

bus = SMBus(1)
bme280 = BME280(i2c_dev=bus)

# ----------------------------------



while True: 
    client.loop()
    
    if (time.time() > timestamp + 10):
        #client.celsiusWrite(1, i)
        #client.luxWrite(2, i*10)
        #client.hectoPascalWrite(3, i+800)
        
        #grab values from Enviro+ monitor. Grab code from Enviro examples
        #Temperature
        temperature = bme280.get_temperature()
        print ('Temperature ', temperature)
        
        #send temperature value to Cayenne
        
        client.virtualWrite(1, temperature, "temp", "c")
        
        
        timestamp = time.time()
        i = i+1

OK here’s the complete script which pulls in everything from the Enviro+ Hat and particulate sensor.
Ive attached a screenshot showing how this is presented within Cayenne.

#this python script works with Pimoroni Envrio+ Air Hat and the additional particle sensor
#shop.pimoroni.com
#this script reads in the BME280 weather sesonor, LTR-559 light and proximity sensor
#MICS6814 Gas sensor which detects 3 types of gases
#PMS5003 particulate matter sensor, reading in PM1, PM2.5 and PM10 particulates
#Triggers can be created within Cayenne dashboard as needed

#!/usr/bin/env python
import cayenne.client
import time
import logging
from pms5003 import PMS5003, ReadTimeoutError
from enviroplus import gas
import requests
import ST7735
#import ltr559
from ltr559 import LTR559
ltr559 = LTR559()

from bme280 import BME280

try:
    from smbus2 import SMBus
except ImportError:
    from smbus import SMBus

# Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
MQTT_USERNAME  = "axxxxxxxxxxx589c6b2"
MQTT_PASSWORD  = "17xxxxxxxxea2e3"
MQTT_CLIENT_ID = "39xxxxx1e6c"


client = cayenne.client.CayenneMQTTClient()
client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID, loglevel=logging.INFO)
# For a secure connection use port 8883 when calling client.begin:
# client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID, port=8883, loglevel=logging.INFO)

i=0
timestamp = 0


# our code from Enviroplus weather.py ----

logging.basicConfig(
    format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
    level=logging.INFO,
    datefmt='%Y-%m-%d %H:%M:%S')

logging.info("""weather.py - Print readings from the BME280 weather sensor.

Press Ctrl+C to exit!

""")

bus = SMBus(1)
bme280 = BME280(i2c_dev=bus)

# ----------------------------------

# Create PMS5003 instance
pms5003 = PMS5003()

pms5003.reset()
print ('resetting our pms')
time.sleep(5)

while True: 
    client.loop()
    
    if (time.time() > timestamp + 10):
        #client.celsiusWrite(1, i)
        #client.luxWrite(2, i*10)
        #client.hectoPascalWrite(3, i+800)
        
        #grab values from Enviro+ monitor. Grab code from Enviro examples
        #Temperature, humidity,pressure etc
        temperature = bme280.get_temperature()
        #print ('Temperature ', temperature)
        pressure = bme280.get_pressure()

        #print ('Pressure hPa ', pressure)
        humidity = bme280.get_humidity()
        
        #read our values in from our particle sensor
        pm_values = pms5003.read()
        
        #grab our individual particles
        #P2 - p2.5 combustion particles, organic compounds etc)
        P2 = pm_values.pm_ug_per_m3(2.5)
        #P10 - Dust, pollen, mould spores
        P10 = pm_values.pm_ug_per_m3(10)
        #P1 = ultrafine particles
        P1 = pm_values.pm_ug_per_m3(1)
        
        
        #print ('P2 ', P2)
        #print ('P1 ', P1)
        
        #grab our gases (reducing, oxidising, and NH3)
        readings = gas.read_all()
        reduce = readings.reducing
        oxidise = readings.oxidising
        nhthree = readings.nh3
        #print ('Gas Reducing',readings.reducing)
        
        #oxidising = gasreadings.oxidising()
        #nh3 = gasreadings.nh3
        
        #grab our light level in Lux
        lightlevel = ltr559.get_lux()
        
        #grab proximity - set a trigger in Cayenne if someone walks in front of it
        distance = ltr559.get_proximity()
        
        
        #send values to Cayenne
        
        client.virtualWrite(1, temperature, "temp", "c")
        client.virtualWrite(2, pressure, "bp", "hPa")
        client.virtualWrite(3, humidity, "rel_hum", "%")
        
        client.virtualWrite(4, P1, "pm", "micrograms")
        client.virtualWrite(5, P2, "pm", "micrograms")
        client.virtualWrite(6, P10, "pm", "micrograms")
        
        client.virtualWrite(7, reduce, "null")
        client.virtualWrite(8, oxidise, "null")
        client.virtualWrite(9, nhthree, "null")
        
        client.virtualWrite(10, lightlevel, "lum", "%")
        client.virtualWrite(11, distance, "prox", "Centimeter")
        
        
        
        timestamp = time.time()
        i = i+1
        print (timestamp)