easycoder 250826.1__tar.gz → 250915.1__tar.gz

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.

Potentially problematic release.


This version of easycoder might be problematic. Click here for more details.

Files changed (202) hide show
  1. {easycoder-250826.1 → easycoder-250915.1}/PKG-INFO +1 -1
  2. easycoder-250915.1/RBRNow copy/README.md +89 -0
  3. easycoder-250915.1/RBRNow copy/ap.py +24 -0
  4. easycoder-250915.1/RBRNow copy/blescan.py +31 -0
  5. easycoder-250915.1/RBRNow copy/boot.py +5 -0
  6. easycoder-250915.1/RBRNow copy/config.py +117 -0
  7. easycoder-250915.1/RBRNow copy/dht22.py +45 -0
  8. easycoder-250915.1/RBRNow copy/espcomms.py +76 -0
  9. easycoder-250915.1/RBRNow copy/files.py +46 -0
  10. easycoder-250915.1/RBRNow copy/files.txt +13 -0
  11. easycoder-250915.1/RBRNow copy/files2.txt +23 -0
  12. easycoder-250915.1/RBRNow copy/handler.py +124 -0
  13. easycoder-250915.1/RBRNow copy/lib/aioble/__init__.py +32 -0
  14. easycoder-250915.1/RBRNow copy/lib/aioble/central.py +307 -0
  15. easycoder-250915.1/RBRNow copy/lib/aioble/client.py +456 -0
  16. easycoder-250915.1/RBRNow copy/lib/aioble/core.py +78 -0
  17. easycoder-250915.1/RBRNow copy/lib/aioble/device.py +304 -0
  18. easycoder-250915.1/RBRNow copy/lib/aioble/files.txt +9 -0
  19. easycoder-250915.1/RBRNow copy/lib/aioble/l2cap.py +214 -0
  20. easycoder-250915.1/RBRNow copy/lib/aioble/peripheral.py +178 -0
  21. easycoder-250915.1/RBRNow copy/lib/aioble/security.py +178 -0
  22. easycoder-250915.1/RBRNow copy/lib/aioble/server.py +340 -0
  23. easycoder-250915.1/RBRNow copy/main.py +79 -0
  24. easycoder-250915.1/RBRNow copy/pin.py +29 -0
  25. easycoder-250915.1/RBRNow copy/server.py +80 -0
  26. easycoder-250915.1/RBRNow copy/sht41.py +27 -0
  27. easycoder-250915.1/RBRNow copy/sta.py +31 -0
  28. {easycoder-250826.1 → easycoder-250915.1}/easycoder/__init__.py +1 -1
  29. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_core.py +3 -2
  30. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_pyside.py +8 -5
  31. easycoder-250915.1/scripts/test.ecs +11 -0
  32. easycoder-250915.1/test.py +5 -0
  33. easycoder-250826.1/scripts/test.ecs +0 -3
  34. easycoder-250826.1/test.py +0 -5
  35. {easycoder-250826.1 → easycoder-250915.1}/.gitignore +0 -0
  36. {easycoder-250826.1 → easycoder-250915.1}/LICENSE +0 -0
  37. {easycoder-250826.1 → easycoder-250915.1}/README.md +0 -0
  38. {easycoder-250826.1 → easycoder-250915.1}/backdrop.jpg +0 -0
  39. {easycoder-250826.1 → easycoder-250915.1}/doc/README.md +0 -0
  40. {easycoder-250826.1 → easycoder-250915.1}/doc/core/README.md +0 -0
  41. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/boolean.md +0 -0
  42. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/empty.md +0 -0
  43. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/ends.md +0 -0
  44. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/even.md +0 -0
  45. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/exists.md +0 -0
  46. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/greater.md +0 -0
  47. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/hasProperty.md +0 -0
  48. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/includes.md +0 -0
  49. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/is.md +0 -0
  50. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/less.md +0 -0
  51. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/list.md +0 -0
  52. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/none.md +0 -0
  53. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/not.md +0 -0
  54. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/numeric.md +0 -0
  55. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/object.md +0 -0
  56. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/odd.md +0 -0
  57. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/starts.md +0 -0
  58. {easycoder-250826.1 → easycoder-250915.1}/doc/core/conditions/string.md +0 -0
  59. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/add.md +0 -0
  60. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/append.md +0 -0
  61. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/assert.md +0 -0
  62. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/begin.md +0 -0
  63. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/clear.md +0 -0
  64. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/close.md +0 -0
  65. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/create.md +0 -0
  66. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/debug.md +0 -0
  67. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/decrement.md +0 -0
  68. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/delete.md +0 -0
  69. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/divide.md +0 -0
  70. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/exit.md +0 -0
  71. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/file.md +0 -0
  72. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/fork.md +0 -0
  73. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/get.md +0 -0
  74. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/go.md +0 -0
  75. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/gosub.md +0 -0
  76. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/if.md +0 -0
  77. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/import.md +0 -0
  78. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/increment.md +0 -0
  79. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/index.md +0 -0
  80. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/init.md +0 -0
  81. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/input.md +0 -0
  82. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/load.md +0 -0
  83. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/lock.md +0 -0
  84. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/log.md +0 -0
  85. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/module.md +0 -0
  86. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/multiply.md +0 -0
  87. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/negate.md +0 -0
  88. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/on.md +0 -0
  89. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/open.md +0 -0
  90. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/pop.md +0 -0
  91. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/post.md +0 -0
  92. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/print.md +0 -0
  93. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/push.md +0 -0
  94. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/put.md +0 -0
  95. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/read.md +0 -0
  96. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/release.md +0 -0
  97. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/replace.md +0 -0
  98. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/return.md +0 -0
  99. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/run.md +0 -0
  100. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/save.md +0 -0
  101. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/script.md +0 -0
  102. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/send.md +0 -0
  103. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/set.md +0 -0
  104. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/shuffle.md +0 -0
  105. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/split.md +0 -0
  106. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/stack.md +0 -0
  107. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/stop.md +0 -0
  108. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/system.md +0 -0
  109. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/take.md +0 -0
  110. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/toggle.md +0 -0
  111. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/truncate.md +0 -0
  112. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/unlock.md +0 -0
  113. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/use.md +0 -0
  114. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/variable.md +0 -0
  115. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/wait.md +0 -0
  116. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/while.md +0 -0
  117. {easycoder-250826.1 → easycoder-250915.1}/doc/core/keywords/write.md +0 -0
  118. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/arg.md +0 -0
  119. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/args.md +0 -0
  120. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/cos.md +0 -0
  121. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/datime.md +0 -0
  122. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/decode.md +0 -0
  123. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/element.md +0 -0
  124. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/elements.md +0 -0
  125. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/empty.md +0 -0
  126. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/encode.md +0 -0
  127. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/error.md +0 -0
  128. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/files.md +0 -0
  129. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/float.md +0 -0
  130. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/from.md +0 -0
  131. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/hash.md +0 -0
  132. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/index.md +0 -0
  133. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/integer.md +0 -0
  134. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/json.md +0 -0
  135. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/keys.md +0 -0
  136. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/left.md +0 -0
  137. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/length.md +0 -0
  138. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/lowercase.md +0 -0
  139. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/memory.md +0 -0
  140. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/modification.md +0 -0
  141. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/modulo.md +0 -0
  142. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/newline.md +0 -0
  143. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/now.md +0 -0
  144. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/position.md +0 -0
  145. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/property.md +0 -0
  146. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/random.md +0 -0
  147. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/right.md +0 -0
  148. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/sin.md +0 -0
  149. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/stringify.md +0 -0
  150. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/tab.md +0 -0
  151. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/tan.md +0 -0
  152. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/timestamp.md +0 -0
  153. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/today.md +0 -0
  154. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/trim.md +0 -0
  155. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/type.md +0 -0
  156. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/uppercase.md +0 -0
  157. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/value.md +0 -0
  158. {easycoder-250826.1 → easycoder-250915.1}/doc/core/values/weekday.md +0 -0
  159. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/README.md +0 -0
  160. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/attach.md +0 -0
  161. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/close.md +0 -0
  162. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/create.md +0 -0
  163. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/ellipse.md +0 -0
  164. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/image.md +0 -0
  165. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/move.md +0 -0
  166. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/on.md +0 -0
  167. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/rectangle.md +0 -0
  168. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/render.md +0 -0
  169. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/run.md +0 -0
  170. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/set.md +0 -0
  171. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/keywords/text.md +0 -0
  172. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/values/attribute.md +0 -0
  173. {easycoder-250826.1 → easycoder-250915.1}/doc/graphics/values/window.md +0 -0
  174. {easycoder-250826.1 → easycoder-250915.1}/easycoder/close.png +0 -0
  175. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_border.py +0 -0
  176. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_classes.py +0 -0
  177. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_compiler.py +0 -0
  178. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_condition.py +0 -0
  179. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_handler.py +0 -0
  180. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_keyboard.py +0 -0
  181. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_program.py +0 -0
  182. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_timestamp.py +0 -0
  183. {easycoder-250826.1 → easycoder-250915.1}/easycoder/ec_value.py +0 -0
  184. {easycoder-250826.1 → easycoder-250915.1}/easycoder/tick.png +0 -0
  185. {easycoder-250826.1 → easycoder-250915.1}/images/Semoigo Dawn.jpg +0 -0
  186. {easycoder-250826.1 → easycoder-250915.1}/json/graphics-demo.json +0 -0
  187. {easycoder-250826.1 → easycoder-250915.1}/plugins/ec_keyboard.py +0 -0
  188. {easycoder-250826.1 → easycoder-250915.1}/plugins/ec_p100.py +0 -0
  189. {easycoder-250826.1 → easycoder-250915.1}/plugins/ec_pyside6.py +0 -0
  190. {easycoder-250826.1 → easycoder-250915.1}/plugins/points.py +0 -0
  191. {easycoder-250826.1 → easycoder-250915.1}/plugins/sql.py +0 -0
  192. {easycoder-250826.1 → easycoder-250915.1}/pyproject.toml +0 -0
  193. {easycoder-250826.1 → easycoder-250915.1}/scripts/benchmark.ecs +0 -0
  194. {easycoder-250826.1 → easycoder-250915.1}/scripts/config.ecg +0 -0
  195. {easycoder-250826.1 → easycoder-250915.1}/scripts/ec_keyboard.py +0 -0
  196. {easycoder-250826.1 → easycoder-250915.1}/scripts/fizzbuzz.ecs +0 -0
  197. {easycoder-250826.1 → easycoder-250915.1}/scripts/hello.ecs +0 -0
  198. {easycoder-250826.1 → easycoder-250915.1}/scripts/points.ecs +0 -0
  199. {easycoder-250826.1 → easycoder-250915.1}/scripts/tests.ecs +0 -0
  200. {easycoder-250826.1 → easycoder-250915.1}/testrc.py +0 -0
  201. {easycoder-250826.1 → easycoder-250915.1}/testsql.py +0 -0
  202. {easycoder-250826.1 → easycoder-250915.1}/testui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easycoder
