-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #29 from philippeportesppo/service_manager_upnp
Service manager upnp
- Loading branch information
Showing
12 changed files
with
1,044 additions
and
50 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,4 +58,4 @@ def handleLevel(evt) { | |
thermostat.each {it.setThermostatFanMode("auto");} | ||
|
||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
This folder contains the smartapp and devicehandlers before I used the GitHubIntegration. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,47 @@ | ||
# AirMentorPro2_SmartThings | ||
|
||
<img src="https://github.com/philippeportesppo/AirMentorPro2_SmartThings/blob/master/overallsetup.png" alt="Overall Setup Icon" style="width:50%;height:50%;"> | ||
|
||
What you need: | ||
|
||
Raspberry PI 3 with Apache2 and PHP5 installed properly<p></p> | ||
Raspberry Pi PHP and Apache installation instructions (https://www.raspberrypi.org/documentation/remote-access/web-server/apache.md) | ||
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)<p></p> | ||
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 using the oringinal image on it, I used this external one then. Then recently, the Raspberry Pi internal BTLE works fine using Linux raspberrypi 4.4.50-v7+ #970 SMP Mon Feb 20 19:18:29 GMT 2017 armv7l GNU/Linux< image from the Raspberry website <p></p> | ||
Additional installation on Raspberry:<p></p> | ||
<li>Bluez (http://www.elinux.org/RPi_Bluetooth_LE)<p></p></li> | ||
<li>BluePy (https://github.com/IanHarvey/bluepy)<p></p></li> | ||
<li>requests (http://raspberrypi-aa.github.io/session4/requests.html)<p></p></li> | ||
Put in Raspberry /var/www/html folder the file : airmentorpro2.php<p></p> | ||
Put in /home/pi/Documents the python script airmentorpro2.py<p></p> | ||
You will launch this first python script by: <b>sudo /usr/bin/python /var/www/html/airmentorpro2.py [your AirMentor MAC] [your hci#] & </b><p></p>Example: sudo /usr/bin/python /var/www/html/airmentorpro2.py fe:ed:fa:ce:be:ef 0 & <p></p>As this script runs an infinit loop, better to fork it with &<p></p> | ||
Put in /home/pi/Documents the python script undergroundweather.py<p></p> | ||
This requires you to get a Weather UnderGround API key from https://www.wunderground.com/weather/api/<p></p>The information is used to provide more data about outside conditions. If you don't want to use this, check the previous versions of the DTH and html page on this GitHub.<p></p> | ||
<p></p> You will launche this script by: <b>sudo /usr/bin/python /var/www/html/undergroundweather.py [yourAPI key] [state] [city] &</b> | ||
<p>As this script runs an infinit loop, better to fork it with & too</p> | ||
In Smartthings IDE: Create a Device Handler (then save and publish for yourself) from air-mentor-pro-2.groovy <p></p> | ||
In Smartthings IDE: Create a SmartApp (then save and publish for yourself) from SmartApp.groovy. The Smartapp is here to allow the alerting on pollution level and polluant. This is an optional app, if you don't want to be notified, you can ignore it.<p> | ||
In Smartthings IDE: Create a SmartApp (then save and publish for yourself) from iaq-vent.groovy. The Smartapp is here to allow piloting vents/swtiches based on selected levels of IAQ</p> | ||
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...<p></p> | ||
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<p></p> | ||
<b>IMPORTANT:</b> use pollster smartapp to cadence the polling (every 5min) otherwise, Smartthing known issue will let the DTH stoping the polling after 24h or so.<p></p> | ||
<h2>Raspberry Pi 3 side: | ||
|
||
<h3>1. Apache2 and PHP5 properly installed <a href="https://www.raspberrypi.org/documentation/remote-access/web-server/apache.md"> (see here) </a> <p> | ||
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)<p></p> | ||
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 using the oringinal image on it, I used this external one then. Then recently, the Raspberry Pi internal BTLE works fine using Linux raspberrypi 4.4.50-v7+ #970 SMP Mon Feb 20 19:18:29 GMT 2017 armv7l GNU/Linux< image from the Raspberry website (or above)<p></p> | ||
<h3>2. Additional installation on Raspberry:<p> | ||
<h4>Bluez (http://www.elinux.org/RPi_Bluetooth_LE)<p></p> | ||
<h4>BluePy (https://github.com/IanHarvey/bluepy)<p></p> | ||
<h4>>requests (http://raspberrypi-aa.github.io/session4/requests.html)</li><p></p><p> | ||
<h3>3. This project files installation:<p> | ||
<li>Put in Raspberry /var/www/html folder the file : airmentorpro2.php</li> | ||
<li>Put in /home/pi the python script airmentorpro2.py</li> | ||
<li>Put in /home/pi the python script ssdp_server.py (this file uses wlan0 as interface. You can change the code to use eth0 or other)</li> | ||
<li>Put in /home/pi the python script undergroundweather.py</li> | ||
This requires you to get a Weather UnderGround API key from https://www.wunderground.com/weather/api/<p></p>The information is used to provide more data about outside conditions. If you don't want to use this, check the previous versions of the DTH and html page on this GitHub. | ||
<li>Create a folder "lib" in /home/pi</li> | ||
<li>Put in /home/pi/lib the 2 files ssdp.py and upnp_http_server.py.</li> | ||
This is mandatory the 2 files are in a lib folder and the lib folder at the same location as ssdp_server.py | ||
<li>In <b>/etc/rc.local</b>, just before the exit 0 (last line):</li> | ||
<li>add: <b>sudo /usr/bin/python /home/pi/airmentorpro2.py [your AirMentor MAC] [your hci#] & </b><p></p>Example: sudo /usr/bin/python /home/pi/airmentorpro2.py fe:ed:fa:ce:be:ef 0 & <p></p></li> | ||
|
||
<li>add: <b>/usr/bin/python /home/pi/ssdp.py &</b></li> | ||
<li>add <b>sudo /usr/bin/python /var/www/html/undergroundweather.py [yourAPI key] [state] [city] &</b></li> | ||
|
||
<h2>Smarthings IDE side:<p> | ||
|
||
<li> with a github enabled SmartThings IDE (see <a href=http://docs.smartthings.com/en/latest/tools-and-ide/github-integration.html > here</a>), import the namespace philippeportesppo and repository AirMentorPro2_SmartThings on master branch</li> and the following device handlers and smartapps: | ||
<h3>SmartApps: | ||
<li>AirMentor Pro UPnP Service Manager : the smartapp managing the ssdp discover.</li> | ||
<li>IAQ_vent : the smartapp managing vents and AC fans upon air quality notification</li> | ||
<li>Notify Me When for AirMentor Pro : the smartapp managing events and notify you about the air quality.</li> | ||
<h3>Device Handler: | ||
<li>Air Mentor Pro 2 : the device handler to access the AirMentor Pro 2</li> | ||
<p> | ||
If you cannot access github integration, you might have to create the devicehandler and smartapps manually from the code. | ||
|
||
<h2>SmartThing Mobile app:<p> | ||
<li>Go to Smartapps section and add the <b>AirMentor Pro UPnP Service Manager</b> smartapp from "My Apps"</li> | ||
<li>Start the research, few seconds later the pi will be discovered, select it, press next and save.</li> | ||
<li>AirMentor Pro 2 will be added | ||
|
||
|
||
<h3><b>IMPORTANT:</b></h3> use pollster smartapp to cadence the polling (every 5min) otherwise, Smartthing known issue will let the DTH stoping the polling after 24h or so.<p></p> | ||
Hope you like it. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
198 changes: 198 additions & 0 deletions
198
...ortesppo/airmentor-pro-upnp-service-manager.src/airmentor-pro-upnp-service-manager.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
/** | ||
* Generic UPnP Service Manager | ||
* | ||
* Copyright 2018 Philippe Portes based on SmartThings original UPnP Service Manager SmartApp | ||
* | ||
* 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: "AirMentor Pro UPnP Service Manager", | ||
namespace: "philippeportesppo", | ||
author: "Philippe Portes", | ||
description: "Discover and configures your Raspberry Pi bridge to AirMentor Pro (see https://github.com/philippeportesppo/AirMentorPro2_SmartThings).", | ||
category: "SmartThings Labs", | ||
iconUrl: "https://raw.githubusercontent.com/philippeportesppo/AirMentorPro2_SmartThings/master/images/app-icon_bw.png", | ||
iconX2Url: "https://raw.githubusercontent.com/philippeportesppo/AirMentorPro2_SmartThings/master/images/app-icon_bw.png", | ||
iconX3Url: "https://raw.githubusercontent.com/philippeportesppo/AirMentorPro2_SmartThings/master/images/app-icon_bw.png") | ||
|
||
|
||
preferences { | ||
page(name: "searchTargetSelection", title: "UPnP AirMentor via Pi Search Target", nextPage: "deviceDiscovery") { | ||
section("Search Target") { | ||
input "searchTarget", "string", title: "Search Target", defaultValue: "urn:schemas-upnp-org:device:AirMentorPro2:1", required: true | ||
} | ||
} | ||
page(name: "deviceDiscovery", title: "UPnP Device Setup", content: "deviceDiscovery") | ||
} | ||
|
||
def deviceDiscovery() { | ||
def options = [:] | ||
def devices = getVerifiedDevices() | ||
devices.each { | ||
def value = it.value.name ?: "AirMentorPro ${it.value.ssdpUSN.split(':')[1][-3..-1]}" | ||
def key = it.value.mac | ||
options["${key}"] = value | ||
} | ||
|
||
ssdpSubscribe() | ||
|
||
ssdpDiscover() | ||
verifyDevices() | ||
|
||
return dynamicPage(name: "deviceDiscovery", title: "Discovery Started!", nextPage: "", refreshInterval: 5, install: true, uninstall: true) { | ||
section("Please wait while we discover your AirMentorPro Device. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { | ||
input "selectedDevices", "enum", required: false, title: "Select Devices (${options.size() ?: 0} found)", multiple: true, options: options | ||
} | ||
} | ||
} | ||
|
||
def installed() { | ||
log.debug "Installed with settings: ${settings}" | ||
|
||
initialize() | ||
} | ||
|
||
def updated() { | ||
log.debug "Updated with settings: ${settings}" | ||
|
||
unsubscribe() | ||
initialize() | ||
} | ||
|
||
def initialize() { | ||
unsubscribe() | ||
unschedule() | ||
|
||
ssdpSubscribe() | ||
|
||
if (selectedDevices) { | ||
log.debug "Selected device" | ||
addDevices() | ||
} | ||
|
||
runEvery5Minutes("ssdpDiscover") | ||
} | ||
|
||
void ssdpDiscover() { | ||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery ${searchTarget}", physicalgraph.device.Protocol.LAN)) | ||
} | ||
|
||
void ssdpSubscribe() { | ||
subscribe(location, "ssdpTerm.${searchTarget}", ssdpHandler) | ||
} | ||
|
||
Map verifiedDevices() { | ||
def devices = getVerifiedDevices() | ||
def map = [:] | ||
devices.each { | ||
def value = it.value.name ?: "AirMentorPro ${it.value.ssdpUSN.split(':')[1][-3..-1]}" | ||
def key = it.value.mac | ||
map["${key}"] = value | ||
log.debug map | ||
} | ||
map | ||
} | ||
|
||
void verifyDevices() { | ||
def devices = getDevices().findAll { it?.value?.verified != true } | ||
devices.each { | ||
int port = convertHexToInt(it.value.deviceAddress) | ||
String ip = convertHexToIP(it.value.networkAddress) | ||
String host = "${ip}:${port}" | ||
sendHubCommand(new physicalgraph.device.HubAction("""GET ${it.value.ssdpPath} HTTP/1.1\r\nHOST: $host\r\n\r\n""", physicalgraph.device.Protocol.LAN, host, [callback: deviceDescriptionHandler])) | ||
} | ||
} | ||
|
||
def getVerifiedDevices() { | ||
getDevices().findAll{ it.value.verified == true } | ||
} | ||
|
||
def getDevices() { | ||
if (!state.devices) { | ||
state.devices = [:] | ||
} | ||
state.devices | ||
} | ||
|
||
def addDevices() { | ||
def devices = getDevices() | ||
|
||
log.debug devices | ||
|
||
selectedDevices.each { dni -> | ||
def selectedDevice = devices.find { it.value.mac == dni } | ||
log.debug selectedDevice | ||
def d | ||
if (selectedDevice) { | ||
d = getChildDevices()?.find { | ||
it.deviceNetworkId == selectedDevice.value.mac | ||
} | ||
} | ||
|
||
if (!d) { | ||
addChildDevice("philippeportesppo", "Air-Mentor-Pro-2", selectedDevice.value.mac, selectedDevice?.value.hub, [ | ||
"label": selectedDevice?.value?.name ?: "AirMentorPro", | ||
"data": [ | ||
"mac": selectedDevice.value.mac, | ||
"ip": selectedDevice.value.networkAddress, | ||
"port": "0050", | ||
"query_path":"/airmentorpro2.php?Action=get" | ||
] | ||
]) | ||
} | ||
} | ||
} | ||
|
||
def ssdpHandler(evt) { | ||
def description = evt.description | ||
def hub = evt?.hubId | ||
|
||
def parsedEvent = parseLanMessage(description) | ||
parsedEvent << ["hub":hub] | ||
|
||
def devices = getDevices() | ||
String ssdpUSN = parsedEvent.ssdpUSN.toString() | ||
|
||
if (devices."${ssdpUSN}") { | ||
def d = devices."${ssdpUSN}" | ||
//log.debug d | ||
//log.debug parsedEvent | ||
if (d.networkAddress != parsedEvent.networkAddress || d.deviceAddress != parsedEvent.deviceAddress) { | ||
d.networkAddress = parsedEvent.networkAddress | ||
d.deviceAddress = parsedEvent.deviceAddress | ||
def child = getChildDevice(parsedEvent.mac) | ||
log.debug "Child: " | ||
if (child) { | ||
log.debug child | ||
child.sync(parsedEvent.networkAddress, parsedEvent.deviceAddress) | ||
} | ||
} | ||
} else { | ||
devices << ["${ssdpUSN}": parsedEvent] | ||
} | ||
} | ||
|
||
void deviceDescriptionHandler(physicalgraph.device.HubResponse hubResponse) { | ||
def body = hubResponse.xml | ||
def devices = getDevices() | ||
def device = devices.find { it?.key?.contains(body?.device?.UDN?.text()) } | ||
if (device) { | ||
device.value << [name: body?.device?.roomName?.text(), model:body?.device?.modelName?.text(), serialNumber:body?.device?.serialNum?.text(), verified: true] | ||
} | ||
} | ||
|
||
private Integer convertHexToInt(hex) { | ||
Integer.parseInt(hex,16) | ||
} | ||
|
||
private String convertHexToIP(hex) { | ||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".") | ||
} |
Oops, something went wrong.