Skip to content

Commit f9e4337

Browse files
author
Nate Clark
authored
Merge pull request #18 from heythisisnate/easy-oauth
Easy OAuth
2 parents b3afe74 + 7bb241f commit f9e4337

16 files changed

+336
-157
lines changed

README.md

+77-51
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,53 @@
11
# SmartThings Connected Wired Security System using a NodeMCU ESP8266
22

3-
This project will help you connect SmartThings to wired contact sensors (for doors and windows) and motion sensors that you may already have pre-wired in your home from a built-in home security system. There are three components to the project:
3+
This project will help you connect wired contact sensors and motion sensors from an old wired home alarm system (such as
4+
Honeywell, ADT, Interlogix, etc) to Samsung SmartThings. Convert your old wired alarm system into an internet connected
5+
Smart Alarm!
6+
7+
We use an inexpensive NodeMCU ESP82660 wifi enabled development board to connect our wired alarm system sensors to the
8+
SmartThings.
9+
10+
There are three components to the project:
411

512
1. a SmartThings Device Handler for contact sensors and motion sensors
6-
2. a SmartThings SmartApp that receives HTTP POST messages
13+
2. a SmartThings SmartApp that interfaces with the Wifi-connected device in your home
714
3. Lua code for the NodeMCU device that connects your wired system to the cloud
815

