@homebridge-plugins/homebridge-firstalert 0.0.1-beta.0

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 (121) hide show
  1. package/.gitattributes +2 -0
  2. package/.github/FUNDING.yml +4 -0
  3. package/.github/ISSUE_TEMPLATE/bug-report.yml +97 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/.github/ISSUE_TEMPLATE/feature-request.yml +38 -0
  6. package/.github/ISSUE_TEMPLATE/support-request.yml +85 -0
  7. package/.github/ISSUE_TEMPLATE.md +52 -0
  8. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +27 -0
  9. package/.github/dependabot.yml +17 -0
  10. package/.github/labeler.yml +38 -0
  11. package/.github/release-drafter.yml +33 -0
  12. package/.github/workflows/beta-release.yml +55 -0
  13. package/.github/workflows/build.yml +18 -0
  14. package/.github/workflows/changerelease.yml +11 -0
  15. package/.github/workflows/labeler.yml +9 -0
  16. package/.github/workflows/release-drafter.yml +14 -0
  17. package/.github/workflows/release.yml +35 -0
  18. package/.github/workflows/stale.yml +12 -0
  19. package/CHANGELOG.md +10 -0
  20. package/LICENSE +14 -0
  21. package/README.md +67 -0
  22. package/SECURITY.md +19 -0
  23. package/branding/Homebridge_x_FirstAlert.svg +48 -0
  24. package/branding/icon.png +0 -0
  25. package/config.schema.json +58 -0
  26. package/dist/homebridge-ui/public/index.html +548 -0
  27. package/dist/src/api/resideoClient.d.ts +32 -0
  28. package/dist/src/api/resideoClient.d.ts.map +1 -0
  29. package/dist/src/api/resideoClient.js +76 -0
  30. package/dist/src/api/resideoClient.js.map +1 -0
  31. package/dist/src/devices/device.d.ts +40 -0
  32. package/dist/src/devices/device.d.ts.map +1 -0
  33. package/dist/src/devices/device.js +207 -0
  34. package/dist/src/devices/device.js.map +1 -0
  35. package/dist/src/devices/leaksensors.d.ts +34 -0
  36. package/dist/src/devices/leaksensors.d.ts.map +1 -0
  37. package/dist/src/devices/leaksensors.js +163 -0
  38. package/dist/src/devices/leaksensors.js.map +1 -0
  39. package/dist/src/devices/smoke.d.ts +35 -0
  40. package/dist/src/devices/smoke.d.ts.map +1 -0
  41. package/dist/src/devices/smoke.js +150 -0
  42. package/dist/src/devices/smoke.js.map +1 -0
  43. package/dist/src/devices/thermostats.d.ts +42 -0
  44. package/dist/src/devices/thermostats.d.ts.map +1 -0
  45. package/dist/src/devices/thermostats.js +192 -0
  46. package/dist/src/devices/thermostats.js.map +1 -0
  47. package/dist/src/devices/valve.d.ts +21 -0
  48. package/dist/src/devices/valve.d.ts.map +1 -0
  49. package/dist/src/devices/valve.js +131 -0
  50. package/dist/src/devices/valve.js.map +1 -0
  51. package/dist/src/homebridge-ui/server.d.ts +5 -0
  52. package/dist/src/homebridge-ui/server.d.ts.map +1 -0
  53. package/dist/src/homebridge-ui/server.js +95 -0
  54. package/dist/src/homebridge-ui/server.js.map +1 -0
  55. package/dist/src/index.d.ts +4 -0
  56. package/dist/src/index.d.ts.map +1 -0
  57. package/dist/src/index.js +7 -0
  58. package/dist/src/index.js.map +1 -0
  59. package/dist/src/platform.d.ts +18 -0
  60. package/dist/src/platform.d.ts.map +1 -0
  61. package/dist/src/platform.js +108 -0
  62. package/dist/src/platform.js.map +1 -0
  63. package/dist/src/settings.d.ts +341 -0
  64. package/dist/src/settings.d.ts.map +1 -0
  65. package/dist/src/settings.js +25 -0
  66. package/dist/src/settings.js.map +1 -0
  67. package/dist/src/utils.d.ts +21 -0
  68. package/dist/src/utils.d.ts.map +1 -0
  69. package/dist/src/utils.js +58 -0
  70. package/dist/src/utils.js.map +1 -0
  71. package/dist/test/index.test.d.ts +2 -0
  72. package/dist/test/index.test.d.ts.map +1 -0
  73. package/dist/test/index.test.js +14 -0
  74. package/dist/test/index.test.js.map +1 -0
  75. package/dist/test/platform.test.d.ts +2 -0
  76. package/dist/test/platform.test.d.ts.map +1 -0
  77. package/dist/test/platform.test.js +56 -0
  78. package/dist/test/platform.test.js.map +1 -0
  79. package/dist/test/settings.test.d.ts +2 -0
  80. package/dist/test/settings.test.d.ts.map +1 -0
  81. package/dist/test/settings.test.js +48 -0
  82. package/dist/test/settings.test.js.map +1 -0
  83. package/dist/test/utils.test.d.ts +2 -0
  84. package/dist/test/utils.test.d.ts.map +1 -0
  85. package/dist/test/utils.test.js +17 -0
  86. package/dist/test/utils.test.js.map +1 -0
  87. package/docs/.nojekyll +1 -0
  88. package/docs/assets/hierarchy.js +1 -0
  89. package/docs/assets/highlight.css +22 -0
  90. package/docs/assets/icons.js +18 -0
  91. package/docs/assets/icons.svg +1 -0
  92. package/docs/assets/main.js +60 -0
  93. package/docs/assets/navigation.js +1 -0
  94. package/docs/assets/search.js +1 -0
  95. package/docs/assets/style.css +1633 -0
  96. package/docs/hierarchy.html +1 -0
  97. package/docs/index.html +77 -0
  98. package/docs/modules.html +1 -0
  99. package/docs/variables/default.html +1 -0
  100. package/eslint.config.js +44 -0
  101. package/nodemon.json +10 -0
  102. package/package.json +106 -0
  103. package/scripts/free-dev-ports.mjs +105 -0
  104. package/src/api/resideoClient.ts +106 -0
  105. package/src/devices/device.ts +226 -0
  106. package/src/devices/leaksensors.ts +206 -0
  107. package/src/devices/smoke.ts +173 -0
  108. package/src/devices/thermostats.ts +243 -0
  109. package/src/devices/valve.ts +162 -0
  110. package/src/homebridge-ui/public/index.html +548 -0
  111. package/src/homebridge-ui/server.ts +102 -0
  112. package/src/index.ts +13 -0
  113. package/src/platform.ts +112 -0
  114. package/src/settings.ts +402 -0
  115. package/src/utils.ts +61 -0
  116. package/test/index.test.ts +18 -0
  117. package/test/platform.test.ts +65 -0
  118. package/test/settings.test.ts +56 -0
  119. package/test/utils.test.ts +20 -0
  120. package/tsconfig.json +27 -0
  121. package/typedoc.json +22 -0
