Successfully Integrated Air Quality Monitor "Air Mentor Pro 2" in SmartThings using Raspberry Pi

requires_server
dth_hvac
project_hvac

(PPO16) #1

Hi there,
Update 8/12: fixed the icons for iOS so that they don’t cover the labels. Android was already ok. Please check the newest Master versions of my github.
Update 5/5: solved Mitchell issue: needed to pass the mac address to python script in lowercase, not uppercase. Also use now external dongle.

IMPORTANT: use pollster smartapp to candence the polling (every 5min) otherwise, Smartthing known issue will let the DTH stoping the polling after 24h or so.

I bought 2 years ago this excellent device called “Air Mentor Pro 2” (https://www.amazon.com/Mentor-Indoor-Quality-Monitor-8096-AP/dp/B0114LE05O) which is an Air Quality autonomous device.

It allows to monitor different hazards for health like CO2, PM2.5, PM10, VOC, temperature and humidity. It also provides an easy to ready index of air quality (IAQ) and shines various colors according to the IAQ. Really great stuff except on one aspect: it connects to a smartphone by Bluetooth LE which basically makes it useful when you are in the same room. I had discussed with CoAsia guys last year at Computex in Taipei and they were to deliver a wifi access to it via a kind of dongle. Unfortunately, this is a proprietary setup. So I decided to create a device handler to integrate it in ST.

The original app page is like ths picture:

First step was to get a BT LE sniffer and spy the traffic, then identify the data inside the packets and mimic that with a Raspberry PI. The PI was the solution I found after checking what python library allows to manipulate Bluetooth LE GATT information.
The PI allows that easily and is also hosting a webpage I use to store and give external access to the device handler.

On the picture, the below path is the standard Air Mentor way, above one is my implementation.

What you need:

Raspberry PI 3 with Apach2 and PHP installed properly
Raspberry Pi PHP and Apache installation instructions

Assign a static IP address to your raspberry on your local network. This project works only if your Hub and raspberry are on same network(otherwise the HubAction won’t work and you need to implement external HTTPrequest instead)

[Optional:]1 USB dongle BT-LE (Plugable Dual-Mode BT-LE/BT model USB-BT4LE)
I didn’t make it with the internal BT-LE of the Pi initially so I used this external one. But then, I managed since to make the internal one working too.

Additional installation on Raspberry::

Put in Raspberry /var/www/html folder the file : airmentorpro2.php airmentorpro2.php

Put in /home/pi/Documents the python script airmentorpro2.py
You will launch this first python script by:
sudo python airmentorpro2.py [your AirMentor MAC] [your hci port #]&
Example:
sudo python airmentorpro2.py fe:ed:be:ef:fa:ce 0 &
As this script runs an infinit loop, better to fork it with &

Put in /home/pi/Documents the python script undergroundweather.py
This requires you to get a Weather UnderGround API key from https://www.wunderground.com/weather/api/
The information is used to provide more data about outside conditions.
Example: python undergroundweather 183aaabbbcccc ca Sunnyvale &

In Smartthing IDE: Create a Device Handler (then save and publish for yourself) from AirMentorDTH.groovy

In Smartthing IDE: Create a SmartApp (then save and publish for yourself) from SmartApp.groovy
The Smartapp is here to allow the alerting on high and very high pollution. You can tweak the smartappto also get alerts on medium pollution.

Create a device in Smartthings web page based on this device handler. Put anything as Device Network Id as the Device Handler will overwrite it at first run. Don’t ever change it after if your raspberry doesn’t change its static IP address otherwise, the parse method is sent for some reason to the former device despite the HubAction is sent by the new instance…

Configure the Smarthing device with the IP, port of the Raspberry and URL of the webpage and self-refreshing regularly.
You can also access the web page directly by a http://[yourraspberry IP]/airmentorpro2.php?Action=get

Hope you like it.


Taiwan User Gathering
sendHubCommand - for SmartThings staffers please
(Mitchell Lu) #5

@Philippe_Portes I am the one, Could be the only one in Taiwan that play all the SmartThings stuff, I follow our your documents and instructions, and I have Raspberry Pi 3 and I have them connect to bluetooth , and here is info or the Air Mentor Pro 2 I connect at Raspberry Pi 3

[bluetooth]# info EC:F0:0E:5F:99:5E
Device EC:F0:0E:5F:99:5E
_ Name: Air Mentor Pro_
_ Alias: Air Mentor Pro_
_ Appearance: 0x0200_
_ Paired: yes_
_ Trusted: yes_
_ Blocked: no_
_ Connected: yes_
_ LegacyPairing: no_
_ UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)_
_ UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)_
_ UUID: Device Information (0000180a-0000-1000-8000-00805f9b34fb)_
_ UUID: Battery Service (0000180f-0000-1000-8000-00805f9b34fb)_
_ UUID: Vendor specific (2dd62dfe-b55c-4d82-ac85-212f500b512e)_
_ UUID: Vendor specific (9e5d1e47-5c13-43a0-8635-82ad38a1386f)_

