@homebridge-plugins/homebridge-firstalert 0.0.1-beta.1 → 0.0.1-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/API/README.md +310 -0
  2. package/API/RESIDEO_API.md +377 -0
  3. package/API/resideo_firstalert/__init__.py +75 -0
  4. package/API/resideo_firstalert/api.py +321 -0
  5. package/API/resideo_firstalert/application_credentials.py +122 -0
  6. package/API/resideo_firstalert/auth.py +336 -0
  7. package/API/resideo_firstalert/binary_sensor.py +245 -0
  8. package/API/resideo_firstalert/config_flow.py +429 -0
  9. package/API/resideo_firstalert/const.py +48 -0
  10. package/API/resideo_firstalert/coordinator.py +53 -0
  11. package/API/resideo_firstalert/icon.png +0 -0
  12. package/API/resideo_firstalert/manifest.json +13 -0
  13. package/API/resideo_firstalert/sensor.py +284 -0
  14. package/API/resideo_firstalert/strings.json +265 -0
  15. package/API/resideo_firstalert/translations/en.json +265 -0
  16. package/RESIDEO_API_REFERENCE.md +227 -0
  17. package/branding/icon.png +0 -0
  18. package/docs/variables/default.html +1 -1
  19. package/package.json +5 -5
  20. package/src/homebridge-ui/public/index.html +119 -443
  21. package/src/homebridge-ui/server.ts +323 -5
  22. package/dist/homebridge-ui/public/index.html +0 -548
  23. package/dist/src/api/resideoClient.d.ts +0 -32
  24. package/dist/src/api/resideoClient.d.ts.map +0 -1
  25. package/dist/src/api/resideoClient.js +0 -76
  26. package/dist/src/api/resideoClient.js.map +0 -1
  27. package/dist/src/devices/device.d.ts +0 -40
  28. package/dist/src/devices/device.d.ts.map +0 -1
  29. package/dist/src/devices/device.js +0 -207
  30. package/dist/src/devices/device.js.map +0 -1
  31. package/dist/src/devices/leaksensors.d.ts +0 -34
  32. package/dist/src/devices/leaksensors.d.ts.map +0 -1
  33. package/dist/src/devices/leaksensors.js +0 -163
  34. package/dist/src/devices/leaksensors.js.map +0 -1
  35. package/dist/src/devices/smoke.d.ts +0 -35
  36. package/dist/src/devices/smoke.d.ts.map +0 -1
  37. package/dist/src/devices/smoke.js +0 -150
  38. package/dist/src/devices/smoke.js.map +0 -1
  39. package/dist/src/devices/thermostats.d.ts +0 -42
  40. package/dist/src/devices/thermostats.d.ts.map +0 -1
  41. package/dist/src/devices/thermostats.js +0 -192
  42. package/dist/src/devices/thermostats.js.map +0 -1
  43. package/dist/src/devices/valve.d.ts +0 -21
  44. package/dist/src/devices/valve.d.ts.map +0 -1
  45. package/dist/src/devices/valve.js +0 -131
  46. package/dist/src/devices/valve.js.map +0 -1
  47. package/dist/src/homebridge-ui/server.d.ts +0 -5
  48. package/dist/src/homebridge-ui/server.d.ts.map +0 -1
  49. package/dist/src/homebridge-ui/server.js +0 -95
  50. package/dist/src/homebridge-ui/server.js.map +0 -1
  51. package/dist/src/index.d.ts +0 -4
  52. package/dist/src/index.d.ts.map +0 -1
  53. package/dist/src/index.js +0 -7
  54. package/dist/src/index.js.map +0 -1
  55. package/dist/src/platform.d.ts +0 -18
  56. package/dist/src/platform.d.ts.map +0 -1
  57. package/dist/src/platform.js +0 -108
  58. package/dist/src/platform.js.map +0 -1
  59. package/dist/src/settings.d.ts +0 -341
  60. package/dist/src/settings.d.ts.map +0 -1
  61. package/dist/src/settings.js +0 -25
  62. package/dist/src/settings.js.map +0 -1
  63. package/dist/src/utils.d.ts +0 -21
  64. package/dist/src/utils.d.ts.map +0 -1
  65. package/dist/src/utils.js +0 -58
  66. package/dist/src/utils.js.map +0 -1
  67. package/dist/test/index.test.d.ts +0 -2
  68. package/dist/test/index.test.d.ts.map +0 -1
  69. package/dist/test/index.test.js +0 -14
  70. package/dist/test/index.test.js.map +0 -1
  71. package/dist/test/platform.test.d.ts +0 -2
  72. package/dist/test/platform.test.d.ts.map +0 -1
  73. package/dist/test/platform.test.js +0 -56
  74. package/dist/test/platform.test.js.map +0 -1
  75. package/dist/test/settings.test.d.ts +0 -2
  76. package/dist/test/settings.test.d.ts.map +0 -1
  77. package/dist/test/settings.test.js +0 -48
  78. package/dist/test/settings.test.js.map +0 -1
  79. package/dist/test/utils.test.d.ts +0 -2
  80. package/dist/test/utils.test.d.ts.map +0 -1
  81. package/dist/test/utils.test.js +0 -17
  82. package/dist/test/utils.test.js.map +0 -1