3
- Version: 250826.1
3
+ Version: 250915.1
4
4
  Summary: Rapid scripting in English
5
5
  Keywords: compiler,scripting,prototyping,programming,coding,python,low code,hypertalk,computer language,learn to code
6
6
  Author-email: Graham Trott <gtanyware@gmail.com>
@@ -0,0 +1,89 @@
1
+ # RBR-Now #
2
+
3
+ **RBR-Now** is a networked control system based on the ESP32 microcontroller, designed for ESP32-based devices such as a radiator TRV or a DHT22 thermometer, and uses ESP-Now to implement chained networking allowing any device to run its own subnetwork to overcome range and capacity issues.
4
+
5
+ All the software in RBR-Now is MicroPython (uPython) and every device in the RBR-Now network is equipped with the same set of files. The behaviour of each device is determined by a small JSON configuration file.
6
+
7
+ The system can be deployed simply by loading devices with the core file set and a suitable configuration file, but is best managed by the RBRConf program; a Python GUI application for Linux computers. (It cannot run on Windows because it relies on networking commands that are not available on that OS.) RBRConf allows any device on the network to be interrogated, controlled and have its firmware updated remotely without any need to remove it from service.
8
+
9
+ An RBR-Now network comprises a hub (master) device and a number of slaves. A control system using RBR-Now connects solely to the hub device and controls the slaves through it. This documentation will first describe the slave functionality as this is the simpler device..
10
+
11
+ ## The RBR-Now Slave device
12
+
13
+ An ESP-Now slave unit only receives; it doesn't initiate messages as it has no information about where to send any. To send a message to one we have to know its MAC address. This can be found by setting up the device as a wifi hotspot and arrange for the SSID to include the MAC address. To make things simpler, the RBR-Now system automates this process as follows.
14
+
15
+ When the device starts it looks for its configuration file, but this will not initially exist. So it drops into slave mode and creates a hotspot whose SSID is based on its MAC address but with a specific RBR prefix. When the configuration program is told to scan for slave devices it looks for this prefix and offers a list of all devices that match. The user selects one (there usually is only one, as I'll explain shortly) and the program notes the relevant MAC address. The new device is added to the list in the Slaves panel as an untitled device. When the user clicks that item, all relevant details for the device can be added in a panel on the UI screen. These details comprise the name by which the device will be known (usually the room name) and pin numbers for the onboard LED, the relay and the thermometer (if either of these are present). With this information, a JSON structure is created and sent to the device as its config file, so that next time it starts up it can configure itself appropriately.
16
+
17
+ The hotspot is basically open (otherwise the device couldn't be connected) so this is an obvious vulnerability. To overcome this, the hotspot is disabled after 2 minutes. (It cannot be removed entirely because this would prevent ESP-Now from working, so it's given an SSID comprising a single dash and a randomly generated numeric password, making it virtually impossible to get into and rendering the MAC address inaccessible.) So by the time a second device is to be configured, existing ones are unlikely to appear in a search.
18
+
19
+ Apart from initial configuration, all comms with a slave is done using ESP-Now, which only requires the MAC address to be known. The device indicates which mode it's in by flashing its onboard LED on-off with a 1-second cycle for the initial 2 minutes, then a brief flash every 5 seconds after that.
20
+
21
+ The slave code waits for messages to arrive and acts on them. Messages will only arrive from the system controller, although in some cases they may be relayed via another slave device. Each message is acknowledged and any requested data is passed back. This includes such things as the relay state, the up time (seconds from startup) and thermometer readings.
22
+
23
+ The device also runs concurrent code that monitors BLE transmissions, looking for the signature of a Mijia thermometer. Every time one is seen, its advertising packet is read and the relevant data extracted. This will be returned as part of the response to the next message from the controller. Queuing is not implemented, so if several Mijias are detected at the same time, only one of them will be captured on this occasion.
24
+
25
+ Among the commands that can be sent are some that permit any file on the device to be updated. (This does not include MicroPython itself.) The configurator has the ability to update one or all the files on a device.
26
+
27
+ ## The RBR-Now Master device
28
+
29
+ The Master (or Hub) device is the gateway between the system controller and the network of RBR-Now devices. It can itself be one of those devices, or it can be a separate module placed centrally where it can easily be seen by all the others, which are all Slave devices as described above. The Master has no knowledge of the other devices, nor of the system controller; it just responds to requests. Most of the functionality of the Slave is also shared by the Master.
30
+
31
+ The Master must be the first device set up by the configurator, which treats it specially. As with the slaves, the device initially has no configuration file, so as above it publishes a hotspot and waits for a connection. The configurator creates a JSON configuration file similar to that for a slave but additionally containing the SSID and password of the home network router and identifying the device as the master, then sends the file to the device and asks it to reset itself. When it restarts, the device will connect to the router and will get an IP address. The configurator waits for 10 seconds to allow this to complete, then sends another message to the hotspot requesting the IP address. From then on, all communication is done through this IP address. As with the slave, after 2 minutes the hotspot is disabled and the LED cycle changes, this time to a double flash every 5 seconds.
32
+
33
+ ## System messaging
34
+
35
+ All messages from the system controller to an RBR-Now device are sent to the Master/Hub device at its IP address on the home network. Messages are prefixed by the MAC address of the target device, as recorded by the configurator when the device was added to the system.
36
+
37
+ When the Master device receives a message it checks to see who the message is addressed to. If it's the master device itself the message is handled and a reply sent. If it's any other address the message is forwarded to that device via the ESP-Now network,and the reply from that device is returned to the system controller.
38
+
39
+ In some cases a device is too far away from the hub to receive messages reliably. In such cases the device can piggy-back off another, closer device. Here, the MAC address given has a special prefix and identifies the path to the target device, going via as many intermediates as necessary. The path is part of the configuration for a device; in most cases it is empty.
40
+
41
+ The messages understood by devices are as follows:
42
+
43
+ ### on
44
+
45
+ Turns on the device relay. It returns 'OK' then the up time of the device (in seconds) and the most recent Mijia reading it has received (if any).
46
+
47
+ ### off
48
+
49
+ Turns off the device relay. The reply is the same as for **on**.
50
+
51
+ ### relay
52
+
53
+ Returns the current state of the relay.
54
+
55
+ ### temp
56
+
57
+ Returns the current temperature, if the device is a thermometer.
58
+
59
+ ### reset
60
+
61
+ Causes the device to reset itself.
62
+
63
+ ### ipaddr
64
+
65
+ Returns the IP address of the device (master only).
66
+
67
+ ### pause
68
+
69
+ Requests the device to pause all operations that may interfere with actions such as updating files.
70
+
71
+ ### resume
72
+
73
+ Requests the device to resume operations as normal.
74
+
75
+ ### part
76
+
77
+ Signals that the attached data (up to 100 characters) is part of a file to be saved by the device. The file content is sent in hex format, 100 bytes at a time, each with a part number starting with zero, and the device saves each part as it arrives. Part numbers must be in the correct sequence or an error occurs.
78
+
79
+ ### save
80
+
81
+ Saves the accumulated parts into the file whose name is given. The device saves the file then reads it back to check it matches what was received by the **part** requests.
82
+
83
+ ### delete
84
+
85
+ Deletes the file whose full path is given.
86
+
87
+ ## mkdir
88
+
89
+ Creates a directory with the name given.
@@ -0,0 +1,24 @@
1
+ import network,binascii,asyncio,random
2
+
3
+ class AP():
4
+
5
+ def __init__(self,config):
6
+ self.config=config
7
+ config.setAP(self)
8
+ ap=network.WLAN(network.AP_IF)
9
+ ap.active(True)
10
+ self.ap=ap
11
+ mac=binascii.hexlify(self.ap.config('mac')).decode()
12
+ self.config.setMAC(mac)
13
+ self.ssid=f'RBR-Now-{mac}'
14
+ ap.config(essid=self.ssid,password='00000000')
15
+ ap.config(channel=config.getChannel())
16
+ ap.ifconfig(('192.168.9.1','255.255.255.0','192.168.9.1','8.8.8.8'))
17
+ print(mac,config.getName())
18
+
19
+ def stop(self):
20
+ password=str(random.randrange(100000,999999))
21
+ print(password)
22
+ self.ap.config(essid='-',password=password)
23
+
24
+ def getChannel(self): return self.ap.config('channel')
@@ -0,0 +1,31 @@
1
+ import asyncio
2
+ import aioble
3
+ from binascii import hexlify
4
+
5
+ class BLEScan():
6
+
7
+ def __init__(self):
8
+ self.values=''
9
+
10
+ async def scan(self):
11
+ print("Scanning for BLE devices...")
12
+ while True:
13
+ async with aioble.scan(duration_ms=20000) as scanner:
14
+ async for result in scanner:
15
+ addr = result.device.addr_hex()
16
+ if addr.find('a4:c1:38:') == 0:
17
+ rssi = result.rssi
18
+ data = hexlify(result.adv_data).decode()
19
+ if len(data) > 28:
20
+ temp = int(data[20:24], 16)
21
+ hum = int(data[24:26], 16)
22
+ batt = int(data[26:28], 16)
23
+ print(f'{addr[9:]} {rssi} {temp} {hum} {batt}')
24
+ self.values = f'{addr[9:]};{rssi};{temp};{hum};{batt}'
25
+ # else: print(f'{addr} - no')
26
+ await asyncio.sleep(0.1)
27
+
28
+ def getValues(self):
29
+ values=self.values
30
+ self.values=''
31
+ return values
@@ -0,0 +1,5 @@
1
+ # This file is executed on every boot (including wake-boot from deepsleep)
2
+ #import esp
3
+ #esp.osdebug(None)
4
+ #import webrepl
5
+ #webrepl.start()
@@ -0,0 +1,117 @@
1
+ import json,asyncio,time
2
+ from files import readFile,writeFile,fileExists
3
+ from pin import PIN
4
+ from server import Server
5
+ from dht22 import DHT22
6
+ from blescan import BLEScan
7
+
8
+ class Config():
9
+
10
+ def __init__(self):
11
+ if fileExists('config.json'):
12
+ self.config=json.loads(readFile('config.json'))
13
+ else:
14
+ self.config={}
15
+ self.config['name']='(none)'
16
+ self.config['master']=False
17
+ self.config['channel']=1
18
+ self.config['path']=''
19
+ self.config['pins']={}
20
+ pin={}
21
+ pin['pin']=''
22
+ pin['invert']=False
23
+ self.config['pins']['led']=pin
24
+ pin={}
25
+ pin['pin']=''
26
+ pin['invert']=False
27
+ self.config['pins']['relay']=pin
28
+ pin={}
29
+ pin['pin']=''
30
+ self.config['pins']['dht22']=pin
31
+ writeFile('config.json',json.dumps(self.config))
32
+ pin,invert=self.getPinInfo('led')
33
+ self.led=PIN(self,pin,invert)
34
+ pin,invert=self.getPinInfo('relay')
35
+ self.relay=PIN(self,pin,invert)
36
+ pin,_=self.getPinInfo('dht22')
37
+ if pin=='': self.dht22=None
38
+ else:
39
+ self.dht22=DHT22(pin)
40
+ asyncio.create_task(self.dht22.measure())
41
+ print('path:',self.config['path'])
42
+ self.ipaddr=None
43
+ self.uptime=0
44
+ self.resetRequested=False
45
+ self.server=Server(self)
46
+ asyncio.create_task(self.runWatchdog())
47
+ self.bleScan=BLEScan()
48
+
49
+ async def respond(self,response,writer):
50
+ await self.server.respond(response,writer)
51
+ async def sendDefaultResponse(self,writer):
52
+ await self.server.sendDefaultResponse(writer)
53
+ async def handleClient(self,reader,writer):
54
+ await self.server.handleClient(reader,writer)
55
+
56
+ async def send(self,peer,espmsg): return await self.espComms.send(peer,espmsg)
57
+
58
+ def reset(self):
59
+ print('Reset requested')
60
+ self.resetRequested=True
61
+
62
+ def pause(self):
63
+ if self.dht22!=None: self.dht22.pause()
64
+
65
+ def resume(self):
66
+ if self.dht22!=None: self.dht22.resume()
67
+
68
+ def setAP(self,ap): self.ap=ap
69
+ def setSTA(self,sta): self.sta=sta
70
+ def setMAC(self,mac): self.mac=mac
71
+ def setChannel(self,channel):
72
+ self.config['channel']=channel
73
+ writeFile('config.json',json.dumps(self.config))
74
+ def setIPAddr(self,ipaddr): self.ipaddr=ipaddr
75
+ def setHandler(self,handler): self.handler=handler
76
+ def setESPComms(self,espComms): self.espComms=espComms
77
+ def addUptime(self,t): self.uptime+=t
78
+
79
+ def isMaster(self): return self.config['master']
80
+ def isESP8266(self): return False
81
+ def getDevice(self): return self.config['device']
82
+ def getName(self): return self.config['name']
83
+ def getSSID(self): return self.config['hostssid']
84
+ def getPassword(self): return self.config['hostpass']
85
+ def getMAC(self): return self.mac
86
+ def getAP(self): return self.ap
87
+ def getSTA(self): return self.sta
88
+ def stopAP(self): self.ap.stop()
89
+ def startServer(self): self.server.startup()
90
+ def getIPAddr(self): return self.ipaddr
91
+ def getChannel(self): return self.ap.getChannel()
92
+ def getHandler(self): return self.handler
93
+ def getESPComms(self): return self.espComms
94
+ def getBLEScan(self): return self.bleScan
95
+ def getRBRNow(self): return self.rbrNow
96
+ def getPinInfo(self,name):
97
+ pin=self.config['pins'][name]
98
+ print(name,pin)
99
+ if 'invert' in pin: invert=pin['invert']
100
+ else: invert=False
101
+ return pin['pin'],invert
102
+ def getLED(self): return self.led
103
+ def getRelay(self): return self.relay
104
+ def getUptime(self): return int(round(self.uptime))
105
+ def getTemperature(self): return self.dht22.getTemperature()
106
+
107
+ async def runWatchdog(self):
108
+ while True:
109
+ self.active=False
110
+ await asyncio.sleep(180)
111
+ if not self.active:
112
+ print('No activity - reset')
113
+ asyncio.get_event_loop().stop()
114
+ pass
115
+
116
+ def kickWatchdog(self):
117
+ self.active=True
@@ -0,0 +1,45 @@
1
+ import asyncio,dht
2
+ from machine import Pin,reset
3
+
4
+ class DHT22():
5
+
6
+ def __init__(self,sensorpin,verbose=False):
7
+ if verbose:
8
+ print('Initialise sensor on pin',sensorpin)
9
+ if sensorpin is '': self.sensor=None
10
+ else: self.sensor=dht.DHT22(Pin(int(sensorpin)))
11
+ self.verbose=verbose
12
+ self.temp=0
13
+ self.errors=0
14
+ self.paused=False
15
+
16
+ async def measure(self):
17
+ if self.sensor==None: return
18
+ msg=None
19
+ print('Run the temperature sensor')
20
+ while True:
21
+ if not self.paused:
22
+ try:
23
+ self.sensor.measure()
24
+ temperature=self.sensor.temperature()
25
+ if temperature>50: temperature=0
26
+ self.temp=round(temperature*10)
27
+ if self.verbose:
28
+ print('Temperature:',temperature)
29
+ self.errors=0
30
+ except OSError as e:
31
+ if self.verbose:
32
+ msg=f'Failed to read sensor: {str(e)}'
33
+ print(msg)
34
+ self.errors+=1
35
+ if self.errors>50: reset()
36
+ await asyncio.sleep(5)
37
+
38
+ def pause(self):
39
+ self.paused=True
40
+
41
+ def resume(self):
42
+ self.paused=False
43
+
44
+ def getTemperature(self):
45
+ return self.temp
@@ -0,0 +1,76 @@
1
+ import asyncio
2
+ from binascii import hexlify,unhexlify
3
+ from espnow import ESPNow as E
4
+
5
+ class ESPComms():
6
+
7
+ def __init__(self,config):
8
+ self.config=config
9
+ config.setESPComms(self)
10
+ E().active(True)
11
+ self.peers=[]
12
+ print('ESP-Now initialised')
13
+
14
+ def checkPeer(self,peer):
15
+ if not peer in self.peers:
16
+ self.peers.append(peer)
17
+ E().add_peer(peer)
18
+
19
+ async def send(self,mac,espmsg):
20
+ peer=unhexlify(mac.encode())
21
+ # Flush any incoming messages
22
+ while E().any(): _,_ = E().irecv()
23
+ self.checkPeer(peer)
24
+ try:
25
+ # print(f'Send {espmsg[0:20]}... to {mac}')
26
+ result=E().send(peer,espmsg)
27
+ # print(f'Result: {result}')
28
+ if result:
29
+ counter=50
30
+ while counter>0:
31
+ if E().any():
32
+ mac,response = E().irecv()
33
+ if response:
34
+ # print(f"Received response: {response.decode()}")
35
+ result=response.decode()
36
+ break
37
+ await asyncio.sleep(.1)
38
+ counter-=1
39
+ if counter==0:
40
+ result='Response timeout'
41
+ else: result='Fail (no result)'
42
+ except Exception as e:
43
+ print(e)
44
+ result=f'Fail ({e})'
45
+ return result
46
+
47
+ async def receive(self):
48
+ print('Starting ESPNow receiver on channel',self.config.getChannel())
49
+ self.waiting=False
50
+ while True:
51
+ if E().any():
52
+ mac,msg=E().recv()
53
+ sender=hexlify(mac).decode()
54
+ msg=msg.decode()
55
+ # print(f'Message from {sender}: {msg[0:30]}...')
56
+ if msg[0]=='!':
57
+ # It's a message to be relayed
58
+ comma=msg.index(',')
59
+ slave=msg[1:comma]
60
+ msg=msg[comma+1:]
61
+ # print(f'Slave: {slave}, msg: {msg}')
62
+ response=await self.send(slave,msg)
63
+ else:
64
+ # It's a message for me
65
+ response=self.config.getHandler().handleMessage(msg)
66
+ response=f'{response} {self.getRSS(sender)}'
67
+ print(f'Response to {sender}: {response}')
68
+ self.checkPeer(mac)
69
+ E().send(mac,response)
70
+ await asyncio.sleep(.1)
71
+ self.config.kickWatchdog()
72
+
73
+ def getRSS(self,mac):
74
+ peer=unhexlify(mac.encode())
75
+ try: return E().peers_table[peer][0]
76
+ except: return 0
@@ -0,0 +1,46 @@
1
+ import os
2
+
3
+ def fileExists(filename):
4
+ try:
5
+ os.stat(filename)
6
+ return True
7
+ except OSError:
8
+ return False
9
+
10
+ def readFile(name):
11
+ try:
12
+ f=open(name,'r')
13
+ value=f.read()
14
+ f.close()
15
+ except:
16
+ value=None
17
+ return value
18
+
19
+ def writeFile(name,text):
20
+ f=open(name,'w')
21
+ f.write(text)
22
+ f.close()
23
+
24
+ def appendFile(name,text):
25
+ f=open(name,'a')
26
+ f.write(text)
27
+ f.close()
28
+
29
+ def createDirectory(path):
30
+ try:
31
+ os.mkdir(path)
32
+ except OSError as e:
33
+ if e.args[0] != 17: return False
34
+ return True
35
+
36
+ def clearFile(name):
37
+ open(name,'w').close()
38
+
39
+ def deleteFile(file):
40
+ try:
41
+ os.remove(file)
42
+ return True
43
+ except: return False
44
+
45
+ def renameFile(oldName,newName):
46
+ os.rename(oldName,newName)
@@ -0,0 +1,13 @@
1
+ ap.py
2
+ blescan.py
3
+ boot.py
4
+ config.py
5
+ dht22.py
6
+ espcomms.py
7
+ files.py
8
+ handler.py
9
+ main.py
10
+ pin.py
11
+ server.py
12
+ sta.py
13
+
@@ -0,0 +1,23 @@
1
+ lib/
2
+ lib/aioble/
3
+ lib/aioble/central.py
4
+ lib/aioble/client.py
5
+ lib/aioble/core.py
6
+ lib/aioble/device.py
7
+ lib/aioble/__init__.py
8
+ lib/aioble/l2cap.py
9
+ lib/aioble/peripheral.py
10
+ lib/aioble/security.py
11
+ lib/aioble/server.py
12
+ ap.py
13
+ blescan.py
14
+ boot.py
15
+ config.py
16
+ dht22.py
17
+ espcomms.py
18
+ files.py
19
+ handler.py
20
+ main.py
21
+ pin.py
22
+ server.py
23
+ sta.py
@@ -0,0 +1,124 @@
1
+ import asyncio,machine
2
+ from binascii import unhexlify
3
+ from files import readFile,writeFile,renameFile,deleteFile,createDirectory
4
+
5
+ class Handler():
6
+
7
+ def __init__(self,config):
8
+ self.config=config
9
+ config.setHandler(self)
10
+ self.relay=config.getRelay()
11
+
12
+ def checkFile(self, buf, file):
13
+ try:
14
+ with open(file, 'r') as f:
15
+ i = 0 # index in lst
16
+ pos = 0 # position in current list item
17
+ while True:
18
+ c = f.read(1)
19
+ if not c:
20
+ # End of file: check if we've also finished the list
21
+ while i < len(buf) and pos == len(buf[i]):
22
+ i += 1
23
+ pos = 0
24
+ return i == len(buf)
25
+ if i >= len(buf) or pos >= len(buf[i]) or c != buf[i][pos]:
26
+ return False
27
+ pos += 1
28
+ if pos == len(buf[i]):
29
+ i += 1
30
+ pos = 0
31
+ except OSError:
32
+ return False
33
+
34
+ def handleMessage(self,msg):
35
+ # print('Message:',msg)
36
+ response=f'OK {self.config.getUptime()} :{self.config.getBLEScan().getValues()}'
37
+ if msg == 'uptime':
38
+ pass
39
+ elif msg == 'on':
40
+ response=f'{response} ON' if self.relay.on() else 'No relay'
41
+ elif msg == 'off':
42
+ response=f'{response} OFF' if self.relay.off() else 'No relay'
43
+ elif msg == 'relay':
44
+ try:
45
+ response=f'OK {self.relay.getState()}'
46
+ except:
47
+ response='No relay'
48
+ elif msg=='reset':
49
+ self.config.reset()
50
+ response='OK'
51
+ elif msg == 'ipaddr':
52
+ response=f'OK {self.config.getIPAddr()}'
53
+ elif msg=='channel':
54
+ response=f'OK {self.config.getChannel()}'
55
+ elif msg[0:8]=='channel=':
56
+ channel=msg[8:]
57
+ self.config.setChannel(channel)
58
+ response=f'OK {channel}'
59
+ elif msg=='temp':
60
+ response=f'OK {self.config.getTemperature()}'
61
+ elif msg=='pause':
62
+ self.config.pause()
63
+ response=f'OK paused'
64
+ elif msg=='resume':
65
+ self.config.resume()
66
+ response=f'OK resumed'
67
+ elif msg[0:6]=='delete':
68
+ file=msg[7:]
69
+ response='OK' if deleteFile(file) else 'Fail'
70
+ elif msg[0:4]=='part':
71
+ # Format is part:{n},text:{text}
72
+ part=None
73
+ text=None
74
+ items=msg.split(',')
75
+ for item in items:
76
+ item=item.split(':')
77
+ label=item[0]
78
+ value=item[1]
79
+ if label=='part': part=int(value)
80
+ elif label=='text': text=value
81
+ if part!=None and text!=None:
82
+ text=text.encode('utf-8')
83
+ text=unhexlify(text)
84
+ text=text.decode('utf-8')
85
+ if part==0:
86
+ self.buffer=[]
87
+ self.pp=0
88
+ self.saveError=False
89
+ else:
90
+ if self.saveError:
91
+ return 'Error'
92
+ else:
93
+ if part==self.pp+1:
94
+ self.pp+=1
95
+ else:
96
+ self.saveError=True
97
+ print('Sequence error')
98
+ return 'Sequence error'
99
+ self.buffer.append(text)
100
+ response=f'{part} {str(len(text))}'
101
+ elif msg[0:4]=='save':
102
+ if len(self.buffer[0])>0:
103
+ file=msg[5:]
104
+ print(f'Save {file}')
105
+ size=0
106
+ f = open(file,'w')
107
+ for n in range(0, len(self.buffer)):
108
+ f.write(self.buffer[n])
109
+ size+= len(self.buffer[n])
110
+ f.close()
111
+ # Check the file against the buffer
112
+ if self.checkFile(self.buffer, file): response=str(size)
113
+ else: response='Bad save'
114
+ else: response='No update'
115
+ text=None
116
+ elif msg[0:5]=='mkdir':
117
+ path=msg[6:]
118
+ print(f'mkdir {path}')
119
+ response='OK' if createDirectory(path) else 'Fail'
120
+ else: response=f'Unknown message: {msg}'
121
+ # print('Handler:',response)
122
+ return response
123
+
124
+
@@ -0,0 +1,32 @@
1
+ # MicroPython aioble module
2
+ # MIT license; Copyright (c) 2021 Jim Mussared
3
+
4
+ from micropython import const
5
+
6
+ from .device import Device, DeviceDisconnectedError
7
+ from .core import log_info, log_warn, log_error, GattError, config, stop
8
+
9
+ try:
10
+ from .peripheral import advertise
11
+ except:
12
+ log_info("Peripheral support disabled")
13
+
14
+ try:
15
+ from .central import scan
16
+ except:
17
+ log_info("Central support disabled")
18
+
19
+ try:
20
+ from .server import (
21
+ Service,
22
+ Characteristic,
23
+ BufferedCharacteristic,
24
+ Descriptor,
25
+ register_services,
26
+ )
27
+ except:
28
+ log_info("GATT server support disabled")
29
+
30
+
31
+ ADDR_PUBLIC = const(0)
32
+ ADDR_RANDOM = const(1)