I also have below process running

root@raspberrypi:/home/pi/Documents# sudo python airmentorpro2.py EC:F0:0E:5F:99:5E&
[3] 18983
root@raspberrypi:/home/pi/Documents# EC:F0:0E:5F:99:5E

I have update all your new *php,*py, *groov file,  but I do not get anything  at SmartThings Air Mentor Pro 2 device, but look at the device logging at Web develop platform, I wonder what is going on of the DTH code,, Please help to advise

5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: error java.lang.NumberFormatException: empty String @ line 257
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug Battery:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug IAQ:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug TVOC:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug humidity:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug Temperature_Cal:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug Temperature:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug PM10:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug PM2.5:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug CO2:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:06:00: debug Executing 'parse’
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: error java.lang.NumberFormatException: empty String @ line 257
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug Battery:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug IAQ:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug TVOC:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug humidity:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug Temperature_Cal:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug Temperature:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug PM10:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug PM2.5:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug CO2:
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:03:00: debug Executing 'parse’
5cbb8fb8-e37a-4131-ad95-195a3cf9b957 下午3:00:03: error java.lang.NumberFormatException: empty String @ line 257


(Mitchell Lu) #7

@Philippe_Portes
Here is the Webpage to load the Raspberry Pi 3 IP address


Here is the DTH Setup

Here is DTH code at #256-258

You do mention that
"if you only see the header and no data in the table then it is something wrong between the php and the py scipts."

I also have no confidence at php and py script, as it need to install a lot Phyton module which I a not sure if I have install everything right, such at below 2 instruction, It is too rough that I do my best to guess and try to reference other document, but please advise what is correct install command and procedure at below

-Installation on Raspberry of Gatttool and all dependencies
-Installation on Raspberry of python libs: requests, pexpect, bluepy ?


(Mitchell Lu) #10

@Philippe_Portes

No matter I I how add/edit/copy the *py file You have add, and execute the command at below all got error, Would you help me to check what is going on of the py files, I edit by atom

pi@raspberrypi:~/Documents $ python airmentorpro2.py
File “airmentorpro2.py”, line 43
payload = {}
^
IndentationError: expected an indented block
pi@raspberrypi:~/Documents $ python airmentorpro2.py EC:F0:0E:5F:99:5E&
[1] 7985
pi@raspberrypi:~/Documents $ File “airmentorpro2.py”, line 43
payload = {}
^
IndentationError: expected an indented block
#!/usr/bin/env python

Philippe Portes February 2017 based on Michael Saunby. April 2013

Licensed under the Apache License, Version 2.0 (the “License”);

you may not use this file except in compliance with the License.

You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software

distributed under the License is distributed on an “AS IS” BASIS,

WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

See the License for the specific language governing permissions and

limitations under the License.

import sys
import requests
import math
from bluepy.btle import Scanner, DefaultDelegate

class AirMentorProDelegate(DefaultDelegate):
def init(self, bluetooth_adr):
self.adr = bluetooth_adr
self.s=requests.Session()
self.AMP_CO2=0
self.AMP_PM25=0
self.AMP_PM10=0
self.AMP_TVOC=0
self.AMP_HUM=0.0
self.AMP_TEM=0.0
self.AMP_IAQ=0
self.AMP_BATT=0
self.AMP_CHARG=0
self.AMP_TEMP_CAL=0.0
DefaultDelegate.init(self)