@@ -0,0 +1 @@
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="./"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>@homebridge-plugins/homebridge-firstalert</title><meta name="description" content="Documentation for @homebridge-plugins/homebridge-firstalert"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="assets/style.css"/><link rel="stylesheet" href="assets/highlight.css"/><script defer src="assets/main.js"></script><script async src="assets/icons.js" id="tsd-icons-script"></script><script async src="assets/search.js" id="tsd-search-script"></script><script async src="assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="index.html" class="title">@homebridge-plugins/homebridge-firstalert</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><h1>@homebridge-plugins/homebridge-firstalert</h1></div><h2>Hierarchy Summary</h2></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="modules.html">@homebridge-plugins/homebridge-firstalert</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
@@ -0,0 +1,77 @@
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="./"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>@homebridge-plugins/homebridge-firstalert</title><meta name="description" content="Documentation for @homebridge-plugins/homebridge-firstalert"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="assets/style.css"/><link rel="stylesheet" href="assets/highlight.css"/><script defer src="assets/main.js"></script><script async src="assets/icons.js" id="tsd-icons-script"></script><script async src="assets/search.js" id="tsd-search-script"></script><script async src="assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="index.html" class="title">@homebridge-plugins/homebridge-firstalert</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><h1>@homebridge-plugins/homebridge-firstalert</h1></div><div class="tsd-panel tsd-typography"><span align="center">
2
+ <p><a href="https://github.com/homebridge/verified/blob/latest/verified-plugins.json"><img alt="homebridge-verified" src="https://raw.githubusercontent.com/homebridge-plugins/homebridge-firstalert/latest/branding/Homebridge_x_Resideo.svg?sanitize=true" width="350px"></a></p>
3
+ <h1 id="homebridge-firstalert" class="tsd-anchor-link">Homebridge FirstAlert<a href="#homebridge-firstalert" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h1>
4
+ <p><a href="https://www.npmjs.com/package/homebridge-firstalert"><img title="npm version" src="https://badgen.net/npm/v/homebridge-firstalert?icon=npm&label" ></a>
5
+ <a href="https://www.npmjs.com/package/homebridge-firstalert"><img title="npm downloads" src="https://badgen.net/npm/dt/homebridge-firstalert?label=downloads" ></a>
6
+ <a href="https://discord.gg/8fpZA4S"><img title="discord-firstalert" src="https://badgen.net/discord/online-members/8fpZA4S?icon=discord&label=discord" ></a>
7
+ <a href="https://paypal.me/donavanbecker"><img title="donate" src="https://badgen.net/badge/donate/paypal/yellow" ></a></p>
8
+ <p>The Homebridge <a href="https://firstalert.com">FirstAlert</a>
9
+ plugin allows you to access your FirstAlert Device(s) from HomeKit with
10
+ <a href="https://homebridge.io">Homebridge</a>.
11
+ </p>
12
+ </span>
13
+ <h2 id="installation" class="tsd-anchor-link">Installation<a href="#installation" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
14
+ <ol>
15
+ <li>Search for &quot;FirstAlert&quot; on the plugin screen of <a href="https://github.com/oznu/homebridge-config-ui-x">Homebridge Config UI X</a>.</li>
16
+ <li>Click <strong>Install</strong>.</li>
17
+ </ol>
18
+ <h2 id="configuration" class="tsd-anchor-link">Configuration<a href="#configuration" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
19
+ <ol>
20
+ <li>Login / create an account at <a href="https://developer.honeywellhome.com/user">https://developer.honeywellhome.com/user</a>
21
+ <ul>
22
+ <li>Your FirstAlert Developer Account, this account is different then your FirstAlert Account that you log into the FirstAlert App with</li>
23
+ </ul>
24
+ </li>
25
+ <li>Click <strong>Create New App</strong></li>
26
+ <li>Give your application a name</li>
27
+ <li>Copy the hostname found on #3 of the Intro Page into the Callback URL field</li>
28
+ </ol>
29
+ <p align="center">
30
+ <img src="https://user-images.githubusercontent.com/9875439/192078620-6998511c-1c11-4e06-b7e1-475b22ef5180.png" width="300px">
31
+ </p>
32
+ <ol start="5">
33
+ <li>Enter the generated consumer key and secret into the plugin settings screen of <a href="https://github.com/oznu/homebridge-config-ui-x">Homebridge Config UI X</a></li>
34
+ <li>Click <strong>Link Account</strong></li>
35
+ </ol>
36
+ <p align="center">
37
+ <img src="https://user-images.githubusercontent.com/9875439/192078614-e568d121-e0e4-4123-bdda-4238e8a6e601.png" width="300px">
38
+ </p>
39
+ <ol start="7">
40
+ <li>Login to your <a href="https://account.honeywellhome.com">https://www.firstalert.com</a>.</li>
41
+ <li>Click Allow</li>
42
+ <li>Select Devices
43
+ <ul>
44
+ <li>I would recommend selecting all devices since you can restrict the devices you don't want in the Home app later, by DeviceID.</li>
45
+ </ul>
46
+ </li>
47
+ <li>Click Connect</li>
48
+ <li>Click Save
49
+ <ul>
50
+ <li>If you plan on adding this plugin into a child bridge, I would configure that at this time before restarting Homebridge.
51
+ <ul>
52
+ <li>Reminder that you will have to add this child bridge into the home app to get FirstAlert accessories to show up.</li>
53
+ </ul>
54
+ </li>
55
+ </ul>
56
+ </li>
57
+ <li>Restart Homebridge</li>
58
+ </ol>
59
+ <h2 id="supported-firstalert-devices" class="tsd-anchor-link">Supported FirstAlert Devices<a href="#supported-firstalert-devices" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></h2>
60
+ <ul>
61
+ <li><a href="https://www.firstalert.com/us/en/products/air/thermostats/wifi-thermostats/t10-pro-smart-thermostat-with-redlinkr-room-sensor-thx321wfs2001w-u/">T10 Thermostat</a> - Already HomeKit Certified</li>
62
+ <li><a href="https://www.firstalert.com/us/en/products/air/thermostats/wifi-thermostats/t9-smart-thermostat-with-sensor-rcht9610wfsw2003-u/">T9 Thermostat</a> - Already HomeKit Certified
63
+ <ul>
64
+ <li><a href="https://www.firstalert.com/us/en/products/air/thermostat-accessories/t9-smart-sensor-rchtsensor-1pk-u/">T9 Smart Roomsensors</a></li>
65
+ </ul>
66
+ </li>
67
+ <li><a href="https://www.firstalert.com/us/en/products/air/thermostats/wifi-thermostats/t6-pro-smart-thermostat-multi-stage-3-heat-2-cool-th6320wf2003-u/">T6 Thermostat</a> - Already HomeKit Certified</li>
68
+ <li><a href="https://www.firstalert.com/us/en/products/air/thermostats/wifi-thermostats/t5-smart-thermostat-with-c-wire-adapter-rcht8612wf2005-u/">T5 Thermostat</a> - Already HomeKit Certified</li>
69
+ <li><a href="https://www.firstalert.com/us/en/products/air/thermostats/wifi-thermostats/the-round-smart-thermostat-rch9310wf5003-u/">Round Thermostat</a> - Already HomeKit Certified</li>
70
+ <li>Some Total Comfort Control Thermostats
71
+ <ul>
72
+ <li>Pushing Commands may not be supported on some.</li>
73
+ </ul>
74
+ </li>
75
+ <li><a href="https://www.firstalert.com/us/en/products/water/spot-leak-detection/wifi-water-leak-freeze-detector-rchw3610wf1001-u/">WiFi Water Leak &amp; Freeze Detector</a></li>
76
+ </ul>
77
+ </div></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><a href="#homebridge-firstalert"><span>Homebridge <wbr/>First<wbr/>Alert</span></a><ul><li><a href="#installation"><span>Installation</span></a></li><li><a href="#configuration"><span>Configuration</span></a></li><li><a href="#supported-firstalert-devices"><span>Supported <wbr/>First<wbr/>Alert <wbr/>Devices</span></a></li></ul></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="modules.html">@homebridge-plugins/homebridge-firstalert</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
@@ -0,0 +1 @@
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="./"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>@homebridge-plugins/homebridge-firstalert</title><meta name="description" content="Documentation for @homebridge-plugins/homebridge-firstalert"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="assets/style.css"/><link rel="stylesheet" href="assets/highlight.css"/><script defer src="assets/main.js"></script><script async src="assets/icons.js" id="tsd-icons-script"></script><script async src="assets/search.js" id="tsd-search-script"></script><script async src="assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="index.html" class="title">@homebridge-plugins/homebridge-firstalert</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"></ul><h1>@homebridge-plugins/homebridge-firstalert</h1></div><details class="tsd-panel-group tsd-member-group tsd-accordion" open><summary class="tsd-accordion-summary" data-key="section-Variables"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h2>Variables</h2></summary><dl class="tsd-member-summaries"><dt class="tsd-member-summary" id="default"><span class="tsd-member-summary-name"><svg class="tsd-kind-icon" viewBox="0 0 24 24" aria-label="Variable"><use href="assets/icons.svg#icon-32"></use></svg><a href="variables/default.html">default</a><a href="#default" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="assets/icons.svg#icon-anchor"></use></svg></a></span></dt><dd class="tsd-member-summary"></dd></dl></details></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div><details open class="tsd-accordion tsd-page-navigation"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg><h3>On This Page</h3></summary><div class="tsd-accordion-details"><details open class="tsd-accordion tsd-page-navigation-section"><summary class="tsd-accordion-summary" data-key="section-Variables"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="assets/icons.svg#icon-chevronDown"></use></svg>Variables</summary><div><a href="#default"><svg class="tsd-kind-icon" viewBox="0 0 24 24" aria-label="Variable"><use href="assets/icons.svg#icon-32"></use></svg><span>default</span></a></div></details></div></details></div><div class="site-menu"><nav class="tsd-navigation"><a href="modules.html" class="current">@homebridge-plugins/homebridge-firstalert</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
@@ -0,0 +1 @@
1
+ <!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>default | @homebridge-plugins/homebridge-firstalert</title><meta name="description" content="Documentation for @homebridge-plugins/homebridge-firstalert"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">@homebridge-plugins/homebridge-firstalert</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="" aria-current="page">default</a></li></ul><h1>Variable default</h1></div><div class="tsd-signature"><span class="tsd-kind-variable">default</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span> <span class="tsd-signature-symbol">=&gt;</span> <span class="tsd-signature-type">void</span></div><div class="tsd-type-declaration"><h4>Type Declaration</h4><ul class="tsd-parameters"><li class="tsd-parameter-signature"><ul class="tsd-signatures"><li class="tsd-signature" id="__type"><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">api</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">API</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">void</span></li><li class="tsd-description"><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">api</span>: <span class="tsd-signature-type">API</span></span></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">void</span></h4></li></ul></li></ul></div><aside class="tsd-sources"><ul><li>Defined in <a href="https://github.com/homebridge-plugins/homebridge-firstalert/blob/4e9b7a0dafe971e2a3d4653fe9b6c5b65ad9c47d/src/index.ts#L11">index.ts:11</a></li></ul></aside></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">@homebridge-plugins/homebridge-firstalert</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer></footer><div class="overlay"></div></body></html>
@@ -0,0 +1,44 @@
1
+ import antfu from '@antfu/eslint-config'
2
+
3
+ export default antfu(
4
+ {
5
+ ignores: ['dist', 'docs'],
6
+ jsx: false,
7
+ typescript: true,
8
+ formatters: {
9
+ markdown: true,
10
+ },
11
+ rules: {
12
+ 'curly': ['error', 'multi-line'],
13
+ 'import/order': 0,
14
+ 'jsdoc/check-alignment': 'error',
15
+ 'jsdoc/check-line-alignment': 'error',
16
+ 'no-undef': 'error',
17
+ 'perfectionist/sort-exports': 'error',
18
+ 'perfectionist/sort-imports': [
19
+ 'error',
20
+ {
21
+ groups: [
22
+ 'builtin',
23
+ 'external',
24
+ 'internal',
25
+ ['parent', 'sibling', 'index'],
26
+ 'unknown',
27
+ ],
28
+ order: 'asc',
29
+ type: 'natural',
30
+ },
31
+ ],
32
+ 'perfectionist/sort-named-exports': 'error',
33
+ 'perfectionist/sort-named-imports': 'error',
34
+ 'sort-imports': 0,
35
+ 'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
36
+ 'style/quote-props': ['error', 'consistent-as-needed'],
37
+ 'test/no-only-tests': 'error',
38
+ 'unicorn/no-useless-spread': 'error',
39
+ 'unused-imports/no-unused-vars': ['error', { caughtErrors: 'none' }],
40
+ 'no-new': 0, // Disable the no-new rule
41
+ 'new-cap': 0, // Disable the new-cap rule
42
+ },
43
+ },
44
+ )
package/nodemon.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "watch": ["src"],
3
+ "ext": "ts,html,json",
4
+ "ignore": [],
5
+ "exec": "npm run build",
6
+ "signal": "SIGTERM",
7
+ "env": {
8
+ "NODE_OPTIONS": "--trace-warnings"
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@homebridge-plugins/homebridge-firstalert",
3
+ "displayName": "FirstAlert",
4
+ "type": "module",
5
+ "version": "0.0.1-beta.0",
6
+ "description": "The FirstAlert plugin allows you to access your FirstAlert device(s) from HomeKit.",
7
+ "author": {
8
+ "name": "donavanbecker",
9
+ "url": "https://github.com/donavanbecker"
10
+ },
11
+ "maintainers": [
12
+ "donavanbecker"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "license": "ISC",
18
+ "funding": [
19
+ {
20
+ "type": "Paypal",
21
+ "url": "https://paypal.me/donavanbecker"
22
+ },
23
+ {
24
+ "type": "GitHub",
25
+ "url": "https://github.com/sponsors/donavanbecker"
26
+ }
27
+ ],
28
+ "homepage": "https://github.com/homebridge-plugins/homebridge-firstalert",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/homebridge-plugins/homebridge-firstalert.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/homebridge-plugins/homebridge-firstalert/issues"
35
+ },
36
+ "keywords": [
37
+ "homebridge-plugin",
38
+ "honeywell",
39
+ "honeywell-home",
40
+ "thermostat",
41
+ "leaksensor",
42
+ "leak sensor",
43
+ "lcc",
44
+ "tcc",
45
+ "FirstAlert",
46
+ "First Alert",
47
+ "Resideo",
48
+ "honeywellhome",
49
+ "honeywell home",
50
+ "temperature",
51
+ "humidity",
52
+ "leak"
53
+ ],
54
+ "main": "dist/index.js",
55
+ "engines": {
56
+ "homebridge": "^1.11.3 || ^2.0.0-beta.77",
57
+ "node": "^22 || ^24"
58
+ },
59
+ "scripts": {
60
+ "check": "npm install && npm outdated",
61
+ "ports:free": "node scripts/free-dev-ports.mjs",
62
+ "typecheck": "tsc --noEmit",
63
+ "lint": "eslint src/**/*.ts test/**/*.ts src/homebridge-ui/**/*.html && npm run typecheck",
64
+ "lint:src": "eslint src/**/*.ts",
65
+ "lint:test": "eslint test/**/*.ts",
66
+ "lint:ui": "eslint src/homebridge-ui/**/*.html",
67
+ "lint:fix": "eslint src/**/*.ts test/**/*.ts src/homebridge-ui/**/*.html --fix && npm run typecheck",
68
+ "watch": "npm run ports:free && npm run build && npm run plugin-ui && npm link && nodemon",
69
+ "watch:ui": "npm run ports:free && bash -c 'npx homebridge -I -U -D ~/.homebridge-dev & HB_PID=$!; sleep 6; UIX_STORAGE_PATH=~/.homebridge-dev node ./node_modules/homebridge-config-ui-x/dist/bin/standalone.js & npm run watch:files'",
70
+ "watch:files": "nodemon",
71
+ "plugin-ui": "mkdir -p ./dist/homebridge-ui/public && if [ -d ./src/homebridge-ui/public ]; then rsync -a ./src/homebridge-ui/public/ ./dist/homebridge-ui/public/; fi",
72
+ "build": "npm run clean && tsc && npm run plugin-ui",
73
+ "prepublishOnly": "npm run lint && npm run build && npm run plugin-ui && npm run docs && npm run lint-docs",
74
+ "postpublish": "npm run clean && npm ci",
75
+ "clean": "shx rm -rf ./dist",
76
+ "test": "npm run lint",
77
+ "docs": "typedoc",
78
+ "lint-docs": "typedoc --emit none --treatWarningsAsErrors"
79
+ },
80
+ "dependencies": {
81
+ "@homebridge/plugin-ui-utils": "^2.2.2",
82
+ "@types/jsonwebtoken": "^9.0.10",
83
+ "@types/ms": "^2.1.0",
84
+ "class-validator": "^0.15.1",
85
+ "rxjs": "^7.8.1",
86
+ "undici": "^7.24.5"
87
+ },
88
+ "devDependencies": {
89
+ "@antfu/eslint-config": "^7.7.3",
90
+ "@types/debug": "^4.1.13",
91
+ "@types/node": "^25.5.0",
92
+ "@types/semver": "^7.7.1",
93
+ "@vitest/coverage-v8": "^4.1.0",
94
+ "esbuild": "^0.27.4",
95
+ "eslint": "^10.1.0",
96
+ "eslint-plugin-format": "^2.0.1",
97
+ "homebridge": "^1.11.3",
98
+ "homebridge-config-ui-x": "^5.20.0",
99
+ "nodemon": "^3.1.14",
100
+ "shx": "^0.4.0",
101
+ "ts-node": "^10.9.2",
102
+ "typedoc": "^0.28.18",
103
+ "typescript": "^5.9.3",
104
+ "vitest": "^4.1.0"
105
+ }
106
+ }
@@ -0,0 +1,105 @@
1
+ import { execSync } from 'node:child_process'
2
+ import { readFileSync } from 'node:fs'
3
+ import { homedir } from 'node:os'
4
+ import { resolve } from 'node:path'
5
+
6
+ function resolvePorts() {
7
+ let bridgePort = 51829
8
+ let uiPort = 8581
9
+
10
+ try {
11
+ const configPath = resolve(homedir(), '.homebridge-dev', 'config.json')
12
+ const config = JSON.parse(readFileSync(configPath, 'utf8'))
13
+
14
+ if (typeof config?.bridge?.port === 'number') {
15
+ bridgePort = config.bridge.port
16
+ }
17
+
18
+ const configUiPlatform = Array.isArray(config?.platforms)
19
+ ? config.platforms.find(platform => platform?.platform === 'config')
20
+ : undefined
21
+
22
+ if (typeof configUiPlatform?.port === 'number') {
23
+ uiPort = configUiPlatform.port
24
+ }
25
+ } catch {}
26
+
27
+ return [...new Set([bridgePort, uiPort])]
28
+ }
29
+
30
+ function pidsForPort(port) {
31
+ try {
32
+ const output = execSync(`lsof -ti tcp:${port}`, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim()
33
+ return output ? output.split('\n').filter(Boolean) : []
34
+ } catch {
35
+ return []
36
+ }
37
+ }
38
+
39
+ function freePort(port) {
40
+ const initialPids = pidsForPort(port)
41
+ if (initialPids.length === 0) {
42
+ return
43
+ }
44
+
45
+ try {
46
+ execSync(`lsof -ti tcp:${port} | xargs -r kill -TERM`, { stdio: ['ignore', 'ignore', 'ignore'] })
47
+ } catch {}
48
+
49
+ const remainingPids = pidsForPort(port)
50
+ if (remainingPids.length > 0) {
51
+ try {
52
+ execSync(`lsof -ti tcp:${port} | xargs -r kill -KILL`, { stdio: ['ignore', 'ignore', 'ignore'] })
53
+ } catch {}
54
+ }
55
+ }
56
+
57
+ function cleanupDevProcesses() {
58
+ let output = ''
59
+ try {
60
+ output = execSync('ps -axo pid=,command=', { stdio: ['ignore', 'pipe', 'ignore'] }).toString()
61
+ } catch {
62
+ return
63
+ }
64
+
65
+ const targetPids = output
66
+ .split('\n')
67
+ .map(line => line.trim())
68
+ .filter(Boolean)
69
+ .map((line) => {
70
+ const firstSpace = line.indexOf(' ')
71
+ if (firstSpace === -1) {
72
+ return null
73
+ }
74
+
75
+ const pid = Number(line.slice(0, firstSpace).trim())
76
+ const command = line.slice(firstSpace + 1)
77
+ return Number.isFinite(pid) ? { pid, command } : null
78
+ })
79
+ .filter((entry) => {
80
+ if (!entry) {
81
+ return false
82
+ }
83
+
84
+ if (entry.pid === process.pid || entry.pid === process.ppid) {
85
+ return false
86
+ }
87
+
88
+ const isNodemonProcess = entry.command.includes('homebridge-switchbot/node_modules/.bin/nodemon')
89
+ const isConfigUiProcess = entry.command.trim().startsWith('homebridge-config-ui-x')
90
+ return isNodemonProcess || isConfigUiProcess
91
+ })
92
+ .map(entry => entry.pid)
93
+
94
+ for (const pid of targetPids) {
95
+ try {
96
+ process.kill(pid, 'SIGTERM')
97
+ } catch {}
98
+ }
99
+ }
100
+
101
+ cleanupDevProcesses()
102
+
103
+ for (const port of resolvePorts()) {
104
+ freePort(port)
105
+ }
@@ -0,0 +1,106 @@
1
+ // FirstAlert API client for OAuth and device polling
2
+ // Implements token refresh, account/device fetch, and strong typing
3
+
4
+ import { request } from 'undici'
5
+
6
+ export interface ResideoDevice {
7
+ id: string
8
+ name: string
9
+ deviceId: string
10
+ globalDeviceType: string
11
+ }
12
+
13
+ export interface ResideoAccount {
14
+ id: string
15
+ firstName: string
16
+ lastName: string
17
+ contactEmail: string
18
+ countryCode: string
19
+ locale: string
20
+ devices: ResideoDevice[]
21
+ }
22
+
23
+ export interface ResideoDeviceState {
24
+ name: string
25
+ deviceType: string
26
+ isOnline: boolean
27
+ deviceState: any
28
+ }
29
+
30
+ export class ResideoClient {
31
+ private clientId = 'SRmiA7CaYi1JgivDZdzzoZu4X5VBogGt'
32
+ private refreshToken: string
33
+ private accessToken: string | null = null
34
+
35
+ constructor(refreshToken: string) {
36
+ this.refreshToken = refreshToken
37
+ }
38
+
39
+ async refreshAccessToken(): Promise<string> {
40
+ const body = JSON.stringify({
41
+ grant_type: 'refresh_token',
42
+ refresh_token: this.refreshToken,
43
+ client_id: this.clientId,
44
+ })
45
+ const { body: resBody } = await request('https://login.firstalert.com/oauth/token', {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body,
49
+ })
50
+ const data = await resBody.json() as { access_token?: string }
51
+ this.accessToken = data.access_token ?? ''
52
+ if (!this.accessToken) {
53
+ throw new Error('Failed to obtain access token from FirstAlert API')
54
+ }
55
+ return this.accessToken
56
+ }
57
+
58
+ async getAccessToken(): Promise<string> {
59
+ if (!this.accessToken) {
60
+ await this.refreshAccessToken()
61
+ }
62
+ return this.accessToken!
63
+ }
64
+
65
+ async getAccount(): Promise<ResideoAccount> {
66
+ const token = await this.getAccessToken()
67
+ const { body: resBody } = await request('https://api.firstalert.com/ris-public-api/api/v1/accounts', {
68
+ method: 'GET',
69
+ headers: { Authorization: `Bearer ${token}` },
70
+ })
71
+ const resp = await resBody.json() as { data: any }
72
+ // Parse and flatten devices
73
+ const data = resp.data
74
+ const devices: ResideoDevice[] = []
75
+ for (const user of data.consumerUsers) {
76
+ for (const loc of user.consumerAccount.locations) {
77
+ for (const dev of loc.consumerDevices) {
78
+ devices.push({
79
+ id: dev.id,
80
+ name: dev.name,
81
+ deviceId: dev.device.deviceId,
82
+ globalDeviceType: dev.device.globalDeviceType,
83
+ })
84
+ }
85
+ }
86
+ }
87
+ return {
88
+ id: data.id,
89
+ firstName: data.firstName,
90
+ lastName: data.lastName,
91
+ contactEmail: data.contactEmail,
92
+ countryCode: data.countryCode,
93
+ locale: data.locale,
94
+ devices,
95
+ }
96
+ }
97
+
98
+ async getDeviceState(deviceId: string): Promise<ResideoDeviceState> {
99
+ const token = await this.getAccessToken()
100
+ const { body: resBody } = await request(`https://api.firstalert.com/ris-public-api/api/v2/devices/smokeDetectors/${deviceId}/state`, {
101
+ method: 'GET',
102
+ headers: { Authorization: `Bearer ${token}` },
103
+ })
104
+ return await resBody.json() as ResideoDeviceState
105
+ }
106
+ }