package/API/README.md ADDED
@@ -0,0 +1,310 @@
1
+ # First Alert by Resideo - Home Assistant Integration
2
+
3
+ A custom Home Assistant integration for First Alert Safe & Sound smoke/CO detectors connected via the Resideo platform.
4
+
5
+ ## Supported Devices
6
+
7
+ - First Alert Safe & Sound Smart Smoke/CO Alarm (SMCO600NVACA)
8
+ - Other Resideo-connected First Alert devices may also work
9
+
10
+ ## Features
11
+
12
+ - **Easy Setup** - Login with your Resideo email and password directly
13
+ - **Smoke Alarm Detection** - Binary sensor that triggers when smoke is detected
14
+ - **CO Alarm Detection** - Binary sensor that triggers when carbon monoxide is detected
15
+ - **Battery Monitoring** - Track battery status and get low battery alerts
16
+ - **Power Source** - See if device is on AC or battery power
17
+ - **Connectivity Status** - Know if your detector is online
18
+ - **Malfunction Detection** - Get alerts if the device has issues
19
+ - **Test Mode & Silence Status** - Monitor when detectors are in test mode or silenced
20
+ - **Early Warning** - Track early warning feature status
21
+ - **End of Life Alerts** - Know when your detector needs replacement
22
+ - **Comprehensive Fault Detection** - Monitor various fault conditions
23
+ - **Configurable Polling** - Adjust update interval from 5 seconds to 1 hour
24
+
25
+ ## Installation
26
+
27
+ ### HACS (Recommended)
28
+
29
+ 1. Open HACS in Home Assistant
30
+ 2. Click the three dots menu → Custom repositories
31
+ 3. Add `https://github.com/aidenmitchell/ha-resideo-firstalert` with category "Integration"
32
+ 4. Search for "First Alert by Resideo" and install
33
+ 5. Restart Home Assistant
34
+
35
+ ### Manual Installation
36
+
37
+ 1. Copy the `custom_components/resideo_firstalert` folder to your Home Assistant `config/custom_components/` directory
38
+ 2. Restart Home Assistant
39
+
40
+ ## Configuration
41
+
42
+ ### Authentication
43
+
44
+ When adding the integration, you have two options:
45
+
46
+ #### Option 1: Login with Email & Password (Recommended)
47
+
48
+ 1. Go to **Settings** → **Devices & Services** → **Add Integration**
49
+ 2. Search for "First Alert by Resideo"
50
+ 3. Select **"Login with email and password"**
51
+ 4. Enter your Resideo account credentials (same as the First Alert app)
52
+ 5. Your devices will be automatically discovered
53
+
54
+ #### Option 2: Manual Token Entry
55
+
56
+ If you prefer, you can manually obtain and enter a refresh token:
57
+
58
+ 1. **Install a network proxy** like [Proxyman](https://proxyman.io/) (macOS/iOS) or [mitmproxy](https://mitmproxy.org/)
59
+
60
+ 2. **Configure SSL interception** for `login.resideo.com`
61
+
62
+ 3. **Log into the First Alert app** on your phone while capturing traffic
63
+
64
+ 4. **Look for the request** to `POST https://login.resideo.com/oauth/token`
65
+
66
+ 5. **Find the `refresh_token`** in the response JSON:
67
+ ```json
68
+ {
69
+ "access_token": "...",
70
+ "refresh_token": "THIS_IS_YOUR_TOKEN",
71
+ "expires_in": 3600,
72
+ "token_type": "Bearer"
73
+ }
74
+ ```
75
+
76
+ 6. In Home Assistant, select **"Enter refresh token manually"** and paste your token
77
+
78
+ ### Options
79
+
80
+ After setup, you can configure the integration via **Settings** → **Devices & Services** → **First Alert by Resideo** → **Configure**:
81
+
82
+ - **Settings** - Adjust the polling interval (5-3600 seconds, default: 60)
83
+ - **Update refresh token** - Enter a new token if needed without recreating the integration
84
+
85
+ ## Entities Created
86
+
87
+ For each smoke detector, the following entities are created:
88
+
89
+ ### Binary Sensors
90
+
91
+ | Entity | Description | Device Class | Default |
92
+ |--------|-------------|--------------|---------|
93
+ | Smoke Alarm | On when smoke is detected | `smoke` | Enabled |
94
+ | CO Alarm | On when CO is detected | `co` | Enabled |
95
+ | Malfunction | On when device has a problem | `problem` | Enabled |
96
+ | Connectivity | On when device is online | `connectivity` | Enabled |
97
+ | Battery Low | On when battery is low | `battery` | Enabled |
98
+ | Test Mode | On when device is in test mode | `running` | Enabled |
99
+ | Silenced | On when alarm is silenced | `running` | Enabled |
100
+ | End of Life | On when device needs replacement | `problem` | Enabled |
101
+ | Early Warning | On when early warning is enabled | - | Enabled |
102
+ | Supervision Healthy | On when supervision is healthy | - | Disabled |
103
+ | General Fault | On when general fault detected | `problem` | Disabled |
104
+ | E2 Fault | On when E2 fault detected | `problem` | Disabled |
105
+ | Photo Sensor Fault | On when photo sensor fault detected | `problem` | Disabled |
106
+ | Drift Malfunction | On when drift malfunction detected | `problem` | Disabled |
107
+ | CO Sensor Fault | On when CO sensor fault detected | `problem` | Disabled |
108
+ | Temperature Sensor Fault | On when temp sensor fault detected | `problem` | Disabled |
109
+ | Voice Module Fault | On when voice module fault detected | `problem` | Disabled |
110
+ | Radio Fault | On when radio fault detected | `problem` | Disabled |
111
+
112
+ ### Sensors
113
+
114
+ | Entity | Description | Default |
115
+ |--------|-------------|---------|
116
+ | Battery Status | `good` or `low` | Enabled |
117
+ | Power Source | `ac` or `battery` | Enabled |
118
+ | Smoke Status | `idle` or `alarm` | Enabled |
119
+ | CO Status | `idle` or `alarm` | Enabled |
120
+ | Test Status | `idle` or `testing` | Enabled |
121
+ | Silence Status | `not_silenced` or `silenced` | Enabled |
122
+ | End of Life Status | `no` or `yes` | Enabled |
123
+ | Language | Device language setting | Enabled |
124
+ | Room | Room number setting | Disabled |
125
+ | WiFi Signal Strength | Signal strength in dBm | Disabled |
126
+ | WiFi Network | Connected SSID | Disabled |
127
+ | Last Seen | Timestamp of last communication | Disabled |
128
+ | Firmware Version | Device firmware | Disabled |
129
+ | Firmware (Exec Core) | Exec core firmware version | Disabled |
130
+ | Firmware (Sensor Core) | Sensor core firmware version | Disabled |
131
+ | Hardware Version (E2C) | E2C hardware version | Disabled |
132
+ | Hardware Version (Exec Core) | Exec core hardware version | Disabled |
133
+ | Hardware Version (Sensor Core) | Sensor core hardware version | Disabled |
134
+ | Voice File Version | Voice file version | Disabled |
135
+ | Running Hours | Total running hours | Disabled |
136
+ | Registration Date | When device was registered | Disabled |
137
+ | Last Firmware Update | Last firmware update timestamp | Disabled |
138
+
139
+ ## Example Automations
140
+
141
+ ### Alert on Smoke Detection
142
+ ```yaml
143
+ automation:
144
+ - alias: "Smoke Alarm Alert"
145
+ trigger:
146
+ - platform: state
147
+ entity_id: binary_sensor.living_room_detector_smoke_alarm
148
+ to: "on"
149
+ action:
150
+ - service: notify.mobile_app
151
+ data:
152
+ title: "SMOKE DETECTED!"
153
+ message: "Smoke alarm triggered in Living Room"
154
+ data:
155
+ priority: high
156
+ ttl: 0
157
+ ```
158
+
159
+ ### Alert on CO Detection
160
+ ```yaml
161
+ automation:
162
+ - alias: "CO Alarm Alert"
163
+ trigger:
164
+ - platform: state
165
+ entity_id: binary_sensor.living_room_detector_co_alarm
166
+ to: "on"
167
+ action:
168
+ - service: notify.mobile_app
169
+ data:
170
+ title: "CARBON MONOXIDE DETECTED!"
171
+ message: "CO alarm triggered - evacuate immediately!"
172
+ data:
173
+ priority: high
174
+ ttl: 0
175
+ ```
176
+
177
+ ### Low Battery Alert
178
+ ```yaml
179
+ automation:
180
+ - alias: "Smoke Detector Low Battery"
181
+ trigger:
182
+ - platform: state
183
+ entity_id: binary_sensor.living_room_detector_battery_low
184
+ to: "on"
185
+ action:
186
+ - service: notify.mobile_app
187
+ data:
188
+ title: "Low Battery"
189
+ message: "Living Room smoke detector battery is low"
190
+ ```
191
+
192
+ ### End of Life Alert
193
+ ```yaml
194
+ automation:
195
+ - alias: "Smoke Detector End of Life"
196
+ trigger:
197
+ - platform: state
198
+ entity_id: binary_sensor.living_room_detector_end_of_life
199
+ to: "on"
200
+ action:
201
+ - service: notify.mobile_app
202
+ data:
203
+ title: "Detector Replacement Needed"
204
+ message: "Living Room smoke detector has reached end of life and should be replaced"
205
+ ```
206
+
207
+ ## Troubleshooting
208
+
209
+ ### "Invalid email or password" error
210
+ Double-check your credentials. These are the same as your First Alert / Resideo app login.
211
+
212
+ ### "Authentication failed" error
213
+ Your refresh token may have expired. Use the **Configure** option to update your token, or re-authenticate with email/password.
214
+
215
+ ### "Unable to connect" error
216
+ Check your internet connection and verify the Resideo API is accessible.
217
+
218
+ ### Devices not showing
219
+ Make sure your devices are properly set up in the First Alert app and are online.
220
+
221
+ ### Token Expiration
222
+ - **Access tokens** expire hourly and are automatically refreshed
223
+ - **Refresh tokens** expire after ~30 days. When this happens, Home Assistant will prompt you to re-authenticate
224
+
225
+ ## Removing the Integration
226
+
227
+ To remove the First Alert by Resideo integration:
228
+
229
+ 1. Go to **Settings** → **Devices & Services**
230
+ 2. Find "First Alert by Resideo" and click on it
231
+ 3. Click the three-dot menu → **Delete**
232
+ 4. Confirm the deletion
233
+
234
+ All entities and device data will be removed. No additional cleanup is required.
235
+
236
+ ## Technical Details
237
+
238
+ - **Polling Interval**: 60 seconds (configurable from 5-3600 seconds)
239
+ - **API Base URL**: `https://api.resideo.com`
240
+ - **Authentication**: OAuth 2.0 with PKCE via Auth0
241
+
242
+ ## Privacy Note
243
+
244
+ This integration communicates with Resideo's cloud servers. Your device data passes through their infrastructure. The integration stores only the refresh token locally - your email and password are not stored.
245
+
246
+ ## License
247
+
248
+ MIT License - See LICENSE file for details.
249
+
250
+ ## Local Development
251
+
252
+ ### Prerequisites
253
+
254
+ - Docker and Docker Compose
255
+ - A Resideo account with First Alert devices
256
+
257
+ ### Quick Start
258
+
259
+ 1. Clone the repository:
260
+ ```bash
261
+ git clone https://github.com/aidenmitchell/ha-resideo-firstalert.git
262
+ cd ha-resideo-firstalert
263
+ ```
264
+
265
+ 2. Create the config file:
266
+ ```bash
267
+ cp config/configuration.yaml.example config/configuration.yaml
268
+ ```
269
+
270
+ 3. Start Home Assistant:
271
+ ```bash
272
+ docker compose up -d
273
+ ```
274
+
275
+ 4. Open http://localhost:8123 in your browser
276
+
277
+ 5. Complete the Home Assistant onboarding, then add the integration:
278
+ - Settings → Devices & Services → Add Integration
279
+ - Search "First Alert by Resideo"
280
+ - Login with your email and password
281
+
282
+ ### Development Workflow
283
+
284
+ The `custom_components` folder is mounted directly into the container, so changes to the integration code take effect after restarting Home Assistant:
285
+
286
+ ```bash
287
+ # Restart to pick up code changes
288
+ docker compose restart
289
+
290
+ # View logs
291
+ docker compose logs -f homeassistant
292
+
293
+ # Stop
294
+ docker compose down
295
+ ```
296
+
297
+ ### Debug Logging
298
+
299
+ The example configuration enables debug logging for the integration. View logs with:
300
+ ```bash
301
+ docker compose logs -f homeassistant 2>&1 | grep resideo_firstalert
302
+ ```
303
+
304
+ ## Contributing
305
+
306
+ Contributions welcome! Please open an issue or PR on GitHub.
307
+
308
+ ## Disclaimer
309
+
310
+ This is an unofficial integration and is not affiliated with, endorsed by, or supported by First Alert or Resideo. Use at your own risk.
@@ -0,0 +1,377 @@
1
+ # First Alert by Resideo API Documentation
2
+
3
+ This document describes the API used by the First Alert by Resideo mobile app to communicate with smoke/CO detectors.
4
+
5
+ ## Authentication
6
+
7
+ The API uses **OAuth 2.0 with PKCE** via **Auth0**.
8
+
9
+ ### OAuth Configuration
10
+
11
+ | Parameter | Value |
12
+ |-----------|-------|
13
+ | Auth Domain | `login.resideo.com` |
14
+ | Client ID | `SRmiA7CaYi1JgivDZdzzoZu4X5VBogGt` |
15
+ | Audience | `https://resideo-prod.auth0.com/api/v2/` |
16
+ | Scopes | `openid profile email offline_access` |
17
+
18
+ ### Token Refresh
19
+
20
+ Access tokens expire after 1 hour. Use the refresh token to get new access tokens:
21
+
22
+ ```http
23
+ POST https://login.resideo.com/oauth/token
24
+ Content-Type: application/json
25
+
26
+ {
27
+ "grant_type": "refresh_token",
28
+ "refresh_token": "<refresh_token>",
29
+ "client_id": "SRmiA7CaYi1JgivDZdzzoZu4X5VBogGt"
30
+ }
31
+ ```
32
+
33
+ **Response:**
34
+ ```json
35
+ {
36
+ "access_token": "eyJ...",
37
+ "id_token": "eyJ...",
38
+ "scope": "openid profile email offline_access",
39
+ "expires_in": 3600,
40
+ "token_type": "Bearer"
41
+ }
42
+ ```
43
+
44
+ ---
45
+
46
+ ## API Endpoints
47
+
48
+ Base URL: `https://api.resideo.com`
49
+
50
+ All requests require:
51
+ ```http
52
+ Authorization: Bearer <access_token>
53
+ Content-Type: application/json
54
+ Accept: application/json
55
+ ```
56
+
57
+ ### Get Account Information
58
+
59
+ ```http
60
+ GET /ris-public-api/api/v1/accounts
61
+ ```
62
+
63
+ **Response:**
64
+ ```json
65
+ {
66
+ "data": {
67
+ "id": "VXNlcjow...",
68
+ "firstName": "John",
69
+ "lastName": "Doe",
70
+ "contactEmail": "user@example.com",
71
+ "countryCode": "US",
72
+ "locale": "en_US",
73
+ "consumerUsers": [
74
+ {
75
+ "id": "Q29uc3VtZXJVc2VyOj...",
76
+ "role": "ADMIN",
77
+ "consumerAccount": {
78
+ "id": "Q29uc3VtZXJBY2NvdW50Oj...",
79
+ "locations": [
80
+ {
81
+ "id": "Q29uc3VtZXJEZXZpY2VMb2NhdGlvbjo...",
82
+ "name": "Home",
83
+ "address": {
84
+ "addressLine1": "123 Main St",
85
+ "city": "Anytown",
86
+ "stateProvinceRegionCode": "CA",
87
+ "zipPostalCode": "90210",
88
+ "countryCode": "US"
89
+ },
90
+ "geoCoordinate": {
91
+ "latitude": 34.0901,
92
+ "longitude": -118.4065
93
+ },
94
+ "consumerDevices": [
95
+ {
96
+ "id": "Q29uc3VtZXJEZXZpY2U6...",
97
+ "name": "Living Room Detector",
98
+ "device": {
99
+ "id": "THlyaWNUaGVybW9zdGF0RGV2aWNlOj...",
100
+ "deviceId": "XXXXXXXXXXXX",
101
+ "globalDeviceType": "Citadel_SC5"
102
+ }
103
+ }
104
+ ]
105
+ }
106
+ ]
107
+ }
108
+ }
109
+ ]
110
+ },
111
+ "errors": []
112
+ }
113
+ ```
114
+
115
+ ### Get Device State
116
+
117
+ ```http
118
+ GET /ris-public-api/api/v2/devices/smokeDetectors/{deviceId}/state
119
+ ```
120
+
121
+ **Example:** `GET /ris-public-api/api/v2/devices/smokeDetectors/XXXXXXXXXXXX/state`
122
+
123
+ **Response:**
124
+ ```json
125
+ {
126
+ "name": "XXXXXXXXXXXX",
127
+ "deviceType": "SmokeDetector",
128
+ "sku": "SMCO600NVACA",
129
+ "registrationStatus": "Registered",
130
+ "isOnline": true,
131
+ "isSupervisionHealthy": true,
132
+ "isOnlineComputed": true,
133
+ "dataSyncState": "Completed",
134
+ "registrationDate": "2025-12-18T03:22:17.457+00:00",
135
+ "lastMessageReceivedTime": "2025-12-20T17:02:30.861+00:00",
136
+ "deviceState": {
137
+ "desired": { ... },
138
+ "reported": {
139
+ "alarmState": {
140
+ "co": {
141
+ "eventSource": "self",
142
+ "tStampEpoch": 1766247701,
143
+ "deviceState": "idle"
144
+ },
145
+ "smoke": {
146
+ "eventSource": "self",
147
+ "tStampEpoch": 1766247701,
148
+ "deviceState": "idle"
149
+ },
150
+ "test": {
151
+ "eventSource": "self",
152
+ "tStampEpoch": 1766034736,
153
+ "deviceState": "idle"
154
+ },
155
+ "malfunction": {
156
+ "eventSource": "self",
157
+ "tStampEpoch": 1766247701,
158
+ "deviceState": "none"
159
+ },
160
+ "battery": {
161
+ "eventSource": "self",
162
+ "tStampEpoch": 1766247701,
163
+ "deviceState": "good"
164
+ },
165
+ "eol": {
166
+ "eventSource": "self",
167
+ "tStampEpoch": 1766247704,
168
+ "deviceState": "no"
169
+ },
170
+ "power": {
171
+ "eventSource": "self",
172
+ "tStampEpoch": 1766029736,
173
+ "deviceState": "ac"
174
+ },
175
+ "silence": {
176
+ "eventSource": "self",
177
+ "tStampEpoch": 1766247701,
178
+ "deviceState": "not_silenced"
179
+ }
180
+ },
181
+ "deviceConfig": {
182
+ "language": "en_US",
183
+ "room": 14,
184
+ "debugLevel": "error",
185
+ "earlyWarning": true
186
+ },
187
+ "deviceInfo": {
188
+ "hwVerE2C": "1.0.0",
189
+ "hwVerExecCore": "1.0.0",
190
+ "hwVerSensorCore": "1.0.0",
191
+ "fwVerE2C": "00.07.72.00",
192
+ "fwVerExecCore": "01.06.38",
193
+ "fwVerSensorCore": "11.00",
194
+ "voiceFileVer": "1.0.0",
195
+ "runningHrs": 0
196
+ },
197
+ "deviceStatus": {
198
+ "rssi": -30,
199
+ "ssid": "WiFiNetwork"
200
+ },
201
+ "deviceStatusFlags": {
202
+ "fault": false,
203
+ "e2Fault": false,
204
+ "photoFault": false,
205
+ "driftMalfunction": false,
206
+ "coFault": false,
207
+ "temperatureFault": false,
208
+ "voiceFault": false,
209
+ "radioFault": false
210
+ }
211
+ }
212
+ },
213
+ "lastFirmwareUpdateTime": "2025-12-18T03:22:45.412+00:00"
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Alarm State Values
220
+
221
+ ### `alarmState.smoke.deviceState`
222
+ | Value | Description |
223
+ |-------|-------------|
224
+ | `idle` | Normal - no smoke detected |
225
+ | `alarm` | Smoke alarm active |
226
+
227
+ ### `alarmState.co.deviceState`
228
+ | Value | Description |
229
+ |-------|-------------|
230
+ | `idle` | Normal - no CO detected |
231
+ | `alarm` | CO alarm active |
232
+
233
+ ### `alarmState.battery.deviceState`
234
+ | Value | Description |
235
+ |-------|-------------|
236
+ | `good` | Battery healthy |
237
+ | `low` | Battery low (assumed) |
238
+
239
+ ### `alarmState.power.deviceState`
240
+ | Value | Description |
241
+ |-------|-------------|
242
+ | `ac` | Running on AC power |
243
+ | `battery` | Running on battery (assumed) |
244
+
245
+ ### `alarmState.malfunction.deviceState`
246
+ | Value | Description |
247
+ |-------|-------------|
248
+ | `none` | No malfunction |
249
+ | (other) | Device malfunction |
250
+
251
+ ### `alarmState.silence.deviceState`
252
+ | Value | Description |
253
+ |-------|-------------|
254
+ | `not_silenced` | Alarm not silenced |
255
+ | `silenced` | Alarm temporarily silenced (assumed) |
256
+
257
+ ### `alarmState.eol.deviceState`
258
+ | Value | Description |
259
+ |-------|-------------|
260
+ | `no` | Not at end of life |
261
+ | `yes` | End of life - replace device (assumed) |
262
+
263
+ ### `alarmState.test.deviceState`
264
+ | Value | Description |
265
+ |-------|-------------|
266
+ | `idle` | Not in test mode |
267
+ | `testing` | Test in progress (assumed) |
268
+
269
+ ---
270
+
271
+ ## Other Endpoints (Discovered)
272
+
273
+ ```http
274
+ GET /ris-public-api/api/v1/geofence
275
+ POST /ds-activity-feed-api/api/v1/app/events
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Device Types
281
+
282
+ | `globalDeviceType` | Description |
283
+ |-------------------|-------------|
284
+ | `Citadel_SC5` | First Alert Safe & Sound Smart Smoke/CO Alarm (SMCO600NVACA) |
285
+
286
+ ---
287
+
288
+ ## Home Assistant Integration Notes
289
+
290
+ ### Sensors to Expose
291
+
292
+ 1. **Binary Sensors:**
293
+ - Smoke Alarm (`alarmState.smoke.deviceState` != "idle")
294
+ - CO Alarm (`alarmState.co.deviceState` != "idle")
295
+ - Malfunction (`alarmState.malfunction.deviceState` != "none")
296
+ - Online Status (`isOnline`)
297
+
298
+ 2. **Sensors:**
299
+ - Battery Status (`alarmState.battery.deviceState`)
300
+ - Power Source (`alarmState.power.deviceState`)
301
+ - WiFi Signal Strength (`deviceStatus.rssi`)
302
+ - Last Message Time (`lastMessageReceivedTime`)
303
+
304
+ 3. **Diagnostic Sensors:**
305
+ - Firmware versions
306
+ - End of Life status
307
+ - Various fault flags
308
+
309
+ ### Polling Interval
310
+
311
+ Recommend polling every 30-60 seconds. The device reports timestamps in `tStampEpoch` format.
312
+
313
+ ### OAuth Flow for Home Assistant
314
+
315
+ For Home Assistant, you'll need to implement the full OAuth PKCE flow:
316
+ 1. Generate code_verifier and code_challenge
317
+ 2. Open browser to authorization URL
318
+ 3. Handle callback with authorization code
319
+ 4. Exchange code for tokens
320
+ 5. Store and refresh tokens as needed
321
+
322
+ ---
323
+
324
+ ## Example Python Client
325
+
326
+ ```python
327
+ import requests
328
+
329
+ class ResideoClient:
330
+ def __init__(self, refresh_token: str):
331
+ self.client_id = "SRmiA7CaYi1JgivDZdzzoZu4X5VBogGt"
332
+ self.refresh_token = refresh_token
333
+ self.access_token = None
334
+
335
+ def _refresh_access_token(self):
336
+ resp = requests.post(
337
+ "https://login.resideo.com/oauth/token",
338
+ json={
339
+ "grant_type": "refresh_token",
340
+ "refresh_token": self.refresh_token,
341
+ "client_id": self.client_id
342
+ }
343
+ )
344
+ data = resp.json()
345
+ self.access_token = data["access_token"]
346
+ return self.access_token
347
+
348
+ def _headers(self):
349
+ if not self.access_token:
350
+ self._refresh_access_token()
351
+ return {
352
+ "Authorization": f"Bearer {self.access_token}",
353
+ "Content-Type": "application/json"
354
+ }
355
+
356
+ def get_accounts(self):
357
+ resp = requests.get(
358
+ "https://api.resideo.com/ris-public-api/api/v1/accounts",
359
+ headers=self._headers()
360
+ )
361
+ return resp.json()
362
+
363
+ def get_device_state(self, device_id: str):
364
+ resp = requests.get(
365
+ f"https://api.resideo.com/ris-public-api/api/v2/devices/smokeDetectors/{device_id}/state",
366
+ headers=self._headers()
367
+ )
368
+ return resp.json()
369
+
370
+ # Usage
371
+ client = ResideoClient(refresh_token="your_refresh_token")
372
+ accounts = client.get_accounts()
373
+ state = client.get_device_state("YOUR_DEVICE_ID")
374
+ print(f"Smoke: {state['deviceState']['reported']['alarmState']['smoke']['deviceState']}")
375
+ print(f"CO: {state['deviceState']['reported']['alarmState']['co']['deviceState']}")
376
+ print(f"Battery: {state['deviceState']['reported']['alarmState']['battery']['deviceState']}")
377
+ ```