def handleDiscovery(self, dev, isNewDev, isNewdata):
    #print "Notification received #2"
    if dev.addr == self.adr:
        for scan in dev.getScanData():
            if scan[0] == 0xff: # Proprietary
            payload = {}
            if int(scan[2][1:3],16)==0x22:
                print scan,
                hex_data = scan[2]
	    #[TVOC][Temp][TempDelta][Humi][IAQ ]
		#[2221][00b9][1963][33       ][  2d][0145]
        self.AMP_TVOC=int(hex_data[4:8],16)
        print "AMP_TVOC: ",self.AMP_TVOC,
        self.AMP_TEM=(float(int(hex_data[8:12],16))-4000)*0.01
        print "AMP_TEM: ",self.AMP_TEM,
        self.AMP_TEMP_CAL=self.AMP_TEM-(float(int(hex_data[12:14],16)))*0.1
        print "AMP_TEMP_CAL: ",self.AMP_TEMP_CAL,
        self.AMP_HUM=float(int(hex_data[14:16],16))*math.exp(self.AMP_TEM*17.62/(self.AMP_TEM+243.12))/math.exp(self.AMP_TEMP_CAL*17.62/(self.AMP_TEMP_CAL+243.12))
        print "AMP_HUM: ",self.AMP_HUM,
        self.AMP_IAQ=int(hex_data[16:],16)
        print "AMP_IAQ: ",self.AMP_IAQ
        try:
            requests.get("http://localhost/airmentorpro2.php?Action=set&CO2="+str(self.AMP_CO2)+"&PM25="+str(self.AMP_PM25)+"&PM10="+str(self.AMP_PM10)+"&TEM_ACT="+str(self.AMP_TEM)+"&TEM_CAL="+str(self.AMP_TEMP_CAL)+"&HUM="+str(self.AMP_HUM)+"&TVOC="+str(self.AMP_TVOC)+"&IAQ="+str(self.AMP_IAQ)+"&BATT="+str(self.AMP_BATT), data=payload)
		except:
            print "Couldn't send request..."
        else:
		if int(scan[2][1:3],16)==0x12:
		    hex_data = scan[2]
		    #       [CO2 ][PM25][PM10][CO O3]
		    # [2121][2710][0003][0003][00 00]
            print scan,
            self.AMP_CO2=int(hex_data[4:8],16)
            print "AMP_CO2",self.AMP_CO2,
            self.AMP_PM25=int(hex_data[8:12],16)
            print "AMP_PM25",self.AMP_PM25,
            self.AMP_PM10=int(hex_data[12:16],16)
            print "AMP_PM10",self.AMP_PM10

            #AMP_BATT=int(hex_data[16],16)
            #print "self.AMP_BATT",self.AMP_BATT,
            try:
                requests.get("http://localhost/airmentorpro2.php?Action=set&CO2="+str(self.AMP_CO2)+"&PM25="+str(self.AMP_PM25)+"&PM10="+str(self.AMP_PM10)+"&TEM_ACT="+str(self.AMP_TEM)++"&TEM_CAL"+str(self.AMP_TEMP_CAL)+"&HUM="+str(self.AMP_HUM)+"&TVOC="+str(self.AMP_TVOC)+"&IAQ="+str(self.AMP_IAQ)+"&BATT="+str(self.AMP_BATT), data=payload)
		    except:
                print "Couldn't send request..."
            #else:
            #print "[",scan[0],":", scan[2], "]"

def main():
global datalog
global barometer

bluetooth_adr = sys.argv[1]

print  bluetooth_adr

while True:
    try:
        # BTLE UUSB is on hci1, so pass 1 to Scanner
        scanner = Scanner(1).withDelegate(AirMentorProDelegate(bluetooth_adr))



    while(1):
        scanner.start()
        scanner.process(1)
        scanner.stop()


    except:
        pass

if name == “main”:
main()


(PPO16) #11

Indentation error come from tour copy paste. Python programs must respect a 4 spaces indentation.

Pleaae go to my github and download the python script so that you won’t face this issue.

Otherwise you can also re-indent the file you have manually.


