MQTT smart home web interface
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <meta name="color-scheme" content="dark light">
  7. <title>Lights Control</title>
  8. <!--<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">-->
  9. <link href="https://cdn.jsdelivr.net/npm/bootstrap-dark-5@1.1.3/dist/css/bootstrap-dark.min.css" rel="stylesheet">
  10. </head>
  11. <body>
  12. <div class="container text-center">
  13. <div class="row">
  14. <div class="col">
  15. <h1>Lights Control</h1>
  16. </div>
  17. </div>
  18. <nav>
  19. <div class="nav nav-tabs" id="nav-tab" role="tablist">
  20. <button class="nav-link" id="nav-livingroom-tab" data-bs-toggle="tab" data-bs-target="#nav-livingroom" type="button" role="tab" aria-controls="nav-livingroom" aria-selected="false">
  21. Livingroom
  22. </button>
  23. <button class="nav-link active" id="nav-bathroom-tab" data-bs-toggle="tab" data-bs-target="#nav-bathroom" type="button" role="tab" aria-controls="nav-bathroom" aria-selected="true">
  24. Bathroom
  25. </button>
  26. <button class="nav-link" id="nav-help-tab" data-bs-toggle="tab" data-bs-target="#nav-help" type="button" role="tab" aria-controls="nav-help" aria-selected="false">
  27. Help
  28. </button>
  29. </div>
  30. </nav>
  31. <div class="row">
  32. <div class="col">
  33. <hr>
  34. </div>
  35. </div>
  36. <div class="tab-content" id="nav-tabContent">
  37. <div class="tab-pane fade show active" id="nav-bathroom" role="tabpanel" aria-labelledby="nav-bathroom-tab" tabindex="0">
  38. <div class="row">
  39. <div class="col">
  40. <h2>Actors</h2>
  41. </div>
  42. </div>
  43. <div class="row">
  44. <div class="col">
  45. <p>Lights</p>
  46. </div>
  47. <div class="col">
  48. <div class="btn-group" role="group">
  49. <input type="radio" class="btn-check" name="bathroomradio" id="bathroomlightauto" autocomplete="off">
  50. <label class="btn btn-outline-primary" for="bathroomlightauto">
  51. Auto
  52. </label>
  53. <input type="radio" class="btn-check" name="bathroomradio" id="bathroomlightbig" autocomplete="off">
  54. <label class="btn btn-outline-success" for="bathroomlightbig">
  55. Big
  56. </label>
  57. <input type="radio" class="btn-check" name="bathroomradio" id="bathroomlightsmall" autocomplete="off">
  58. <label class="btn btn-outline-info" for="bathroomlightsmall">
  59. Small
  60. </label>
  61. <input type="radio" class="btn-check" name="bathroomradio" id="bathroomlightoff" autocomplete="off">
  62. <label class="btn btn-outline-dark" for="bathroomlightoff">
  63. Off
  64. </label>
  65. </div>
  66. </div>
  67. </div>
  68. <div class="row">
  69. <div class="col">
  70. <hr>
  71. </div>
  72. </div>
  73. <div class="row">
  74. <div class="col">
  75. <h2>Sensors</h2>
  76. </div>
  77. </div>
  78. <div class="row">
  79. <div class="col">
  80. <p>Temperature</p>
  81. </div>
  82. <div class="col" id="bathtemp">
  83. <p>Unknown</p>
  84. </div>
  85. </div>
  86. <div class="row">
  87. <div class="col">
  88. <p>Relative Humidity</p>
  89. </div>
  90. <div class="col" id="bathhumid">
  91. <p>Unknown</p>
  92. </div>
  93. </div>
  94. <div class="row">
  95. <div class="col">
  96. <p>tVOC</p>
  97. </div>
  98. <div class="col" id="bathtvoc">
  99. <p>Unknown</p>
  100. </div>
  101. </div>
  102. <div class="row">
  103. <div class="col">
  104. <p>eCo2</p>
  105. </div>
  106. <div class="col" id="batheco2">
  107. <p>Unknown</p>
  108. </div>
  109. </div>
  110. <div class="row">
  111. <div class="col">
  112. <p>Air Pressure</p>
  113. </div>
  114. <div class="col" id="bathpress">
  115. <p>Unknown</p>
  116. </div>
  117. </div>
  118. </div>
  119. <div class="tab-pane fade" id="nav-livingroom" role="tabpanel" aria-labelledby="nav-livingroom-tab" tabindex="0">
  120. <div class="row">
  121. <div class="col">
  122. <p>No controls available yet.</p>
  123. </div>
  124. </div>
  125. <div class="row">
  126. <div class="col">
  127. <p>Workspace Lights</p>
  128. </div>
  129. <div class="col">
  130. <div class="btn-group" role="group">
  131. <input type="radio" class="btn-check" name="workspaceradio" id="workspaceon" autocomplete="off">
  132. <label class="btn btn-outline-primary" for="workspaceon">
  133. On
  134. </label>
  135. <input type="radio" class="btn-check" name="workspaceradio" id="workspaceoff" autocomplete="off">
  136. <label class="btn btn-outline-dark" for="workspaceoff">
  137. Off
  138. </label>
  139. </div>
  140. </div>
  141. </div>
  142. <div class="row">
  143. <div class="col">
  144. <p>TV Lights</p>
  145. </div>
  146. <div class="col">
  147. <div class="btn-group" role="group">
  148. <input type="radio" class="btn-check" name="tvradio" id="tvon" autocomplete="off">
  149. <label class="btn btn-outline-primary" for="tvon">
  150. On
  151. </label>
  152. <input type="radio" class="btn-check" name="tvradio" id="tvoff" autocomplete="off">
  153. <label class="btn btn-outline-dark" for="tvoff">
  154. Off
  155. </label>
  156. </div>
  157. </div>
  158. </div>
  159. <div class="row">
  160. <div class="col">
  161. <p>Kitchen Lights</p>
  162. </div>
  163. <div class="col">
  164. <div class="btn-group" role="group">
  165. <input type="radio" class="btn-check" name="kitchenradio" id="kitchenon" autocomplete="off">
  166. <label class="btn btn-outline-primary" for="kitchenon">
  167. On
  168. </label>
  169. <input type="radio" class="btn-check" name="kitchenradio" id="kitchenoff" autocomplete="off">
  170. <label class="btn btn-outline-dark" for="kitchenoff">
  171. Off
  172. </label>
  173. </div>
  174. </div>
  175. </div>
  176. </div>
  177. <div class="tab-pane fade" id="nav-help" role="tabpanel" aria-labelledby="nav-help-tab" tabindex="0">
  178. <div class="row">
  179. <div class="col">
  180. <p><a href=".">Refresh page</a></p>
  181. </div>
  182. </div>
  183. <div class="row">
  184. <div class="col">
  185. <p>Please wait after opening the page until the buttons change to reflect the current state of the lights. If that doesn't happen after a couple of seconds, there is probably a connection problem.</p>
  186. </div>
  187. </div>
  188. <div class="row">
  189. <div class="col">
  190. <p><b>Please note:</b> this only works when the MQTT broker and the NodeRED logic are working properly. Also not all web browsers support web socket connections to the MQTT broker. Firefox gives problems, so try a Chromium based browser instead.</p>
  191. </div>
  192. </div>
  193. </div>
  194. </div>
  195. <div class="row">
  196. <div class="col">
  197. <hr>
  198. </div>
  199. </div>
  200. </div>
  201. <!--<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>-->
  202. <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
  203. <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
  204. <script src="credentials.js"></script>
  205. <script src="lights.js"></script>
  206. <script>
  207. const btnsBath = document.querySelectorAll("#bathroomlightauto, #bathroomlightbig, #bathroomlightsmall, #bathroomlightoff")
  208. // handle changes to bathroom lights
  209. subscribeTopic("bathroom/force_light", function (msg) {
  210. // clear all buttons
  211. for (const btn of btnsBath) {
  212. btn.checked = false
  213. }
  214. // activate proper button
  215. if (msg == "none") {
  216. const btn = document.querySelector("#bathroomlightauto")
  217. btn.checked = true
  218. } else if (msg == "big") {
  219. const btn = document.querySelector("#bathroomlightbig")
  220. btn.checked = true
  221. } else if (msg == "small") {
  222. const btn = document.querySelector("#bathroomlightsmall")
  223. btn.checked = true
  224. } else if (msg == "off") {
  225. const btn = document.querySelector("#bathroomlightoff")
  226. btn.checked = true
  227. } else {
  228. console.log("unknown msg " + msg)
  229. }
  230. })
  231. // set new bathroom light state
  232. for (const btn of btnsBath) {
  233. btn.addEventListener('change', function (e) {
  234. if (this.checked) {
  235. if (this == document.querySelector("#bathroomlightauto")) {
  236. setTopic("bathroom/force_light", "none")
  237. } else if (this == document.querySelector("#bathroomlightbig")) {
  238. setTopic("bathroom/force_light", "big")
  239. } else if (this == document.querySelector("#bathroomlightsmall")) {
  240. setTopic("bathroom/force_light", "small")
  241. } else if (this == document.querySelector("#bathroomlightoff")) {
  242. setTopic("bathroom/force_light", "off")
  243. } else {
  244. console.log("unknown btn value " + this.value)
  245. }
  246. }
  247. })
  248. }
  249. // handle bathroom sensors
  250. subscribeTopic("bathroom/temperature", function (msg) {
  251. const txt = document.querySelector("#bathtemp")
  252. txt.innerHTML = "<p>" + msg + " °C</p>"
  253. })
  254. subscribeTopic("bathroom/humidity", function (msg) {
  255. const txt = document.querySelector("#bathhumid")
  256. txt.innerHTML = "<p>" + msg + " %</p>"
  257. })
  258. subscribeTopic("bathroom/pressure", function (msg) {
  259. const txt = document.querySelector("#bathpress")
  260. txt.innerHTML = "<p>" + msg + " Pa</p>"
  261. })
  262. subscribeTopic("bathroom/tvoc", function (msg) {
  263. const txt = document.querySelector("#bathtvoc")
  264. txt.innerHTML = "<p>" + msg + "</p>"
  265. })
  266. subscribeTopic("bathroom/eco2", function (msg) {
  267. const txt = document.querySelector("#batheco2")
  268. txt.innerHTML = "<p>" + msg + " ppm</p>"
  269. })
  270. </script>
  271. </body>
  272. </html>