16+
## Update June 17 2017
17+
#### Release 1.6: Easy OAuth and Pre-Loaded Kits Available for Pre-order!
18+
19+
**Easy OAuth:** Many people who contacted me for help were having trouble with the OAuth flow. In the latest [1.6 release](https://github.com/heythisisnate/nodemcu-smartthings/releases/tag/1.6)
20+
this is now much simpler! You no longer have to manually do the OAuth step. Just open your browser, copy and paste your OAuth
21+
Client ID and Secret when prompted, and the application handles the rest.
22+
23+
**Pre-loaded Kits for Sale!** I'm working hard to make it as easy as possible for anyone to connect their wired alarm system
24+
to SmartThings, so I've decided to begin selling all-inclusive DIY kits with this software pre-loaded! With one of my DIY kits,
25+
there's no flashing or code to modify. Simply wire your sensors and alarm following the online instructions and open up
26+
your web browser to configure.
27+
28+
### [Now Accepting Pre-orders](https://nodemcu-smartthings.com/collections/wired-alarm-system-smartthings-connection-kits-and-accessories) for first shipment in August 2017
29+
30+
<img src="screenshots/complete-kit.jpg" width="252" height="189" align="right"/>
31+
32+
#### [Wired Alarm System Complete DIY Kit](https://nodemcu-smartthings.com/collections/wired-alarm-system-smartthings-connection-kits-and-accessories/products/wired-alarm-system-complete-kit)
33+
* For connecting up to 5 sensors and one siren or alarm
34+
* Software is pre-loaded! Just wire it up and configure with your web browser.
35+
* Includes NodeMCU board, NodeMCU base, relay for siren, and jumper wires
36+
* Includes email support to help you get up and running
37+
* **Pre-order now** for first shipment in August 2017
38+
39+
<img src="screenshots/add-on-kit.jpg" width="252" height="189" align="right"/>
40+
41+
#### [Wired Alarm System Add-on DIY Kit](https://nodemcu-smartthings.com/collections/wired-alarm-system-smartthings-connection-kits-and-accessories/products/wired-alarm-system-add-on-kit)
42+
* For connecting up to 6 sensors (no siren)
43+
* Software is pre-loaded! Just wire it up and configure with your web browser.
44+
* Includes NodeMCU board, NodeMCU base and jumper wires
45+
* Includes email support to help you get up and running
46+
* **Pre-order now** for first shipment in August 2017
47+
48+
#### [Donate to this Project!](https://nodemcu-smartthings.com/products/donate-to-this-project)
49+
* If you've loved this open-source project, donate any amount to support it!
50+
951
### Background
1052

1153
The house I live in was built in the early 90s and came with a built-in home security system. I'm not interested in using the outdated alarm system panel, but I wanted to connect the contact sensors in my doors and the motion sensor in my house to SmartThings. I learned about the NodeMCU ESP8266, a small, cheap, programmable development board that has WiFi built in. I set out to connect my door and motion sensors to the NodeMCU and program it to update SmartThings every time a change is detected.
@@ -29,24 +71,37 @@ _Update 2:_ One user reported that he had success with [this board](https://www.
2971

3072
## Updates
3173

32-
##### v1.5 / 2017-04-07
74+
### v1.6 / 2017-06-17
75+
76+
_Feature:_ Easy OAuth. The application handles the OAuth flow automatically now. Just point your browser
77+
to `http://<your-device-ip>:8100/oauth`. See the updated README for details.
78+
79+
_Feature:_ Authorize multiple alarms with the SmartApp.
80+
81+
_Bug Fix:_ Strobe output did not work due to copy/paste bug.
82+
83+
_Bug Fix:_ Fix error in SmartApp when you only have motion sensors authorized.
84+
85+
**IMPORTANT: Read the [1.6 upgrade notes](https://github.com/heythisisnate/nodemcu-smartthings-sensors/releases/tag/1.6) if you're upgrading from an earlier version.**
86+
87+
### v1.5 / 2017-04-07
3388
_Feature:_ Connect a wired siren and/or strobe. Integrates seamlessly with the Smart Home Monitor app.
3489

35-
**IMPORTANT: Read the [upgrade notes](https://github.com/heythisisnate/nodemcu-smartthings-sensors/releases/tag/1.5) if you're upgrading from an earlier version.**
90+
**IMPORTANT: Read the [1.5 upgrade notes](https://github.com/heythisisnate/nodemcu-smartthings-sensors/releases/tag/1.5) if you're upgrading from an earlier version.**
3691

37-
##### v1.3 / 2017-03-29
92+
### v1.3 / 2017-03-29
3893

3994
_Feature:_ Blink the onboard LED on successful communication with SmartThings. To enable set `blink_led = true` in
4095
`variables.lua`
4196

4297
_Feature:_ **Reliable polling**. Configure by setting `poll_interval` in `variables.lua` to a number to indicate the number
4398
of seconds between polling for sensors that may have gotten out of sync.
4499

45-
##### v1.2 / 2017-03-06
100+
### v1.2 / 2017-03-06
46101

47102
_Feature:_ Reports the status of each sensor upon startup.
48103

49-
##### v1.1 / 2017-02-18
104+
### v1.1 / 2017-02-18
50105

51106
_Feature:_ Support for wired smoke detectors.
52107

@@ -103,46 +158,7 @@ The SmartApp receives data from your NodeMCU device, and updates the status of y
103158
1. Make note of the OAuth Client ID and Client Secret, you'll need these later.
104159
1. Click Publish -> For Me
105160

106-
### 5. Generate an OAuth token
107-
108-
The OAuth token is used to sign HTTP requests from the NodeMCU to the SmartApp you just created. [SmartThings has documentation of this process here.](http://docs.smartthings.com/en/latest/smartapp-web-services-developers-guide/authorization.html). We'll be going through the OAuth flow manually to capture the token which can then be saved on the NodeMCU.
109-
110-
1. Copy and paste the below web address into your browser and replace `YOUR-SMARTAPP-CLIENT-ID` with the OAuth Client ID from the SmartApp created eariler.
111-
112-
```
113-
https://graph.api.smartthings.com/oauth/authorize?response_type=code&client_id=YOUR-SMARTAPP-CLIENT-ID&scope=app&redirect_uri=http://localhost:3000/auth
114-
```
115-
116-
1. You'll see a page like this allowing you to authorize the devices you set up earlier:
117-
118-
![](screenshots/Authorization2017-02-05-21-59-54.png)
119-
120-
1. Once you click Authorize, you'll be redirect to http://localhost:3000/auth which will error. That's ok! It wasn't supposed to work. All you need is the code out of the URL parameter:
121-
122-
![](screenshots/localhost2017-02-05-22-24-28.png)
123-
124-
1. Now that you've got the code, it's time to make a POST request to get the access token. For this I like to use [Advanced REST Client Chrome app](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=en-US). You can use any tool that can create a POST request with form parameters. Just fill in [the fields](http://docs.smartthings.com/en/latest/smartapp-web-services-developers-guide/authorization.html#get-access-token) as shown:
125-
126-
![](screenshots/ARC2017-02-05-22-09-03.png)
127-
128-
1. Click Send, and with any luck, you'll get a successful response back that contains your access token:
129-
130-
![](screenshots/ARC2017-02-05-22-11-09.png)
131-
132-
Copy this access token into the `credentials.lua` file.
133-
134-
1. Finally, get your SmartApp endpoint by doing a GET request to `https://graph.api.smartthings.com/api/smartapps/endpoints`, signing the request with an `Authorization` header and your token:
135-
136-
![](screenshots/ARC2017-02-05-22-52-45.png)
137-
138-
1. Click send and make note of the url data returned:
139-
140-
![](screenshots/ARC2017-02-0522-53-21.png)
141-
142-
Copy the `base_url` field from here into the `apiHost` variable in the variables file
143-
Copy the `url` field into the `apiEndpoint` variable in the variables file
144-
145-
### 6. Flash the NodeMCU Lua firmware
161+
### 5. Flash the NodeMCU Lua firmware
146162

147163
#### Drivers
148164

@@ -182,12 +198,23 @@ good [documentation here](https://nodemcu.readthedocs.io/en/master/en/flash/) in
182198

183199
![](screenshots/ESPlorer2017-02-06-22-58-30.png)
184200

185-
1. Once connected, it's time to upload the code. Click Open in Esplorer and open each of the lua files on your computer and click "Save to ESP". Alternatively, you can use the Upload button to upload them all at once. [This documentation](http://esp8266.ru/download/esp8266-doc/Getting%20Started%20with%20the%20ESPlorer%20IDE%20-%20Rui%20Santos.pdf) was also very helpful in learning how to interact with the device using Esplorer.
186-
1. After all the code is uploaded, click the Reset button to restart the device. It should boot up, connect to your WiFi and output a message for each configured sensor, like this:
201+
1. Once connected, it's time to upload the code. Click the Upload button in ESplorer and navigate to the `lua` directory. Highlight all the `lua` and `html` files and click Open to upload them to the device:
202+
203+
![](screenshots/ESP2017-06-17-14-37-45.png)
204+
205+
1. After all the code is uploaded, toggle the RTS button on then off to restart the device. It should boot up, connect to your WiFi and output a link to begin the OAuth flow.
206+
1. Copy and paste the OAuth link URL into your web browser and begin the OAuth flow. You'll need the OAuth Client ID and Secret from the SmartApp.
207+
1. After you enter the Client ID and Secret, you'll see a page like this allowing you to authorize the devices you set up earlier:
208+
209+
![](screenshots/Authorization2017-02-05-21-59-54.png)
210+
211+
1. Authorize all the NodeMCU connected devices. When prompted, reboot the device by toggling the RTS button in ESPlorer on and off.
212+
213+
1. Now your device should be working. You will see a debug message on boot for each configured sensor, like this:
187214

188215
![](screenshots/ESPlorer2017-02-06-23-02-30.png)
189216

190-
1. Now let's test it out! The first sensor in this example is configured on pin 6 (labled D6). Take a wire and connect one end to pin D6 and the other end to the ground (GND). This completes the circuit, setting the pin low or 0, indicating that the contact sensor is closed. Hopefully it worked and you should see a success message in the Esplorer terminal, and when you open your SmartThings app you should see that the door is closed. Now remove the wire and watch it set to open.
217+
1. Let's test it out! The first sensor in this example is configured on pin 6 (labled D6). Take a wire and connect one end to pin D6 and the other end to the ground (GND). This completes the circuit, setting the pin low or 0, indicating that the contact sensor is closed. Hopefully it worked and you should see a success message in the Esplorer terminal, and when you open your SmartThings app you should see that the door is closed. Now remove the wire and watch it set to open.
191218
1. _Note about the pins:_ I found that some of the pins don't work very well when normally _low_. It took a lot of trial and error to figure out that pins D1, D2, D6 and D7 worked reliabliy for me. I had problems with D3, D9 and D10. Your mileage may vary.
192219

193220
### 7. Connect your switches at the alarm panel
@@ -211,7 +238,6 @@ signal from the ESP8266 board. These instructions are written for the relay link
211238
1. Make sure the black (-) wire of the siren/strobe is connected to the ground (-) on the alarm panel.
212239
1. Connect the COM on the relay to the 12V aux power out (+) on the alarm panel with a jumper wire.
213240

214-
215241
## Problems or Questions
216242

217243
Please [open an issue](https://github.com/heythisisnate/nodemcu-smartthings-sensors/issues) if you run into problems or have feature requests. You can also [join the discussion on SmartThings community](https://community.smartthings.com/t/connect-wired-alarm-system-sensors-to-smartthings-with-a-nodemcu-esp8266/76010)

SmartThings/cloud-sensor.groovy

+6-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ preferences {
3232
input "contactSensors", "capability.contactSensor", title: "Contact sensors", multiple:true, required:false
3333
input "motionSensors", "capability.motionSensor", title: "Motion sensors", multiple:true, required:false
3434
input "smokeDetectors", "capability.smokeDetector", title: "Smoke detectors", multiple:true, required:false
35-
input "alarm", "capability.alarm", title: "Alarm", required:false
35+
input "alarms", "capability.alarm", title: "Alarms", multiple: true, required:false
3636
}
3737
}
3838

@@ -52,7 +52,7 @@ mappings {
5252

5353
def handle_event() {
5454
def event = request.JSON
55-
def allSensors = (contactSensors?:[] + motionSensors?:[] + smokeDetectors?:[]) - null
55+
def allSensors = (contactSensors?:[]) + (motionSensors?:[]) + (smokeDetectors?:[]) - null
5656
def device = allSensors.find {
5757
event.sensor_id == it.id
5858
}
@@ -74,7 +74,10 @@ def handle_event() {
7474

7575
def sync() {
7676
def sync_data = request.JSON
77-
if (sync_data.device_id == alarm.id) {
77+
def alarm = alarms.find {
78+
sync_data.device_id == it.id
79+
}
80+
if (alarm) {
7881
alarm.sync(sync_data.ip, sync_data.port as String, sync_data.mac)
7982
}
8083
return [ "success": true ]

lua/alarm.lua

-58
Original file line numberDiff line numberDiff line change
@@ -62,61 +62,3 @@ function alarmState()
6262
if strobe then return "strobe" end
6363
return "off"
6464
end
65-
66-
-- Process an incoming HTTP request
67-
function processRequest(connection, request)
68-
69-
-- iterate through each line in the request payload and construct a table
70-
local requestObject = {}
71-
-- for line in string.gmatch(request, '[^\n]+') do print(line) end
72-
requestObject.method, requestObject.path = string.match(request, '^(%u+) (/[%w%p]*)')
73-
requestObject.host, requestObject.port = string.match(request, '[Hh][Oo][Ss][Tt]: ([%w%p]*):(%d+)')
74-
print(requestObject.host .. ":" ..requestObject.port, requestObject.method, requestObject.path)
75-
76-
local response_body
77-
local response_code
78-
local device_id
79-
local action
80-
81-
device_id, action = string.match(requestObject.path, '^/([%w-]+)/(%w+)')
82-
83-
if device_id == alarm.deviceId and requestObject.method == 'POST' then
84-
alarmAction(action)
85-
response_code = "200 OK"
86-
response_body = cjson.encode({ device_id = device_id, alarm = alarmState() })
87-
if blink_led then blinkLed() end
88-
else
89-
response_code = "404 NOT FOUND"
90-
response_body = [[{"status":"error","message":"No device with ID ]] .. device_id .. [[ is configured"}]]
91-
end
92-
93-
local response = {}
94-
response[1] = "HTTP/1.1 " .. response_code .. "\r\n"
95-
response[2] = "Server: NodeMCU on ESP8266\r\n"
96-
response[3] = "Content-Type: application/json\r\n"
97-
response[4] = "Content-Length: " .. string.len(response_body) .. "\r\n\r\n"
98-
response[5] = response_body
99-
100-
-- sends and removes the first element from the 'response' table
101-
local function send(localSocket)
102-
if #response > 0 then
103-
localSocket:send(table.remove(response, 1))
104-
else
105-
localSocket:close()
106-
response = nil
107-
end
108-
end
109-
110-
-- triggers the send() function again once the first chunk of data was sent
111-
connection:on("sent", send)
112-
send(connection)
113-
end
114-
115-
if alarm and alarm.httpPort and alarm.deviceId and (alarm.sirenPin or alarm.strobePin) then
116-
alarmListener = net.createServer(net.TCP)
117-
alarmListener:listen(alarm.httpPort, function(connection)
118-
connection:on("receive", processRequest)
119-
end)
120-
121-
print("Listening for Alarm commands on HTTP port " .. alarm.httpPort)
122-
end

lua/application.lua

+26-37
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,16 @@
1-
--
2-
-- SETUP
3-
--
4-
5-
require "variables"
61

72
-- set up application variables
83
globalHeaders = "Host: " .. apiHost .. "\r\n"
94
globalHeaders = globalHeaders .. "Authorization: Bearer " .. auth_token .. "\r\n"
105
globalHeaders = globalHeaders .. "Content-Type: application/json\r\n"
6+
117
requestQueue = {}
128

139
if blink_led then
1410
led_pin = 4
1511
gpio.mode(led_pin, gpio.OUTPUT)
1612
end
1713

18-
--
19-
-- GLOBAL FUNCTIONS
20-
--
21-
22-
-- Blink the onboard LED
23-
function blinkLed()
24-
gpio.write(led_pin, gpio.LOW)
25-
tmr.create():alarm(100, tmr.ALARM_SINGLE, function()
26-
gpio.write(led_pin, gpio.HIGH)
27-
end)
28-
end
29-
3014
-- Inserts a request to the end of the queue
3115
function queueRequest(endpoint, requestData)
3216
table.insert(requestQueue, {endpoint, requestData})
@@ -37,25 +21,30 @@ function doNextRequest()
3721
local requestData = requestQueue[1]
3822

3923
if requestData then
40-
local endpoint = requestData[1]
41-
local payload = cjson.encode(requestData[2])
42-
-- set http headers
43-
local headers = globalHeaders .. "Content-Length: " .. string.len(payload) .. "\r\n"
44-
45-
-- do the POST to SmartThings
46-
http.post(
47-
apiHost .. apiEndpoint .. endpoint,
48-
headers,
49-
payload,
50-
function(code, data)
51-
if code == 201 then
52-
print("Success: " .. payload)
53-
table.remove(requestQueue, 1) -- remove from the queue when successful
54-
if blink_led then blinkLed() end
55-
elseif code > 201 then
56-
print("Error " .. code .. " posting " .. payload .. ", retrying")
57-
end
58-
end)
24+
local endpoint = requestData[1]
25+
local payload = cjson.encode(requestData[2])
26+
local url = apiHost .. apiEndpoint .. endpoint
27+
28+
-- set http headers
29+
local headers = globalHeaders .. "Content-Length: " .. string.len(payload) .. "\r\n"
30+
31+
-- do the POST to SmartThings
32+
http.post(
33+
url,
34+
headers,
35+
payload,
36+
function(code)
37+
if code == 201 then
38+
print("Success: " .. payload)
39+
table.remove(requestQueue, 1) -- remove from the queue when successful
40+
if blink_led then blinkLed() end
41+
elseif code > 201 then
42+
print("Error " .. code .. " posting " .. payload .. ", retrying")
43+
end
44+
payload = nil
45+
end)
46+
47+
headers, requestData, url, endpoint = nil
5948
end
6049
end
6150

@@ -103,7 +92,7 @@ end
10392
-- In case of a HTTP failure, re-insert the request data back into the first position so it will
10493
-- retry on the next cycle.
10594
-- This throttles the HTTP calls to SmartThings in an attempt to prevent timeouts
106-
tmr.create():alarm(1000, tmr.ALARM_AUTO, doNextRequest)
95+
tmr.create():alarm(1500, tmr.ALARM_AUTO, doNextRequest)
10796

10897
-- Poll sensors periodically if configured
10998
if poll_interval and poll_interval > 0 then

lua/boot.lua

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--
2+
-- SETUP
3+
--
4+
5+
require "variables"
6+
require "common"
7+
require "server"
8+
require "oauth"
9+
10+
if auth_token then
11+
getApiEndpointAndStart()
12+
end

0 commit comments

Comments
 (0)