(Mitchell Lu) #12

@Philippe_Portes I fix the Indentation error, and I also try to re-install Python 2.7.9, bluesy…and everything I could install, but I still got see situation as I talk to you at first beginning, and all the snapshot ae remain same,

How I can debug if the data from Air mentor Pro 2 to Raspberry Pi 3 is success or not ? as since now, I can not see anything transfer out to Raspberry Pi 3…


(Mitchell Lu) #14

@Philippe_Portes

I do have hci in hci0. and I modify to
scanner = Scanner(0).withDelegate(AirMentorProDelegate(bluetooth_adr)) but nothing happen
here is my hciconfig, Do i do anything at install bluetooth

hci0: Type: BR/EDR Bus: UART
BD Address: B8:27:EB:D7:B5:1A ACL MTU: 1021:8 SCO MTU: 64:1
UP RUNNING PSCAN ISCAN
RX bytes:104025 acl:500 sco:0 events:3549 errors:0
TX bytes:12210 acl:96 sco:0 commands:922 errors:0
Features: 0xbf 0xfe 0xcf 0xfe 0xdb 0xff 0x7b 0x87
Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
Link policy: RSWITCH SNIFF
Link mode: SLAVE ACCEPT
Name: 'raspberrypi’
Class: 0x000000
Service Classes: Unspecified
Device Class: Miscellaneous,
HCI Version: 4.1 (0x7) Revision: 0xb6
LMP Version: 4.1 (0x7) Subversion: 0x2209
Manufacturer: Broadcom Corporation (15)


(PPO16) #15

The only thing I am thinking is that you are using the internal Pi BT HCI (as it shows UART) while I am using an external dongle. I didn’t understand the internal Pi BT was supporting BT LE.
I am using USB dongle BT-LE (Plugable Dual-Mode BT-LE/BT model USB-BT4LE).

Maybe you can have a try.


(Mitchell Lu) #16

@Philippe_Portes I have another external USB bluetooth which is essence D704, ad I plug to Raspberry Pi 3 and it is hci1, and I also check with that,same thing just like I did at hci0

I wonder if any process or daemon that I did not install so I can not receive any data from Air Mentor pro 2, and now the firmware of Air Mentor Pro 2 is 2.14..Do you think Is that could be the problem

Any way I can debug if Air Mentor Pro 2 did send out the data through Bluetooth, but hci0 or hci1 in Raspberry Pi 3 did not receive that or decode that ?!!


(Mitchell Lu) #18

@Philippe_Portes

Could you check if your Air Mentor Pro 2 is under  2.1.4, and  Does it still working at your SmartThings.

I have no way to check what is going on ?, either Air Mentor Pro 2 did not send out the data or the Raspberry Pi 3 did not read the data and interpret correctly


(joe s) #19

I just got a Pi3 to connect to my air mentor. I Cant seem to get the data from the air mentor to the Pi. Was it decided that the built in pi 3 radio will not connect to the air mentor? I can run python airmentorpro2.py ec:f0:0e:49:34:9f 0 & fine but no data makes it to the served website, in addition while the website shows the weater underground data, that data does not sappear in my smart things app.


(PPO16) #20

Good to know.that you got it working after our debug session!

Enjoy


(joe s) #21

Hi Philippe. I am trying to activate a switch based on the CO2 level reported by air mentor pro 2 output I was planning on modifying the smart app CO2 vent but I can’t seem to get it to recognize the air mentor pro2 as a device the. any thought? I stole the below smart app and modified the code for a netamo base station.

/**

  • CO2 Vent
  • Copyright 2014 Brian Steere
  • Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except
  • in compliance with the License. You may obtain a copy of the License at:
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.

*/
definition(
name: “CO2 Vent”,
namespace: “dianoga”,
author: “Brian Steere”,
description: “Turn on a switch when CO2 levels are too high”,
category: “Health & Wellness”,
iconUrl: “https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png”,
iconX2Url: “https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png
)

preferences {
section(“CO2 Sensor”) {
input “Air_Mentor_Pro_2”, “device.Air_Mentor_Pro_2”, title: “Sensor”, required: true
input “level”, “number”, title: “CO2 Level”, required: true
}

section("Ventilation Fan") {
	input "switches", "capability.switch", title: "Switches", required: true, multiple: true
}

}

def installed() {
log.debug “Installed with settings: ${settings}”

initialize()

}

def updated() {
log.debug “Updated with settings: ${settings}”

unsubscribe()
initialize()

}

def initialize() {
state.active = false;
subscribe(sensor, “carbonDioxide”, ‘handleLevel’)
}

def handleLevel(evt) {
def co2 = sensor.currentValue(“co2level”).toInteger()
log.debug “CO2 Level: ${co2} / ${settings.level} Active: ${state.active}”

if(co2 >= settings.level && !state.active) {
	log.debug "Turning on"
	switches.each { it.on(); }
    state.active = true;
} else if(co2 < settings.level && state.active) {
	log.debug "Turning off"
	state.active = false;
    switches.each { it.off(); }
}

}


(PPO16) #22

Hi Joe,

I was preparing to answer your questions by reviewing your code and in the end, I wrote a smartapp…

This is based on CO2_vent from Brian Steere but I basically remove the logic to adapt it to my DTH.

Back to your code, you were missing few aspects so you can refer to mine for more details:

  1. To allow the smartapp to find AirMentor, you have to specify in the input a capability the DTH has among:

     capability "sensor"
     capability "Carbon Dioxide Measurement"
     capability "capability.temperatureMeasurement"
     capability "capability.relativeHumidityMeasurement" 
    

So you can use:

     input "Air_Mentor_Pro_2", "capability.carbonDioxideMeasurement", title: "IAQ Sensor", required: true
  1. You have to register a smartapp handler to events you want to monitor the values.
    I see you planned to use values for CO2.

I would make it simple and use IAQ levels so that whatever goes wrong between PM2.5, PM10, TVOC or CO2, you just have one indication to monitor and you can conveniently use “good, moderate, etc…” to trigger the vents instead of numbers.

If you really want to only address CO2, then you would have to listen to the events “co2level”, not “carbonDioxide” since my DTH doesn’t send such event. co2level has the value displayed in the Airmentor CO2 tile.

The advantage of your method is indeed to be able to trigger upon CO2 values that are constantly monitored by the DTH (every x minutes depending on your pollster setting).
My method suppose to define the levels to trigger the vent and those to stop it. So if you go with my method, you have to make sure the DTH for Airmentor you use is the latest version on my GitHub that enable the events sending for Moderate and Good.
I initially had ignore them to avoid getting hundreds of notifications when the air is good/moderate, specially as mine is very sensitive so it easily jitter between these 2 level while this has not a real interest, health wise.
So in the last version of the DTH, I re-enabled the good and moderate notifications and setup a state check so that I only fire events if they are different from the previous level for this poluant/IAQ.

Let me know if you need further help. I am lacking of ideas so I am happy to help


(joe s) #23

This is awesome, Yes i too realized it is quite sensitive, but you are right that regardless of the air problem PM2.5, CO2, or VOC the response should be cycle the air. I wonder, if in addition to a switch Is there a way to trigger a thermostat to turn its fan on for x minutes after the air mentor trigger?


(PPO16) #24

Which smart thermostat are you using?

I can check if something is possible but a thermostat doesn’t act like a switch. So what is needed is to rely on a always “auto” set thermostat and send it the fan mode ‘on’.
My concern is the way the fan is declared. I just checked the zenthermostat sources and there is no fan capability declared and no on odf actions associated, so the hack for this one would be to send the event “fanMode” with the value “on” from my smartapp. But maybe for another thermostat the message wpuld be different.


(joe s) #25

I am using the honey well thermostat Honeywell TCC 8000/9000 is the
handler I use. It looks like the fan has on, auto and circulate as
supported fan modes.


(PPO16) #26

ok good, I use the same so I can have a try later today and send you an updated smartapp if i make it.

Stay tuned.


(PPO16) #27

Ok, @smarterjp, can you try https://github.com/philippeportesppo/AirMentorPro2_SmartThings/blob/master/iaq-vent.groovy ?

I added the thermostat fan management. I tried with mine just now and it works fine on my side.


(PPO16) #28

Have you tried the code I provided you ?

Just curious…