Browse Source

initial check in. mostly feature complete.

FloKra 4 years ago
commit
8846bd29cf
100 changed files with 11136 additions and 0 deletions
  1. 47 0
      Hardware/WiFiPCController-Hardware.txt
  2. 267 0
      Hardware/schematic/WiFiPCController/WiFiPCController-cache.lib
  3. 521 0
      Hardware/schematic/WiFiPCController/WiFiPCController.bak
  4. 33 0
      Hardware/schematic/WiFiPCController/WiFiPCController.pro
  5. 551 0
      Hardware/schematic/WiFiPCController/WiFiPCController.sch
  6. 24 0
      README-DE.md
  7. 14 0
      README.md
  8. 165 0
      Software/src/WiFiPCController/PersWiFiManager.cpp
  9. 52 0
      Software/src/WiFiPCController/PersWiFiManager.h
  10. 263 0
      Software/src/WiFiPCController/WiFiPCController.ino
  11. BIN
      Software/src/WiFiPCController/WiFiPCController.ino.d1_mini - Copy.bin
  12. BIN
      Software/src/WiFiPCController/WiFiPCController.ino.d1_mini.bin
  13. 139 0
      Software/src/WiFiPCController/commands.ino
  14. 380 0
      Software/src/WiFiPCController/config.ino
  15. 782 0
      Software/src/WiFiPCController/httpServer.ino
  16. 79 0
      Software/src/WiFiPCController/inputs.ino
  17. 59 0
      Software/src/WiFiPCController/lib/##AM2320/AM2320.cpp
  18. 17 0
      Software/src/WiFiPCController/lib/##AM2320/AM2320.h
  19. 29 0
      Software/src/WiFiPCController/lib/##AM2320/example/AM2320lib_test/AM2320lib_test.ino
  20. 4 0
      Software/src/WiFiPCController/lib/##AM2320/keywords.txt
  21. 13 0
      Software/src/WiFiPCController/lib/AS_BH1750/.gitignore
  22. 476 0
      Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750.cpp
  23. 183 0
      Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750.h
  24. 705 0
      Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750A.cpp
  25. 207 0
      Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750A.h
  26. 502 0
      Software/src/WiFiPCController/lib/AS_BH1750/LICENSE
  27. 37 0
      Software/src/WiFiPCController/lib/AS_BH1750/README.md
  28. 68 0
      Software/src/WiFiPCController/lib/AS_BH1750/examples/BH1750Simple/BH1750Simple.ino
  29. 96 0
      Software/src/WiFiPCController/lib/AS_BH1750/examples/LightMeter_LCD/LightMeter_LCD.ino
  30. 30 0
      Software/src/WiFiPCController/lib/AS_BH1750/keywords.txt
  31. BIN
      Software/src/WiFiPCController/lib/Arduino-DHT22-master.zip
  32. BIN
      Software/src/WiFiPCController/lib/DHT-sensor-library-master.zip
  33. BIN
      Software/src/WiFiPCController/lib/DHT.7z
  34. 46 0
      Software/src/WiFiPCController/lib/DHT/.github/ISSUE_TEMPLATE.md
  35. 26 0
      Software/src/WiFiPCController/lib/DHT/.github/PULL_REQUEST_TEMPLATE.md
  36. 259 0
      Software/src/WiFiPCController/lib/DHT/DHT.cpp
  37. 75 0
      Software/src/WiFiPCController/lib/DHT/DHT.h
  38. 179 0
      Software/src/WiFiPCController/lib/DHT/DHT_U.cpp
  39. 78 0
      Software/src/WiFiPCController/lib/DHT/DHT_U.h
  40. 15 0
      Software/src/WiFiPCController/lib/DHT/README.md
  41. 84 0
      Software/src/WiFiPCController/lib/DHT/examples/DHT_Unified_Sensor/DHT_Unified_Sensor.ino
  42. 69 0
      Software/src/WiFiPCController/lib/DHT/examples/DHTtester/DHTtester.ino
  43. 22 0
      Software/src/WiFiPCController/lib/DHT/keywords.txt
  44. 9 0
      Software/src/WiFiPCController/lib/DHT/library.properties
  45. BIN
      Software/src/WiFiPCController/lib/ESPmanager-master.zip
  46. 322 0
      Software/src/WiFiPCController/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp
  47. 126 0
      Software/src/WiFiPCController/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h
  48. 69 0
      Software/src/WiFiPCController/lib/LiquidCrystal_I2C/diff.txt
  49. 70 0
      Software/src/WiFiPCController/lib/LiquidCrystal_I2C/examples/CustomChars/CustomChars.pde
  50. 20 0
      Software/src/WiFiPCController/lib/LiquidCrystal_I2C/examples/HelloWorld/HelloWorld.pde
  51. 34 0
      Software/src/WiFiPCController/lib/LiquidCrystal_I2C/examples/SerialDisplay/SerialDisplay.pde
  52. 46 0
      Software/src/WiFiPCController/lib/LiquidCrystal_I2C/keywords.txt
  53. BIN
      Software/src/WiFiPCController/lib/esp8266-dht22-master.zip
  54. 21 0
      Software/src/WiFiPCController/lib/esp8266-dht22-master/LICENSE
  55. 74 0
      Software/src/WiFiPCController/lib/esp8266-dht22-master/README.md
  56. 289 0
      Software/src/WiFiPCController/lib/esp8266-dht22-master/dht22.c
  57. 54 0
      Software/src/WiFiPCController/lib/esp8266-dht22-master/dht22.h
  58. 1 0
      Software/src/WiFiPCController/lib/pubsubclient/.gitignore
  59. 7 0
      Software/src/WiFiPCController/lib/pubsubclient/.travis.yml
  60. 68 0
      Software/src/WiFiPCController/lib/pubsubclient/CHANGES.txt
  61. 20 0
      Software/src/WiFiPCController/lib/pubsubclient/LICENSE.txt
  62. 47 0
      Software/src/WiFiPCController/lib/pubsubclient/README.md
  63. 43 0
      Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_auth/mqtt_auth.ino
  64. 77 0
      Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_basic/mqtt_basic.ino
  65. 126 0
      Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_esp8266/mqtt_esp8266.ino
  66. 60 0
      Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino
  67. 67 0
      Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino
  68. 57 0
      Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_stream/mqtt_stream.ino
  69. 30 0
      Software/src/WiFiPCController/lib/pubsubclient/keywords.txt
  70. 17 0
      Software/src/WiFiPCController/lib/pubsubclient/library.json
  71. 9 0
      Software/src/WiFiPCController/lib/pubsubclient/library.properties
  72. 590 0
      Software/src/WiFiPCController/lib/pubsubclient/src/PubSubClient.cpp
  73. 144 0
      Software/src/WiFiPCController/lib/pubsubclient/src/PubSubClient.h
  74. 4 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/.gitignore
  75. 25 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/Makefile
  76. 93 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/README.md
  77. 256 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/connect_spec.cpp
  78. 185 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/keepalive_spec.cpp
  79. 23 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Arduino.h
  80. 50 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/BDDTest.cpp
  81. 23 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/BDDTest.h
  82. 30 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Buffer.cpp
  83. 23 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Buffer.h
  84. 21 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Client.h
  85. 44 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/IPAddress.cpp
  86. 72 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/IPAddress.h
  87. 153 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/ShimClient.cpp
  88. 51 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/ShimClient.h
  89. 39 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Stream.cpp
  90. 22 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Stream.h
  91. 10 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/trace.h
  92. 190 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/publish_spec.cpp
  93. 249 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/receive_spec.cpp
  94. 177 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/src/subscribe_spec.cpp
  95. 0 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/__init__.py
  96. 43 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/mqtt_basic.py
  97. 64 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/mqtt_publish_in_callback.py
  98. 2 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/settings.py
  99. 179 0
      Software/src/WiFiPCController/lib/pubsubclient/tests/testsuite.py
  100. 15 0
      Software/src/WiFiPCController/miscFunctions.ino

+ 47 - 0
Hardware/WiFiPCController-Hardware.txt

@@ -0,0 +1,47 @@
+
+
+
+
+
+
+GPIOs 4 and 5 are the only ones that are always high impedance. All others do have internal pull-ups or are even driven low/high during boot.
+GPIOs 3, 12, 13 and 14 pulled HIGH during boot. Their actual state does not influence the boot process.
+GPIOs 0, 1, 2 and 15 are pulled HIGH during boot and also driven LOW for short periods.
+The device will not boot if 0, 1 or 2 is driven LOW during start-up.
+GPIO 16 is driven HIGH during boot, don't short to GND. 
+
+
+http://rabbithole.wwwdotorg.org/2017/03/28/esp8266-gpio.html
+
+GPIO Behaviour Summary
+D0 	16 	High 	High during boot, falls after ~110ms (to ~1V?) 	High during boot, falls after ~110ms (to ~1V)
+D1 	5 	Low 	Low 	Low
+D2 	4 	Low 	Low 	Low
+D3 	0 	Low then oscillates 	Varies, stabilizes high after ~100ms 	Varies, stabilizes low after ~110ms
+D4 	2 	Varies, stabilizes high after ~60ms 	Varies, stabilizes high after ~70ms 	Varies, stabilizes low after ~110ms
+D5 	14 	High 	High, then low after ~110ms 	High, then low after ~110ms
+D6 	12 	High 	High, then low after ~110ms 	High, then low after ~110ms
+D7 	13 	High 	High, then low after ~110ms 	High, then low after ~110ms
+D8 	15 	Low 	Low, with glitch ~110ms 	Low, with glitch ~110ms
+D9 	3 	Low 	Low until ~50ms then high 	Low until ~50ms then high until ~110ms then low
+D10 	1 	Low 	Low until ~50ms then high 	Low until ~50ms then high until ~110ms then low
+
+Conclusion: GPIOs D1 and D2 are the only safe GPIOs I can use to drive relays if I don’t want them to operate autonomously at boot. I will have to rework my PCB:-(
+
+D3, D4 dürfen während dem boot nicht auf low gezogen werden, sonst bootet der ESP nicht -> nicht als Eingang zu gebrauchen, außer für Taster die während des bootens 
+ziemlich sicher nicht betätigt werden. 
+
+
+Daher folgende Pin-Zuordnung:
+D1/GPIO5 -> power switch
+D2/GPIO4 -> reset switch
+
+Ausgangsbeschaltung zur Ansteuerung von POWER und RESET:
+GPIO mit active HIGH logik
+ext. Pulldown-Widerstand -> Basiswiderstand auf NPN-Transistor
+Collector ist Schaltausgang und kommt parallel zum jeweiligen Eingang am Mainboard
+sollte bei den beiden "safe" GPIOs kein Problem machen
+
+Eingang von Power-LED: 
+NPN-Transistor mit Basiswiderstand von POWER_LED, Collector zieht GPIO auf GND wenn LED aktiv
+

+ 267 - 0
Hardware/schematic/WiFiPCController/WiFiPCController-cache.lib

@@ -0,0 +1,267 @@
+EESchema-LIBRARY Version 2.4
+#encoding utf-8
+#
+# Connector_Conn_01x01_Male
+#
+DEF Connector_Conn_01x01_Male J 0 40 Y N 1 F N
+F0 "J" 0 100 50 H V C CNN
+F1 "Connector_Conn_01x01_Male" 0 -100 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ Connector*:*
+$ENDFPLIST
+DRAW
+S 34 5 0 -5 1 1 6 F
+P 2 1 1 6 50 0 34 0 N
+X Pin_1 1 200 0 150 L 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# Connector_Conn_01x02_Male
+#
+DEF Connector_Conn_01x02_Male J 0 40 Y N 1 F N
+F0 "J" 0 100 50 H V C CNN
+F1 "Connector_Conn_01x02_Male" 0 -200 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ Connector*:*_1x??_*
+$ENDFPLIST
+DRAW
+S 34 -95 0 -105 1 1 6 F
+S 34 5 0 -5 1 1 6 F
+P 2 1 1 6 50 -100 34 -100 N
+P 2 1 1 6 50 0 34 0 N
+X Pin_1 1 200 0 150 L 50 50 1 1 P
+X Pin_2 2 200 -100 150 L 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# Connector_DB9_Female
+#
+DEF Connector_DB9_Female J 0 40 Y N 1 F N
+F0 "J" 0 550 50 H V C CNN
+F1 "Connector_DB9_Female" 0 -575 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ DSUB*Female*
+$ENDFPLIST
+DRAW
+C -70 -400 30 0 1 0 N
+C -70 -200 30 0 1 0 N
+C -70 0 30 0 1 0 N
+C -70 200 30 0 1 0 N
+C -70 400 30 0 1 0 N
+C 50 -300 30 0 1 0 N
+C 50 -100 30 0 1 0 N
+C 50 100 30 0 1 0 N
+C 50 300 30 0 1 0 N
+P 2 0 1 0 -150 -400 -100 -400 N
+P 2 0 1 0 -150 -300 20 -300 N
+P 2 0 1 0 -150 -200 -100 -200 N
+P 2 0 1 0 -150 -100 20 -100 N
+P 2 0 1 0 -150 0 -100 0 N
+P 2 0 1 0 -150 100 20 100 N
+P 2 0 1 0 -150 200 -100 200 N
+P 2 0 1 0 -150 300 20 300 N
+P 2 0 1 0 -150 400 -100 400 N
+P 5 0 1 10 -150 525 -150 -525 150 -375 150 375 -150 525 f
+X 1 1 -300 400 150 R 50 50 1 1 P
+X 2 2 -300 200 150 R 50 50 1 1 P
+X 3 3 -300 0 150 R 50 50 1 1 P
+X 4 4 -300 -200 150 R 50 50 1 1 P
+X 5 5 -300 -400 150 R 50 50 1 1 P
+X 6 6 -300 300 150 R 50 50 1 1 P
+X 7 7 -300 100 150 R 50 50 1 1 P
+X 8 8 -300 -100 150 R 50 50 1 1 P
+X 9 9 -300 -300 150 R 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# Connector_DB9_Male
+#
+DEF Connector_DB9_Male J 0 40 Y N 1 F N
+F0 "J" 0 550 50 H V C CNN
+F1 "Connector_DB9_Male" 0 -575 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ DSUB*Male*
+$ENDFPLIST
+DRAW
+C -70 -400 30 0 1 0 F
+C -70 -200 30 0 1 0 F
+C -70 0 30 0 1 0 F
+C -70 200 30 0 1 0 F
+C -70 400 30 0 1 0 F
+C 50 -300 30 0 1 0 F
+C 50 -100 30 0 1 0 F
+C 50 100 30 0 1 0 F
+C 50 300 30 0 1 0 F
+P 2 0 1 0 -150 -400 -100 -400 N
+P 2 0 1 0 -150 -300 20 -300 N
+P 2 0 1 0 -150 -200 -100 -200 N
+P 2 0 1 0 -150 -100 20 -100 N
+P 2 0 1 0 -150 0 -100 0 N
+P 2 0 1 0 -150 100 20 100 N
+P 2 0 1 0 -150 200 -100 200 N
+P 2 0 1 0 -150 300 20 300 N
+P 2 0 1 0 -150 400 -100 400 N
+P 5 0 1 10 -150 -525 -150 525 150 375 150 -375 -150 -525 f
+X 1 1 -300 -400 150 R 50 50 1 1 P
+X 2 2 -300 -200 150 R 50 50 1 1 P
+X 3 3 -300 0 150 R 50 50 1 1 P
+X 4 4 -300 200 150 R 50 50 1 1 P
+X 5 5 -300 400 150 R 50 50 1 1 P
+X 6 6 -300 -300 150 R 50 50 1 1 P
+X 7 7 -300 -100 150 R 50 50 1 1 P
+X 8 8 -300 100 150 R 50 50 1 1 P
+X 9 9 -300 300 150 R 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# Device_CP
+#
+DEF Device_CP C 0 10 N Y 1 F N
+F0 "C" 25 100 50 H V L CNN
+F1 "Device_CP" 25 -100 50 H V L CNN
+F2 "" 38 -150 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ CP_*
+$ENDFPLIST
+DRAW
+S -90 20 -90 40 0 1 0 N
+S -90 20 90 20 0 1 0 N
+S 90 -20 -90 -40 0 1 0 F
+S 90 40 -90 40 0 1 0 N
+S 90 40 90 20 0 1 0 N
+P 2 0 1 0 -70 90 -30 90 N
+P 2 0 1 0 -50 110 -50 70 N
+X ~ 1 0 150 110 D 50 50 1 1 P
+X ~ 2 0 -150 110 U 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# Device_D
+#
+DEF Device_D D 0 40 N N 1 F N
+F0 "D" 0 100 50 H V C CNN
+F1 "Device_D" 0 -100 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ TO-???*
+ *_Diode_*
+ *SingleDiode*
+ D_*
+$ENDFPLIST
+DRAW
+P 2 0 1 8 -50 50 -50 -50 N
+P 2 0 1 0 50 0 -50 0 N
+P 4 0 1 8 50 50 50 -50 -50 0 50 50 N
+X K 1 -150 0 100 R 50 50 1 1 P
+X A 2 150 0 100 L 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# Device_R
+#
+DEF Device_R R 0 0 N Y 1 F N
+F0 "R" 80 0 50 V V C CNN
+F1 "Device_R" 0 0 50 V V C CNN
+F2 "" -70 0 50 V I C CNN
+F3 "" 0 0 50 H I C CNN
+$FPLIST
+ R_*
+$ENDFPLIST
+DRAW
+S -40 -100 40 100 0 1 10 N
+X ~ 1 0 150 50 D 50 50 1 1 P
+X ~ 2 0 -150 50 U 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# Transistor_BJT_BC546
+#
+DEF Transistor_BJT_BC546 Q 0 0 Y N 1 F N
+F0 "Q" 200 75 50 H V L CNN
+F1 "Transistor_BJT_BC546" 200 0 50 H V L CNN
+F2 "Package_TO_SOT_THT:TO-92_Inline" 200 -75 50 H I L CIN
+F3 "" 0 0 50 H I L CNN
+ALIAS BC546 BC548 BC549 BC550 BC337 BC338
+$FPLIST
+ TO?92*
+$ENDFPLIST
+DRAW
+C 50 0 111 0 1 10 N
+P 2 0 1 0 0 0 25 0 N
+P 2 0 1 0 25 25 100 100 N
+P 3 0 1 0 25 -25 100 -100 100 -100 N
+P 3 0 1 20 25 75 25 -75 25 -75 N
+P 5 0 1 0 50 -70 70 -50 90 -90 50 -70 50 -70 F
+X C 1 100 200 100 D 50 50 1 1 P
+X B 2 -200 0 200 R 50 50 1 1 I
+X E 3 100 -200 100 U 50 50 1 1 P
+ENDDRAW
+ENDDEF
+#
+# power_+5V
+#
+DEF power_+5V #PWR 0 0 Y Y 1 F P
+F0 "#PWR" 0 -150 50 H I C CNN
+F1 "power_+5V" 0 140 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+DRAW
+P 2 0 1 0 -30 50 0 100 N
+P 2 0 1 0 0 0 0 100 N
+P 2 0 1 0 0 100 30 50 N
+X +5V 1 0 0 0 U 50 50 1 1 W N
+ENDDRAW
+ENDDEF
+#
+# power_GND
+#
+DEF power_GND #PWR 0 0 Y Y 1 F P
+F0 "#PWR" 0 -250 50 H I C CNN
+F1 "power_GND" 0 -150 50 H V C CNN
+F2 "" 0 0 50 H I C CNN
+F3 "" 0 0 50 H I C CNN
+DRAW
+P 6 0 1 0 0 0 0 -50 50 -50 0 -100 -50 -50 0 -50 N
+X GND 1 0 0 0 D 50 50 1 1 W N
+ENDDRAW
+ENDDEF
+#
+# wemos_mini_WeMos_mini
+#
+DEF wemos_mini_WeMos_mini U 0 40 Y Y 1 F N
+F0 "U" 0 500 60 H V C CNN
+F1 "wemos_mini_WeMos_mini" 0 -500 60 H V C CNN
+F2 "" 550 -700 60 H V C CNN
+F3 "" 550 -700 60 H V C CNN
+DRAW
+S -300 450 300 -550 0 1 0 N
+X 5V 1 -500 350 200 R 50 50 1 1 W
+X A0 10 500 -250 200 L 50 50 1 1 B
+X D0 11 500 -150 200 L 50 50 1 1 B
+X D5 12 500 -50 200 L 50 50 1 1 B
+X D6 13 500 50 200 L 50 50 1 1 B
+X D7 14 500 150 200 L 50 50 1 1 B
+X D8 15 500 250 200 L 50 50 1 1 B
+X 3.3V 16 500 350 200 L 50 50 1 1 w
+X GND 2 -500 250 200 R 50 50 1 1 W
+X D4 3 -500 150 200 R 50 50 1 1 B
+X D3 4 -500 50 200 R 50 50 1 1 B
+X D2 5 -500 -50 200 R 50 50 1 1 B
+X D1 6 -500 -150 200 R 50 50 1 1 B
+X Rx 7 -500 -250 200 R 50 50 1 1 B
+X Tx 8 -500 -350 200 R 50 50 1 1 B
+X Rst 9 500 -350 200 L 50 50 1 1 B
+ENDDRAW
+ENDDEF
+#
+#End Library

+ 521 - 0
Hardware/schematic/WiFiPCController/WiFiPCController.bak

@@ -0,0 +1,521 @@
+EESchema Schematic File Version 4
+EELAYER 26 0
+EELAYER END
+$Descr A4 11693 8268
+encoding utf-8
+Sheet 1 1
+Title ""
+Date ""
+Rev ""
+Comp ""
+Comment1 ""
+Comment2 ""
+Comment3 ""
+Comment4 ""
+$EndDescr
+$Comp
+L wemos_mini:WeMos_mini U?
+U 1 1 5E00DCCE
+P 2950 2100
+F 0 "U?" H 2950 2843 60  0001 C CNN
+F 1 "WeMos D1 mini" H 2950 2737 60  0000 C CNN
+F 2 "" H 3500 1400 60  0000 C CNN
+F 3 "ESP8266" H 2950 2631 60  0000 C CNN
+	1    2950 2100
+	1    0    0    -1  
+$EndComp
+$Comp
+L Transistor_BJT:BC546 Q?
+U 1 1 5E00DE92
+P 5000 2650
+F 0 "Q?" H 5191 2696 50  0001 L CNN
+F 1 "BC546" H 5191 2650 50  0000 L CNN
+F 2 "Package_TO_SOT_THT:TO-92_Inline" H 5200 2575 50  0001 L CIN
+F 3 "http://www.fairchildsemi.com/ds/BC/BC547.pdf" H 5000 2650 50  0001 L CNN
+	1    5000 2650
+	1    0    0    -1  
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00DED2
+P 4500 1700
+F 0 "R?" V 4293 1700 50  0001 C CNN
+F 1 "2k2" V 4385 1700 50  0000 C CNN
+F 2 "" V 4430 1700 50  0001 C CNN
+F 3 "~" H 4500 1700 50  0001 C CNN
+	1    4500 1700
+	0    1    1    0   
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00DF85
+P 4500 2650
+F 0 "R?" V 4293 2650 50  0001 C CNN
+F 1 "2k2" V 4385 2650 50  0000 C CNN
+F 2 "" V 4430 2650 50  0001 C CNN
+F 3 "~" H 4500 2650 50  0001 C CNN
+	1    4500 2650
+	0    1    1    0   
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00DFE3
+P 4200 1900
+F 0 "R?" H 4270 1946 50  0001 L CNN
+F 1 "2k2" H 4270 1900 50  0000 L CNN
+F 2 "" V 4130 1900 50  0001 C CNN
+F 3 "~" H 4200 1900 50  0001 C CNN
+	1    4200 1900
+	1    0    0    -1  
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00E0A1
+P 4200 2850
+F 0 "R?" H 4270 2896 50  0001 L CNN
+F 1 "2k2" H 4270 2850 50  0000 L CNN
+F 2 "" V 4130 2850 50  0001 C CNN
+F 3 "~" H 4200 2850 50  0001 C CNN
+	1    4200 2850
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00E18B
+P 5100 3100
+F 0 "#PWR?" H 5100 2850 50  0001 C CNN
+F 1 "GND" H 5105 2927 50  0000 C CNN
+F 2 "" H 5100 3100 50  0001 C CNN
+F 3 "" H 5100 3100 50  0001 C CNN
+	1    5100 3100
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00E1BB
+P 5100 2050
+F 0 "#PWR?" H 5100 1800 50  0001 C CNN
+F 1 "GND" H 5105 1877 50  0000 C CNN
+F 2 "" H 5100 2050 50  0001 C CNN
+F 3 "" H 5100 2050 50  0001 C CNN
+	1    5100 2050
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00E21B
+P 1900 2750
+F 0 "#PWR?" H 1900 2500 50  0001 C CNN
+F 1 "GND" H 1905 2577 50  0000 C CNN
+F 2 "" H 1900 2750 50  0001 C CNN
+F 3 "" H 1900 2750 50  0001 C CNN
+	1    1900 2750
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	5100 2050 5100 1900
+Wire Wire Line
+	5100 2850 5100 3000
+$Comp
+L Transistor_BJT:BC546 Q?
+U 1 1 5E00DDD0
+P 5000 1700
+F 0 "Q?" H 5191 1746 50  0001 L CNN
+F 1 "BC546" H 5191 1700 50  0000 L CNN
+F 2 "Package_TO_SOT_THT:TO-92_Inline" H 5200 1625 50  0001 L CIN
+F 3 "http://www.fairchildsemi.com/ds/BC/BC547.pdf" H 5000 1700 50  0001 L CNN
+	1    5000 1700
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	4200 2050 5100 2050
+Connection ~ 5100 2050
+Wire Wire Line
+	4200 3000 5100 3000
+Connection ~ 5100 3000
+Wire Wire Line
+	5100 3000 5100 3100
+Wire Wire Line
+	4200 1750 4200 1700
+Wire Wire Line
+	4200 1700 4350 1700
+Wire Wire Line
+	4650 1700 4800 1700
+Wire Wire Line
+	4650 2650 4800 2650
+Wire Wire Line
+	4350 2650 4200 2650
+Wire Wire Line
+	4200 2650 4200 2700
+Wire Wire Line
+	2450 2250 2350 2250
+Wire Wire Line
+	2350 2250 2350 2750
+Wire Wire Line
+	3850 2750 3850 1700
+Wire Wire Line
+	3850 1700 4200 1700
+Connection ~ 4200 1700
+Wire Wire Line
+	3950 2650 4200 2650
+Connection ~ 4200 2650
+Wire Wire Line
+	2250 2150 2450 2150
+Wire Wire Line
+	2250 2150 2250 2850
+Wire Wire Line
+	3950 2650 3950 2850
+Wire Wire Line
+	2450 1850 1900 1850
+Wire Wire Line
+	1900 1850 1900 2750
+$Comp
+L Transistor_BJT:BC546 Q?
+U 1 1 5E00FED0
+P 3000 3500
+F 0 "Q?" H 3191 3546 50  0001 L CNN
+F 1 "BC546" H 3191 3500 50  0000 L CNN
+F 2 "Package_TO_SOT_THT:TO-92_Inline" H 3200 3425 50  0001 L CIN
+F 3 "http://www.fairchildsemi.com/ds/BC/BC547.pdf" H 3000 3500 50  0001 L CNN
+	1    3000 3500
+	1    0    0    -1  
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00FED7
+P 2500 3500
+F 0 "R?" V 2293 3500 50  0001 C CNN
+F 1 "2k2" V 2385 3500 50  0000 C CNN
+F 2 "" V 2430 3500 50  0001 C CNN
+F 3 "~" H 2500 3500 50  0001 C CNN
+	1    2500 3500
+	0    1    1    0   
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00FEE5
+P 3100 3950
+F 0 "#PWR?" H 3100 3700 50  0001 C CNN
+F 1 "GND" H 3105 3777 50  0000 C CNN
+F 2 "" H 3100 3950 50  0001 C CNN
+F 3 "" H 3100 3950 50  0001 C CNN
+	1    3100 3950
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	2650 3500 2800 3500
+Wire Wire Line
+	3100 3300 3100 3050
+Wire Wire Line
+	3100 3050 3600 3050
+Wire Wire Line
+	3600 3050 3600 2150
+Wire Wire Line
+	3600 2150 3450 2150
+$Comp
+L Connector:Conn_01x02_Male J?
+U 1 1 5E010F08
+P 950 1550
+F 0 "J?" H 1056 1728 50  0001 C CNN
+F 1 "SUPPLY" H 1056 1637 50  0000 C CNN
+F 2 "" H 950 1550 50  0001 C CNN
+F 3 "~" H 950 1550 50  0001 C CNN
+	1    950  1550
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	1150 1550 1300 1550
+Wire Wire Line
+	2450 1550 2450 1750
+Wire Wire Line
+	1900 1850 1900 1650
+Wire Wire Line
+	1900 1650 1150 1650
+Connection ~ 1900 1850
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E011E93
+P 950 3500
+F 0 "J?" H 1056 3678 50  0001 C CNN
+F 1 "PC POWER LED +" H 1056 3587 50  0000 C CNN
+F 2 "" H 950 3500 50  0001 C CNN
+F 3 "~" H 950 3500 50  0001 C CNN
+	1    950  3500
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	3100 3700 3100 3950
+Wire Wire Line
+	2350 3500 2000 3500
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E015969
+P 6400 1500
+F 0 "J?" H 6373 1430 50  0001 R CNN
+F 1 "PC POWER SWITCH +" H 6373 1476 50  0000 R TNN
+F 2 "" H 6400 1500 50  0001 C CNN
+F 3 "~" H 6400 1500 50  0001 C CNN
+	1    6400 1500
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E015A0F
+P 6400 2450
+F 0 "J?" H 6372 2380 50  0001 R CNN
+F 1 "PC RESET SWITCH +" H 6372 2426 50  0000 R CNN
+F 2 "" H 6400 2450 50  0001 C CNN
+F 3 "~" H 6400 2450 50  0001 C CNN
+	1    6400 2450
+	-1   0    0    1   
+$EndComp
+Wire Wire Line
+	5100 2450 5600 2450
+Wire Wire Line
+	5100 1500 5600 1500
+Text Notes 850  1850 0    39   ~ 0
+1 = 5VSB -> ATX Pin 9 (purple)\n2 = GND -> ATX Pin 7 (black)
+Wire Wire Line
+	2350 2750 3850 2750
+Wire Wire Line
+	3950 2850 2250 2850
+$Comp
+L Device:R R?
+U 1 1 5E0186B7
+P 3600 1900
+F 0 "R?" H 3670 1946 50  0001 L CNN
+F 1 "2k2" H 3670 1900 50  0000 L CNN
+F 2 "" V 3530 1900 50  0001 C CNN
+F 3 "~" H 3600 1900 50  0001 C CNN
+	1    3600 1900
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	3600 2150 3600 2050
+Connection ~ 3600 2150
+Wire Wire Line
+	3600 1750 3450 1750
+$Comp
+L Device:R R?
+U 1 1 5E01A841
+P 5750 1500
+F 0 "R?" V 5543 1500 50  0001 C CNN
+F 1 "200" V 5635 1500 50  0000 C CNN
+F 2 "" V 5680 1500 50  0001 C CNN
+F 3 "~" H 5750 1500 50  0001 C CNN
+	1    5750 1500
+	0    1    1    0   
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E01A8CF
+P 5750 2450
+F 0 "R?" V 5543 2450 50  0001 C CNN
+F 1 "200" V 5635 2450 50  0000 C CNN
+F 2 "" V 5680 2450 50  0001 C CNN
+F 3 "~" H 5750 2450 50  0001 C CNN
+	1    5750 2450
+	0    1    1    0   
+$EndComp
+$Comp
+L Connector:DB9_Male J?
+U 1 1 5E01FC50
+P 2800 5000
+F 0 "J?" H 2980 5046 50  0001 L CNN
+F 1 "DB9_Male" H 2980 5000 50  0000 L CNN
+F 2 "" H 2800 5000 50  0001 C CNN
+F 3 " ~" H 2800 5000 50  0001 C CNN
+	1    2800 5000
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E01FCC4
+P 1000 5500
+F 0 "#PWR?" H 1000 5250 50  0001 C CNN
+F 1 "GND" H 1005 5327 50  0000 C CNN
+F 2 "" H 1000 5500 50  0001 C CNN
+F 3 "" H 1000 5500 50  0001 C CNN
+	1    1000 5500
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	1000 5500 1000 4600
+$Comp
+L power:+5V #PWR?
+U 1 1 5E020BD8
+P 1300 1100
+F 0 "#PWR?" H 1300 950 50  0001 C CNN
+F 1 "+5V" H 1315 1273 50  0000 C CNN
+F 2 "" H 1300 1100 50  0001 C CNN
+F 3 "" H 1300 1100 50  0001 C CNN
+	1    1300 1100
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:+5V #PWR?
+U 1 1 5E020BFC
+P 1000 4400
+F 0 "#PWR?" H 1000 4250 50  0001 C CNN
+F 1 "+5V" H 1015 4573 50  0000 C CNN
+F 2 "" H 1000 4400 50  0001 C CNN
+F 3 "" H 1000 4400 50  0001 C CNN
+	1    1000 4400
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	1300 1100 1300 1550
+Connection ~ 1300 1550
+Wire Wire Line
+	1300 1550 2450 1550
+Wire Wire Line
+	1000 4400 1200 4400
+Wire Wire Line
+	1200 4400 1200 5100
+Wire Wire Line
+	2500 5000 2000 5000
+Wire Wire Line
+	2000 5000 2000 3500
+Connection ~ 2000 3500
+Wire Wire Line
+	2000 3500 1150 3500
+Wire Wire Line
+	1000 4600 2500 4600
+Wire Wire Line
+	1200 5100 2500 5100
+Wire Wire Line
+	2200 4300 2200 5200
+Wire Wire Line
+	2200 5200 2500 5200
+Wire Wire Line
+	2500 4900 2300 4900
+Wire Wire Line
+	2300 4900 2300 4400
+Wire Wire Line
+	6100 2200 6000 2200
+Wire Wire Line
+	6000 2200 6000 1500
+$Comp
+L Connector:DB9_Female J?
+U 1 1 5E02F648
+P 4700 5000
+F 0 "J?" H 4620 4308 50  0001 C CNN
+F 1 "DB9_Female" H 4620 4400 50  0000 C CNN
+F 2 "" H 4700 5000 50  0001 C CNN
+F 3 " ~" H 4700 5000 50  0001 C CNN
+	1    4700 5000
+	-1   0    0    1   
+$EndComp
+Wire Wire Line
+	5900 2450 6000 2450
+Wire Wire Line
+	2200 4300 3700 4300
+Wire Wire Line
+	3700 4300 3700 3500
+Wire Wire Line
+	3700 3500 6000 3500
+Wire Wire Line
+	6000 3500 6000 2450
+Connection ~ 6000 2450
+Wire Wire Line
+	6000 2450 6200 2450
+Wire Wire Line
+	6100 2200 6100 3600
+Wire Wire Line
+	6100 3600 3800 3600
+Wire Wire Line
+	3800 3600 3800 4400
+Wire Wire Line
+	3800 4400 2300 4400
+Connection ~ 6000 1500
+Wire Wire Line
+	6000 1500 6200 1500
+Wire Wire Line
+	5900 1500 6000 1500
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E0379C4
+P 6500 4600
+F 0 "J?" H 6473 4530 50  0001 R CNN
+F 1 "PC POWER SWITCH +" H 6473 4576 50  0000 R TNN
+F 2 "" H 6500 4600 50  0001 C CNN
+F 3 "~" H 6500 4600 50  0001 C CNN
+	1    6500 4600
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E038D99
+P 6500 4800
+F 0 "J?" H 6472 4730 50  0001 R CNN
+F 1 "PC RESET SWITCH +" H 6472 4776 50  0000 R CNN
+F 2 "" H 6500 4800 50  0001 C CNN
+F 3 "~" H 6500 4800 50  0001 C CNN
+	1    6500 4800
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E03B53D
+P 6500 5000
+F 0 "J?" H 6606 5178 50  0001 C CNN
+F 1 "PC POWER LED +" H 6472 4976 50  0000 R CNN
+F 2 "" H 6500 5000 50  0001 C CNN
+F 3 "~" H 6500 5000 50  0001 C CNN
+	1    6500 5000
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x02_Male J?
+U 1 1 5E04048A
+P 6500 5300
+F 0 "J?" H 6606 5478 50  0001 C CNN
+F 1 "SUPPLY" H 6606 5387 50  0000 C CNN
+F 2 "" H 6500 5300 50  0001 C CNN
+F 3 "~" H 6500 5300 50  0001 C CNN
+	1    6500 5300
+	-1   0    0    1   
+$EndComp
+Text Notes 6300 5600 0    39   ~ 0
+1 = 5VSB -> ATX Pin 9 5VSB (purple)\n2 = GND -> ATX Pin 7 GND (black)
+Wire Wire Line
+	6300 5200 5600 5200
+Wire Wire Line
+	5600 5200 5600 4600
+Wire Wire Line
+	5600 4600 5000 4600
+Wire Wire Line
+	5000 5100 5500 5100
+Wire Wire Line
+	5500 5100 5500 5300
+Wire Wire Line
+	5500 5300 6300 5300
+Wire Wire Line
+	5000 5200 5400 5200
+Wire Wire Line
+	5400 5200 5400 4800
+Wire Wire Line
+	5400 4800 6300 4800
+Wire Wire Line
+	5000 4900 5300 4900
+Wire Wire Line
+	5300 4900 5300 4700
+Wire Wire Line
+	5300 4700 6100 4700
+Wire Wire Line
+	6100 4700 6100 4600
+Wire Wire Line
+	6100 4600 6300 4600
+Wire Wire Line
+	5000 5000 6300 5000
+Wire Notes Line
+	4300 4200 4300 5800
+Wire Notes Line
+	4300 5800 7600 5800
+Wire Notes Line
+	7600 5800 7600 4200
+Wire Notes Line
+	7600 4200 4300 4200
+Text Notes 4350 4300 0    50   ~ 0
+PC internal (slot bracket w female DB9 connector)
+$EndSCHEMATC

+ 33 - 0
Hardware/schematic/WiFiPCController/WiFiPCController.pro

@@ -0,0 +1,33 @@
+update=22/05/2015 07:44:53
+version=1
+last_client=kicad
+[general]
+version=1
+RootSch=
+BoardNm=
+[pcbnew]
+version=1
+LastNetListRead=
+UseCmpFile=1
+PadDrill=0.600000000000
+PadDrillOvalY=0.600000000000
+PadSizeH=1.500000000000
+PadSizeV=1.500000000000
+PcbTextSizeV=1.500000000000
+PcbTextSizeH=1.500000000000
+PcbTextThickness=0.300000000000
+ModuleTextSizeV=1.000000000000
+ModuleTextSizeH=1.000000000000
+ModuleTextSizeThickness=0.150000000000
+SolderMaskClearance=0.000000000000
+SolderMaskMinWidth=0.000000000000
+DrawSegmentWidth=0.200000000000
+BoardOutlineThickness=0.100000000000
+ModuleOutlineThickness=0.150000000000
+[cvpcb]
+version=1
+NetIExt=net
+[eeschema]
+version=1
+LibDir=
+[eeschema/libraries]

+ 551 - 0
Hardware/schematic/WiFiPCController/WiFiPCController.sch

@@ -0,0 +1,551 @@
+EESchema Schematic File Version 4
+EELAYER 26 0
+EELAYER END
+$Descr A4 11693 8268
+encoding utf-8
+Sheet 1 1
+Title ""
+Date ""
+Rev ""
+Comp ""
+Comment1 ""
+Comment2 ""
+Comment3 ""
+Comment4 ""
+$EndDescr
+$Comp
+L wemos_mini:WeMos_mini U?
+U 1 1 5E00DCCE
+P 2950 2100
+F 0 "U?" H 2950 2843 60  0001 C CNN
+F 1 "WeMos D1 mini" H 2950 2737 60  0000 C CNN
+F 2 "" H 3500 1400 60  0000 C CNN
+F 3 "ESP8266" H 2950 2631 60  0000 C CNN
+	1    2950 2100
+	1    0    0    -1  
+$EndComp
+$Comp
+L Transistor_BJT:BC546 Q?
+U 1 1 5E00DE92
+P 5000 2650
+F 0 "Q?" H 5191 2696 50  0001 L CNN
+F 1 "BC546" H 5191 2650 50  0000 L CNN
+F 2 "Package_TO_SOT_THT:TO-92_Inline" H 5200 2575 50  0001 L CIN
+F 3 "http://www.fairchildsemi.com/ds/BC/BC547.pdf" H 5000 2650 50  0001 L CNN
+	1    5000 2650
+	1    0    0    -1  
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00DED2
+P 4500 1700
+F 0 "R?" V 4293 1700 50  0001 C CNN
+F 1 "2k2" V 4385 1700 50  0000 C CNN
+F 2 "" V 4430 1700 50  0001 C CNN
+F 3 "~" H 4500 1700 50  0001 C CNN
+	1    4500 1700
+	0    1    1    0   
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00DF85
+P 4500 2650
+F 0 "R?" V 4293 2650 50  0001 C CNN
+F 1 "2k2" V 4385 2650 50  0000 C CNN
+F 2 "" V 4430 2650 50  0001 C CNN
+F 3 "~" H 4500 2650 50  0001 C CNN
+	1    4500 2650
+	0    1    1    0   
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00DFE3
+P 4200 1900
+F 0 "R?" H 4270 1946 50  0001 L CNN
+F 1 "2k2" H 4270 1900 50  0000 L CNN
+F 2 "" V 4130 1900 50  0001 C CNN
+F 3 "~" H 4200 1900 50  0001 C CNN
+	1    4200 1900
+	1    0    0    -1  
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00E0A1
+P 4200 2850
+F 0 "R?" H 4270 2896 50  0001 L CNN
+F 1 "2k2" H 4270 2850 50  0000 L CNN
+F 2 "" V 4130 2850 50  0001 C CNN
+F 3 "~" H 4200 2850 50  0001 C CNN
+	1    4200 2850
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00E18B
+P 5100 3100
+F 0 "#PWR?" H 5100 2850 50  0001 C CNN
+F 1 "GND" H 5105 2927 50  0000 C CNN
+F 2 "" H 5100 3100 50  0001 C CNN
+F 3 "" H 5100 3100 50  0001 C CNN
+	1    5100 3100
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00E1BB
+P 5100 2050
+F 0 "#PWR?" H 5100 1800 50  0001 C CNN
+F 1 "GND" H 5105 1877 50  0000 C CNN
+F 2 "" H 5100 2050 50  0001 C CNN
+F 3 "" H 5100 2050 50  0001 C CNN
+	1    5100 2050
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00E21B
+P 1900 2750
+F 0 "#PWR?" H 1900 2500 50  0001 C CNN
+F 1 "GND" H 1905 2577 50  0000 C CNN
+F 2 "" H 1900 2750 50  0001 C CNN
+F 3 "" H 1900 2750 50  0001 C CNN
+	1    1900 2750
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	5100 2050 5100 1900
+Wire Wire Line
+	5100 2850 5100 3000
+$Comp
+L Transistor_BJT:BC546 Q?
+U 1 1 5E00DDD0
+P 5000 1700
+F 0 "Q?" H 5191 1746 50  0001 L CNN
+F 1 "BC546" H 5191 1700 50  0000 L CNN
+F 2 "Package_TO_SOT_THT:TO-92_Inline" H 5200 1625 50  0001 L CIN
+F 3 "http://www.fairchildsemi.com/ds/BC/BC547.pdf" H 5000 1700 50  0001 L CNN
+	1    5000 1700
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	4200 2050 5100 2050
+Connection ~ 5100 2050
+Wire Wire Line
+	4200 3000 5100 3000
+Connection ~ 5100 3000
+Wire Wire Line
+	5100 3000 5100 3100
+Wire Wire Line
+	4200 1750 4200 1700
+Wire Wire Line
+	4200 1700 4350 1700
+Wire Wire Line
+	4650 1700 4800 1700
+Wire Wire Line
+	4650 2650 4800 2650
+Wire Wire Line
+	4350 2650 4200 2650
+Wire Wire Line
+	4200 2650 4200 2700
+Wire Wire Line
+	2450 2250 2350 2250
+Wire Wire Line
+	2350 2250 2350 2750
+Wire Wire Line
+	3850 2750 3850 1700
+Wire Wire Line
+	3850 1700 4200 1700
+Connection ~ 4200 1700
+Wire Wire Line
+	3950 2650 4200 2650
+Connection ~ 4200 2650
+Wire Wire Line
+	2250 2150 2450 2150
+Wire Wire Line
+	2250 2150 2250 2850
+Wire Wire Line
+	3950 2650 3950 2850
+Wire Wire Line
+	2450 1850 2100 1850
+Wire Wire Line
+	1900 1850 1900 2750
+$Comp
+L Transistor_BJT:BC546 Q?
+U 1 1 5E00FED0
+P 3000 3500
+F 0 "Q?" H 3191 3546 50  0001 L CNN
+F 1 "BC546" H 3191 3500 50  0000 L CNN
+F 2 "Package_TO_SOT_THT:TO-92_Inline" H 3200 3425 50  0001 L CIN
+F 3 "http://www.fairchildsemi.com/ds/BC/BC547.pdf" H 3000 3500 50  0001 L CNN
+	1    3000 3500
+	1    0    0    -1  
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E00FED7
+P 2500 3500
+F 0 "R?" V 2293 3500 50  0001 C CNN
+F 1 "2k2" V 2385 3500 50  0000 C CNN
+F 2 "" V 2430 3500 50  0001 C CNN
+F 3 "~" H 2500 3500 50  0001 C CNN
+	1    2500 3500
+	0    1    1    0   
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E00FEE5
+P 3100 3950
+F 0 "#PWR?" H 3100 3700 50  0001 C CNN
+F 1 "GND" H 3105 3777 50  0000 C CNN
+F 2 "" H 3100 3950 50  0001 C CNN
+F 3 "" H 3100 3950 50  0001 C CNN
+	1    3100 3950
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	2650 3500 2800 3500
+Wire Wire Line
+	3100 3300 3100 3050
+Wire Wire Line
+	3100 3050 3600 3050
+Wire Wire Line
+	3600 3050 3600 2150
+Wire Wire Line
+	3600 2150 3450 2150
+$Comp
+L Connector:Conn_01x02_Male J?
+U 1 1 5E010F08
+P 950 1550
+F 0 "J?" H 1056 1728 50  0001 C CNN
+F 1 "SUPPLY" H 1056 1637 50  0000 C CNN
+F 2 "" H 950 1550 50  0001 C CNN
+F 3 "~" H 950 1550 50  0001 C CNN
+	1    950  1550
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	1150 1550 1300 1550
+Wire Wire Line
+	2450 1550 2450 1750
+Wire Wire Line
+	1900 1850 1900 1650
+Wire Wire Line
+	1900 1650 1150 1650
+Connection ~ 1900 1850
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E011E93
+P 950 3500
+F 0 "J?" H 1056 3678 50  0001 C CNN
+F 1 "PC POWER LED +" H 1056 3587 50  0000 C CNN
+F 2 "" H 950 3500 50  0001 C CNN
+F 3 "~" H 950 3500 50  0001 C CNN
+	1    950  3500
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	3100 3700 3100 3950
+Wire Wire Line
+	2350 3500 2000 3500
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E015969
+P 6400 1500
+F 0 "J?" H 6373 1430 50  0001 R CNN
+F 1 "PC POWER SWITCH +" H 6373 1476 50  0000 R TNN
+F 2 "" H 6400 1500 50  0001 C CNN
+F 3 "~" H 6400 1500 50  0001 C CNN
+	1    6400 1500
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E015A0F
+P 6400 2450
+F 0 "J?" H 6372 2380 50  0001 R CNN
+F 1 "PC RESET SWITCH +" H 6372 2426 50  0000 R CNN
+F 2 "" H 6400 2450 50  0001 C CNN
+F 3 "~" H 6400 2450 50  0001 C CNN
+	1    6400 2450
+	-1   0    0    1   
+$EndComp
+Wire Wire Line
+	5100 2450 5600 2450
+Wire Wire Line
+	5100 1500 5600 1500
+Text Notes 850  1850 0    39   ~ 0
+1 = 5VSB -> ATX Pin 9 (purple)\n2 = GND -> ATX Pin 7 (black)
+Wire Wire Line
+	2350 2750 3850 2750
+Wire Wire Line
+	3950 2850 2250 2850
+$Comp
+L Device:R R?
+U 1 1 5E0186B7
+P 3600 1900
+F 0 "R?" H 3670 1946 50  0001 L CNN
+F 1 "2k2" H 3670 1900 50  0000 L CNN
+F 2 "" V 3530 1900 50  0001 C CNN
+F 3 "~" H 3600 1900 50  0001 C CNN
+	1    3600 1900
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	3600 2150 3600 2050
+Connection ~ 3600 2150
+Wire Wire Line
+	3600 1750 3450 1750
+$Comp
+L Device:R R?
+U 1 1 5E01A841
+P 5750 1500
+F 0 "R?" V 5543 1500 50  0001 C CNN
+F 1 "200" V 5635 1500 50  0000 C CNN
+F 2 "" V 5680 1500 50  0001 C CNN
+F 3 "~" H 5750 1500 50  0001 C CNN
+	1    5750 1500
+	0    1    1    0   
+$EndComp
+$Comp
+L Device:R R?
+U 1 1 5E01A8CF
+P 5750 2450
+F 0 "R?" V 5543 2450 50  0001 C CNN
+F 1 "200" V 5635 2450 50  0000 C CNN
+F 2 "" V 5680 2450 50  0001 C CNN
+F 3 "~" H 5750 2450 50  0001 C CNN
+	1    5750 2450
+	0    1    1    0   
+$EndComp
+$Comp
+L Connector:DB9_Male J?
+U 1 1 5E01FC50
+P 2800 5000
+F 0 "J?" H 2980 5046 50  0001 L CNN
+F 1 "DB9_Male" H 2980 5000 50  0000 L CNN
+F 2 "" H 2800 5000 50  0001 C CNN
+F 3 " ~" H 2800 5000 50  0001 C CNN
+	1    2800 5000
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:GND #PWR?
+U 1 1 5E01FCC4
+P 1000 5500
+F 0 "#PWR?" H 1000 5250 50  0001 C CNN
+F 1 "GND" H 1005 5327 50  0000 C CNN
+F 2 "" H 1000 5500 50  0001 C CNN
+F 3 "" H 1000 5500 50  0001 C CNN
+	1    1000 5500
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	1000 5500 1000 4600
+$Comp
+L power:+5V #PWR?
+U 1 1 5E020BD8
+P 1300 1100
+F 0 "#PWR?" H 1300 950 50  0001 C CNN
+F 1 "+5V" H 1315 1273 50  0000 C CNN
+F 2 "" H 1300 1100 50  0001 C CNN
+F 3 "" H 1300 1100 50  0001 C CNN
+	1    1300 1100
+	1    0    0    -1  
+$EndComp
+$Comp
+L power:+5V #PWR?
+U 1 1 5E020BFC
+P 1000 4400
+F 0 "#PWR?" H 1000 4250 50  0001 C CNN
+F 1 "+5V" H 1015 4573 50  0000 C CNN
+F 2 "" H 1000 4400 50  0001 C CNN
+F 3 "" H 1000 4400 50  0001 C CNN
+	1    1000 4400
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	1300 1100 1300 1550
+Wire Wire Line
+	1000 4400 1200 4400
+Wire Wire Line
+	1200 4400 1200 5100
+Wire Wire Line
+	2500 5000 2000 5000
+Wire Wire Line
+	2000 5000 2000 3500
+Connection ~ 2000 3500
+Wire Wire Line
+	2000 3500 1150 3500
+Wire Wire Line
+	1000 4600 2500 4600
+Wire Wire Line
+	1200 5100 2500 5100
+Wire Wire Line
+	2200 4300 2200 5200
+Wire Wire Line
+	2200 5200 2500 5200
+Wire Wire Line
+	2500 4900 2300 4900
+Wire Wire Line
+	2300 4900 2300 4400
+Wire Wire Line
+	6100 2200 6000 2200
+Wire Wire Line
+	6000 2200 6000 1500
+$Comp
+L Connector:DB9_Female J?
+U 1 1 5E02F648
+P 4700 5000
+F 0 "J?" H 4620 4308 50  0001 C CNN
+F 1 "DB9_Female" H 4620 4400 50  0000 C CNN
+F 2 "" H 4700 5000 50  0001 C CNN
+F 3 " ~" H 4700 5000 50  0001 C CNN
+	1    4700 5000
+	-1   0    0    1   
+$EndComp
+Wire Wire Line
+	5900 2450 6000 2450
+Wire Wire Line
+	2200 4300 3700 4300
+Wire Wire Line
+	3700 4300 3700 3500
+Wire Wire Line
+	3700 3500 6000 3500
+Wire Wire Line
+	6000 3500 6000 2450
+Connection ~ 6000 2450
+Wire Wire Line
+	6000 2450 6200 2450
+Wire Wire Line
+	6100 2200 6100 3600
+Wire Wire Line
+	6100 3600 3800 3600
+Wire Wire Line
+	3800 3600 3800 4400
+Wire Wire Line
+	3800 4400 2300 4400
+Connection ~ 6000 1500
+Wire Wire Line
+	6000 1500 6200 1500
+Wire Wire Line
+	5900 1500 6000 1500
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E0379C4
+P 6500 4600
+F 0 "J?" H 6473 4530 50  0001 R CNN
+F 1 "PC POWER SWITCH +" H 6473 4576 50  0000 R TNN
+F 2 "" H 6500 4600 50  0001 C CNN
+F 3 "~" H 6500 4600 50  0001 C CNN
+	1    6500 4600
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E038D99
+P 6500 4800
+F 0 "J?" H 6472 4730 50  0001 R CNN
+F 1 "PC RESET SWITCH +" H 6472 4776 50  0000 R CNN
+F 2 "" H 6500 4800 50  0001 C CNN
+F 3 "~" H 6500 4800 50  0001 C CNN
+	1    6500 4800
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x01_Male J?
+U 1 1 5E03B53D
+P 6500 5000
+F 0 "J?" H 6606 5178 50  0001 C CNN
+F 1 "PC POWER LED +" H 6472 4976 50  0000 R CNN
+F 2 "" H 6500 5000 50  0001 C CNN
+F 3 "~" H 6500 5000 50  0001 C CNN
+	1    6500 5000
+	-1   0    0    1   
+$EndComp
+$Comp
+L Connector:Conn_01x02_Male J?
+U 1 1 5E04048A
+P 6500 5300
+F 0 "J?" H 6606 5478 50  0001 C CNN
+F 1 "SUPPLY" H 6606 5387 50  0000 C CNN
+F 2 "" H 6500 5300 50  0001 C CNN
+F 3 "~" H 6500 5300 50  0001 C CNN
+	1    6500 5300
+	-1   0    0    1   
+$EndComp
+Text Notes 6300 5600 0    39   ~ 0
+1 = 5VSB -> ATX Pin 9 5VSB (purple)\n2 = GND -> ATX Pin 7 GND (black)
+Wire Wire Line
+	6300 5200 5600 5200
+Wire Wire Line
+	5600 5200 5600 4600
+Wire Wire Line
+	5600 4600 5000 4600
+Wire Wire Line
+	5000 5100 5500 5100
+Wire Wire Line
+	5500 5100 5500 5300
+Wire Wire Line
+	5500 5300 6300 5300
+Wire Wire Line
+	5000 5200 5400 5200
+Wire Wire Line
+	5400 5200 5400 4800
+Wire Wire Line
+	5400 4800 6300 4800
+Wire Wire Line
+	5000 4900 5300 4900
+Wire Wire Line
+	5300 4900 5300 4700
+Wire Wire Line
+	5300 4700 6100 4700
+Wire Wire Line
+	6100 4700 6100 4600
+Wire Wire Line
+	6100 4600 6300 4600
+Wire Wire Line
+	5000 5000 6300 5000
+Wire Notes Line
+	4300 4200 4300 5800
+Wire Notes Line
+	4300 5800 7600 5800
+Wire Notes Line
+	7600 5800 7600 4200
+Wire Notes Line
+	7600 4200 4300 4200
+Text Notes 4350 4300 0    50   ~ 0
+PC internal (slot bracket w female DB9 connector)
+$Comp
+L Device:CP C?
+U 1 1 5E058F1B
+P 2100 1700
+F 0 "C?" H 2218 1746 50  0001 L CNN
+F 1 "220u" H 2218 1700 50  0000 L CNN
+F 2 "" H 2138 1550 50  0001 C CNN
+F 3 "~" H 2100 1700 50  0001 C CNN
+	1    2100 1700
+	1    0    0    -1  
+$EndComp
+Wire Wire Line
+	2100 1550 2450 1550
+Connection ~ 2100 1850
+Wire Wire Line
+	2100 1850 1900 1850
+$Comp
+L Device:D D?
+U 1 1 5E05AD66
+P 1650 1550
+F 0 "D?" H 1650 1334 50  0001 C CNN
+F 1 "1N4001" H 1650 1426 50  0000 C CNN
+F 2 "" H 1650 1550 50  0001 C CNN
+F 3 "~" H 1650 1550 50  0001 C CNN
+	1    1650 1550
+	-1   0    0    1   
+$EndComp
+Wire Wire Line
+	1800 1550 2100 1550
+Connection ~ 2100 1550
+Wire Wire Line
+	1500 1550 1300 1550
+Connection ~ 1300 1550
+$EndSCHEMATC

+ 24 - 0
README-DE.md

@@ -0,0 +1,24 @@
+# ESP-PC-Controller
+ESP8266-basierte Fernsteuerung für PCs, steuerbar über Web Interface, HTTP-API und MQTT.
+
+Wegen schlechter Erfahrungen mit Wake-on-LAN musste eine zuverlässigere Lösung her, um den PC von unterwegs aufwecken zu können. 
+WoL funktioniert meist nur zuverlässig wenn der PC im Sleep-Modus ist - ist er heruntergefahren wird es zum Glücksspiel. Wurde der PC nach dem herunterfahren vom Stromnetz getrennt, funktioniert WoL überhaupt nicht mehr. 
+
+Eine einfache Möglichkeit ist natürlich den PC an eine WiFi-Steckdose anzuschließen und diese bei Bedarf einzuschalten. Das hat aber einige Nachteile - es muss dann im BIOS eingestellt sein, dass der PC immer startet wenn die Stromversorgung unterbrochen war. Dadurch würde der PC manchmal unbemerkt laufen, ohne dass er gebraucht wird. Außerdem funktioniert das nicht bei Verwendung des Sleep-Modus - und könnte zu Datenverlust führen, wenn der im Sleep befindliche PC einfach vom Stromnetz getrennt wird. 
+
+Ich wollte daher eine Lösung, mit der ich ganz einfach den Power-Button des PC fernbedienen kann, und die Rückmeldung über den aktuellen Status gibt, indem die Power-LED ausgewertet wird. 
+
+Dafür bietet sich ein Modul auf Basis des ESP8266 an. Die Stromversorgung kann von der +5VSB-Leitung des ATX Netzteils abgegriffen werden, dann ist der ESP aktiv solange der PC am Strom hängt. Für die Anbindung an die PC-Hardware braucht man dann nur ein paar Transistoren und Widerstände.
+
+
+## Features
+- connects to the POWER switch, RESET switch and POWER-LED terminals on the PC motherboard
+- detection of PC status (on, sleep, off) by observing the power LED output (blinking LED is sleep on most motherboards)
+- perform short press and hold on the POWER switch
+- perform reset switch action
+- controllable via web interface, HTTP-API and MQTT
+- status reporting via web interface, HTTP-API and MQTT
+- WiFi setup via captive portal
+- configurable via web interface
+- direct support for Home Assistant switch and sensor devices
+

+ 14 - 0
README.md

@@ -0,0 +1,14 @@
+# WiFi-PC-Controller
+ESP8266 based WiFi connected remote control for PCs, controllable by Web interface and MQTT.
+
+## Features
+- connects to the POWER switch, RESET switch and POWER-LED terminals on the PC motherboard
+- detection of PC status (on, sleep, off) by observing the power LED output (blinking LED is sleep on most motherboards)
+- perform short press and hold on the POWER switch
+- perform reset switch action
+- controllable via web interface, HTTP-API and MQTT
+- status reporting via web interface, HTTP-API and MQTT
+- WiFi setup via captive portal
+- configurable via web interface
+- direct support for Home Assistant switch and sensor devices
+

+ 165 - 0
Software/src/WiFiPCController/PersWiFiManager.cpp

@@ -0,0 +1,165 @@
+/* PersWiFiManager
+   version 3.0.1
+   https://r-downing.github.io/PersWiFiManager/
+*/
+
+#include "PersWiFiManager.h"
+
+//#ifdef WIFI_HTM_PROGMEM
+const char wifi_htm[] PROGMEM = R"=====(<!DOCTYPE html><html><head><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/><title>ESP WiFi</title><script>function g(i){return document.getElementById(i);};function p(t,l){if(confirm(t)) window.location=l;};function E(s){return document.createElement(s)};var S="setAttribute",A="appendChild",H="innerHTML",X,wl;function scan(){if(X) return;X=new XMLHttpRequest(),wl=document.getElementById('wl');wl[H]="Scanning...";X.onreadystatechange=function(){if (this.readyState==4&&this.status==200){X=0;wl[H]="";this.responseText.split("\n").forEach(function (e){let t=e.split(","), s=t.slice(2).join(',');var d=E('div'),i=E('a'),c=E('a');i[S]('class','s'); c[S]('class','q');i.onclick=function(){g('s').value=s;g('p').focus();};i[A](document.createTextNode(s));c[H]=t[0]+"%"+(parseInt(t[1])?"\uD83D\uDD12":"\u26A0");wl[A](i); wl[A](c);wl[A](document.createElement('br'));});}};X.open("GET","wifi/list",true);X.send();};</script><style>input{padding:5px;font-size:1em;width:95%;}body{text-align:center;font-family:verdana;background-color:black;color:white;}a{color:#1fa3ec;}button{border:0;border-radius:0.3em;background-color:#1fa3ec;color:#fff;line-height:2.4em;font-size:1.2em;width:100%;display:block;}.q{float:right;}.s{display:inline-block;width:14em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}#wl{line-height:1.5em;}</style></head><body><div style='text-align:left;display:inline-block;width:320px;padding:5px'><button onclick="scan()">&#x21bb; Scan</button><p id='wl'></p><form method='post' action='/wifi/connect'><input id='s' name='n' length=32 placeholder='SSID'><br><input id='p' name='p' length=64 type='password' placeholder='password'><br><br><button type='submit'>Connect</button></form><br><br><button onclick="p('Start WPS?','/wifi/wps')">WPS Setup</button><br><button onclick="p('Reboot device?','/wifi/rst')">Reboot</button><br><a href="/">Home</a></div></body></html>)=====";
+//#endif
+
+PersWiFiManager::PersWiFiManager(ESP8266WebServer& s, DNSServer& d) {
+  _server = &s;
+  _dnsServer = &d;
+  _apPass = "";
+} //PersWiFiManager
+
+bool PersWiFiManager::attemptConnection(const String& ssid, const String& pass) {
+  //attempt to connect to wifi
+  WiFi.mode(WIFI_STA);
+  if (ssid.length()) {
+    if (pass.length()) WiFi.begin(ssid.c_str(), pass.c_str());
+    else WiFi.begin(ssid.c_str());
+  } else {
+    WiFi.begin();
+  }
+
+  //if in nonblock mode, skip this loop
+  _connectStartTime = millis();// + 1;
+  while (!_connectNonBlock && _connectStartTime) {
+    handleWiFi();
+    delay(10);
+  }
+
+  return (WiFi.status() == WL_CONNECTED);
+
+} //attemptConnection
+
+void PersWiFiManager::handleWiFi() {
+  if (!_connectStartTime) return;
+
+  if (WiFi.status() == WL_CONNECTED) {
+    _connectStartTime = 0;
+    if (_connectHandler) _connectHandler();
+    return;
+  }
+
+  //if failed or not connected and time is up
+  //if ((WiFi.status() == WL_CONNECT_FAILED) || ((WiFi.status() != WL_CONNECTED) && ((millis() - _connectStartTime) > (1000 * WIFI_CONNECT_TIMEOUT)))) {
+  if ((WiFi.status() == WL_CONNECT_FAILED) || ((WiFi.status() != WL_CONNECTED))) {
+    if(((millis() - _connectStartTime) > (1000 * WIFI_CONNECT_TIMEOUT))) {
+      startApMode();
+      _connectStartTime = 0; //reset connect start time
+    }
+    //else {
+      //retrying...
+//      attemptConnection();
+  //  }
+  }
+
+} //handleWiFi
+
+void PersWiFiManager::startApMode(){
+  //start AP mode
+  IPAddress apIP(192, 168, 1, 1);
+  WiFi.mode(WIFI_AP);
+  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
+  _apPass.length() ? WiFi.softAP(getApSsid().c_str(), _apPass.c_str()) : WiFi.softAP(getApSsid().c_str());
+  if (_apHandler) _apHandler();  
+}//startApMode
+
+void PersWiFiManager::setConnectNonBlock(bool b) {
+  _connectNonBlock = b;
+} //setConnectNonBlock
+
+void PersWiFiManager::setupWiFiHandlers() {
+  IPAddress apIP(192, 168, 1, 1);
+  _dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
+  _dnsServer->start((byte)53, "*", apIP); //used for captive portal in AP mode
+
+  _server->on("/wifi/list", [&] () {
+    //scan for wifi networks
+    int n = WiFi.scanNetworks();
+
+    //build array of indices
+    int ix[n];
+    for (int i = 0; i < n; i++) ix[i] = i;
+
+    //sort by signal strength
+    for (int i = 0; i < n; i++) for (int j = 1; j < n - i; j++) if (WiFi.RSSI(ix[j]) > WiFi.RSSI(ix[j - 1])) std::swap(ix[j], ix[j - 1]);
+    //remove duplicates
+    for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) if (WiFi.SSID(ix[i]).equals(WiFi.SSID(ix[j])) && WiFi.encryptionType(ix[i]) == WiFi.encryptionType(ix[j])) ix[j] = -1;
+
+    //build plain text string of wifi info
+    //format [signal%]:[encrypted 0 or 1]:SSID
+    String s = "";
+    s.reserve(2050);
+    for (int i = 0; i < n && s.length() < 2000; i++) { //check s.length to limit memory usage
+      if (ix[i] != -1) {
+        s += String(i ? "\n" : "") + ((constrain(WiFi.RSSI(ix[i]), -100, -50) + 100) * 2) + ","
+             + ((WiFi.encryptionType(ix[i]) == ENC_TYPE_NONE) ? 0 : 1) + "," + WiFi.SSID(ix[i]);
+      }
+    }
+
+    //send string to client
+    _server->send(200, "text/plain", s);
+  }); //_server->on /wifi/list
+
+  _server->on("/wifi/wps", [&]() {
+    _server->send(200, "text/html", "attempting WPS");
+    WiFi.mode(WIFI_STA);
+    WiFi.beginWPSConfig();
+    delay(100);
+    if (WiFi.status() != WL_CONNECTED) {
+      attemptConnection("", "");
+    }
+  }); //_server->on /wifi/wps
+
+  _server->on("/wifi/connect", [&]() {
+    _server->send(200, "text/html", "connecting...");
+    attemptConnection(_server->arg("n"), _server->arg("p"));
+  }); //_server->on /wifi/connect
+
+  _server->on("/wifi/ap", [&](){
+    _server->send(200, "text/html", "access point: "+getApSsid());
+    startApMode();
+  }); //_server->on /wifi/ap
+
+  _server->on("/wifi/rst", [&]() {
+    _server->send(200, "text/html", "Rebooting...");
+    delay(100);
+    ESP.restart();
+  });
+
+//#ifdef WIFI_HTM_PROGMEM
+  _server->on("/wifi.htm", [&]() {
+    _server->send(200, "text/html", wifi_htm);
+  });
+//#endif
+
+}//setupWiFiHandlers
+
+bool PersWiFiManager::begin(const String& ssid, const String& pass) {
+  setupWiFiHandlers();
+  return attemptConnection(ssid, pass); //switched order of these two for return
+} //begin
+
+String PersWiFiManager::getApSsid() {
+  return _apSsid.length() ? _apSsid : "ESP8266";
+} //getApSsid
+
+void PersWiFiManager::setApCredentials(const String& apSsid, const String& apPass) {
+  if (apSsid.length()) _apSsid = apSsid;
+  if (apPass.length() >= 8) _apPass = apPass;
+} //setApCredentials
+
+void PersWiFiManager::onConnect(WiFiChangeHandlerFunction fn) {
+  _connectHandler = fn;
+}
+
+void PersWiFiManager::onAp(WiFiChangeHandlerFunction fn) {
+  _apHandler = fn;
+}
+
+

+ 52 - 0
Software/src/WiFiPCController/PersWiFiManager.h

@@ -0,0 +1,52 @@
+#ifndef PERSWIFIMANAGER_H
+#define PERSWIFIMANAGER_H
+
+#include <ESP8266WiFi.h>
+#include <ESP8266WebServer.h>
+#include <DNSServer.h>
+
+#define WIFI_CONNECT_TIMEOUT 60
+
+class PersWiFiManager {
+
+  public:
+
+    typedef std::function<void(void)> WiFiChangeHandlerFunction;
+
+    PersWiFiManager(ESP8266WebServer& s, DNSServer& d);
+
+    bool attemptConnection(const String& ssid = "", const String& pass = "");
+
+    void setupWiFiHandlers();
+
+    bool begin(const String& ssid = "", const String& pass = "");
+
+    String getApSsid();
+
+    void setApCredentials(const String& apSsid, const String& apPass = "");
+
+    void setConnectNonBlock(bool b);
+
+    void handleWiFi();
+
+    void startApMode();
+
+    void onConnect(WiFiChangeHandlerFunction fn);
+
+    void onAp(WiFiChangeHandlerFunction fn);
+
+  private:
+    ESP8266WebServer * _server;
+    DNSServer * _dnsServer;
+    String _apSsid, _apPass;
+
+    bool _connectNonBlock;
+    unsigned long _connectStartTime;
+
+    WiFiChangeHandlerFunction _connectHandler;
+    WiFiChangeHandlerFunction _apHandler;
+
+};//class
+
+#endif
+

+ 263 - 0
Software/src/WiFiPCController/WiFiPCController.ino

@@ -0,0 +1,263 @@
+
+// pre compiletime config
+#define SPIFFS_DBG
+#define SPIFFS_USE_MAGIC
+
+#define FIRMWARE_NAME "WiFiPCController"
+#define VERSION "1.0.0"
+#define COPYRIGHT "Flo Kra"
+
+// default values, can later be overridden via configuration
+#define DEVICE_NAME "WiFiPCController-1"
+#define MQTT_SERVER "mqtt.lan"
+#define MQTT_PORT 1883
+#define MQTT_TOPIC_IN "Test/PCController1/cmd"
+#define MQTT_TOPIC_OUT "Test/PCController1/status"
+
+#define CLEARCONF_TOKEN "TUES"
+
+#define PIN_OUT_POWERSW 5
+#define PIN_OUT_RESETSW 4
+#define PIN_IN_PWRLED 14
+
+// default logic levels
+#define OUT_POWERSW_ONSTATE HIGH
+#define OUT_RESETSW_ONSTATE HIGH
+#define IN_PWRLED_ONSTATE LOW
+
+#include "PersWiFiManager.h"
+#include <ArduinoJson.h>
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <ESP8266HTTPUpdateServer.h>
+#include "PubSubClient.h"
+#include <DNSServer.h>
+#include <FS.h>
+
+boolean serialdebug = true;
+boolean mqttdebug = true;
+
+// config variables - do not change here!
+// confWeb
+char deviceName[31];
+char http_user[31];
+char http_pass[31];
+boolean http_user_auth = false;
+char http_token[31];
+char http_user1[31];
+char http_pass1[31];
+char http_user2[31];
+char http_pass2[31];
+
+// confMqtt
+char mqtt_server[41];
+int mqtt_port = MQTT_PORT;
+char mqtt_user[31];
+char mqtt_pass[31];
+char mqtt_topic_in[51];
+char mqtt_topic_out[51];
+char mqtt_willTopic[51];
+int mqtt_willQos = 2;
+boolean mqtt_willRetain = false;
+char mqtt_willMsg[31];
+char mqtt_connMsg[31];
+boolean mqtt_outRetain = false;
+
+char mqtt_topic_in_pcpwr[64];
+char mqtt_topic_in_pcpwrh[64];
+char mqtt_topic_in_pcrst[64];
+char mqtt_topic_in_pconsleep[64];
+
+// global variables
+byte mqttMode = 0;
+unsigned long mqttLastReconnectAttempt = 0;
+int mqttReconnectAttempts = 0;
+int mqttReconnects = 0;
+boolean mqttConnected = false;
+unsigned long mqttLastHeartbeat;
+boolean mqttInTopicSubscribed = false;
+char cmdPayload[101]; // buffer for commands
+boolean cmdInQueue = false; // command is queued and will be processed next loop() run
+
+bool saveConfigWebToFlash = false;
+bool saveConfigMqttToFlash = false;
+//bool saveConfigHwToFlash = false;
+
+
+char tmp_topic_pub[51];
+
+boolean pwrSwitchStateOn;
+unsigned long pwrSwitchOnAt;
+unsigned int pwrSwitchOffAfter;
+
+boolean resSwitchStateOn;
+unsigned long resSwitchOnAt;
+unsigned int resSwitchOffAfter;
+
+boolean in_pwrled_lastState = LOW;
+boolean in_pwrled_currState = LOW;
+unsigned long in_pwrled_millis = 0;
+unsigned long PC_standby_pending_millis = 0;
+int PCstate = 0;
+int PCstate_lastPublished = 0;
+
+unsigned int sysUptime_days = 0;
+unsigned int sysUptime_hours = 0;
+unsigned int sysUptime_mins = 0;
+char uptimeStr[15];
+
+WiFiClient espClient;
+
+void mqttCallback(char* topic, byte* payload, unsigned int length);
+PubSubClient mqttclient(espClient);
+
+ESP8266WebServer httpServer(80);
+DNSServer dnsServer;
+PersWiFiManager persWM(httpServer, dnsServer);
+
+ESP8266HTTPUpdateServer httpUpdater;
+
+
+void setup() {
+  Serial.begin(115200);
+  delay(500);
+  Serial.println();
+  Serial.print(FIRMWARE_NAME);
+  Serial.print(" v");
+  Serial.print(VERSION);
+  Serial.println(COPYRIGHT);
+  Serial.println("starting...");
+
+  pinMode(PIN_OUT_POWERSW, OUTPUT);
+  digitalWrite(PIN_OUT_POWERSW, !OUT_POWERSW_ONSTATE);
+  pinMode(PIN_OUT_RESETSW, OUTPUT);
+  digitalWrite(PIN_OUT_RESETSW, !OUT_RESETSW_ONSTATE);
+
+  pinMode(PIN_IN_PWRLED, INPUT_PULLUP);
+  //delay(50);
+  //in_pwrled_lastState=digitalRead(PIN_IN_PWRLED);
+
+  strlcpy(deviceName, DEVICE_NAME, 31);
+  strlcpy(mqtt_server, MQTT_SERVER, 41);
+  strlcpy(mqtt_topic_in, MQTT_TOPIC_IN, 51);
+  strlcpy(mqtt_topic_out, MQTT_TOPIC_OUT, 51);
+  Serial.println("default config values loaded..");
+
+
+  Serial.println("Mounting FS...");
+  if (!SPIFFS.begin()) {
+    Serial.println("Failed to mount file system");
+    return;
+  }
+
+
+  //SPIFFS.format();
+  //Serial.print("Format SPIFFS complete.");
+
+  if (!SPIFFS.exists("/formatted")) {
+    Serial.println("Please wait 30 secs for SPIFFS to be formatted");
+    SPIFFS.format();
+    Serial.println("Spiffs formatted");
+
+    File f = SPIFFS.open("/formatted", "w");
+    if (!f) {
+      Serial.println("file open failed");
+    } else {
+      f.println("Format Complete");
+    }
+    f.close();
+  } else {
+    Serial.println("SPIFFS is formatted. Moving along...");
+  }
+
+  Serial.println("files in SPIFFS:");
+  Dir dir = SPIFFS.openDir("/");
+  while (dir.next()) {
+    Serial.print(dir.fileName());
+    File f = dir.openFile("r");
+    Serial.print("  ");
+    Serial.println(f.size());
+    f.close();
+  }
+  Serial.println("---");
+
+
+  // load config from SPIFFS if files exist
+  if (!loadConfigWeb()) {
+    Serial.println("Failed to load confWeb.json");
+  } else {
+    Serial.println("confWeb.json loaded");
+  }
+
+  if (!loadConfigMqtt()) {
+    Serial.println("Failed to load confWeb.json");
+  } else {
+    Serial.println("confWeb.json loaded");
+  }
+
+  delay(500);
+
+  //optional code handlers to run everytime wifi is connected...
+  persWM.onConnect([]() {
+    Serial.print("wifi connected to ");
+    Serial.print(WiFi.SSID());
+    Serial.print(", IP: ");
+    Serial.println(WiFi.localIP());
+  });
+  //...or AP mode is started
+  persWM.onAp([]() {
+    Serial.print("AP MODE: ");
+    Serial.println(persWM.getApSsid());
+  });
+
+  //sets network name for AP mode
+  persWM.setApCredentials(DEVICE_NAME);
+  //persWM.setApCredentials(DEVICE_NAME, "password"); optional password
+
+  //make connecting/disconnecting non-blocking
+  persWM.setConnectNonBlock(true);
+
+  //in non-blocking mode, program will continue past this point without waiting
+  persWM.begin();
+  delay(500);
+
+  httpServerInit();
+
+  mqttPrepareConnection();
+  mqttClientInit();
+
+  buildUptimeString();
+
+  Serial.println("setup complete.");
+  delay(1000);
+} //void setup
+
+
+
+void loop() {
+  checkMillis();
+
+  persWM.handleWiFi(); //in non-blocking mode, handleWiFi must be called in the main loop
+
+  yield();
+
+  dnsServer.processNextRequest();
+
+  httpServer.handleClient();
+
+  yield();
+  mqttHandleConnection();
+  yield();
+  evalCmd();
+
+  PC_pwrSwitchOffAfterTimeout();
+  PC_resSwitchOffAfterTimeout();
+
+  if (Serial.available()) {
+    serialEvent();
+    yield();
+  }
+
+} //void loop

BIN
Software/src/WiFiPCController/WiFiPCController.ino.d1_mini - Copy.bin


BIN
Software/src/WiFiPCController/WiFiPCController.ino.d1_mini.bin


+ 139 - 0
Software/src/WiFiPCController/commands.ino

@@ -0,0 +1,139 @@
+
+#define SER_INPUT_SIZE 70
+char serBuffer[SER_INPUT_SIZE + 1];    // Serial Input-Buffer
+int serBufferCount;    // Anzahl der eingelesenen Zeichen
+
+void serialEvent() {
+  char ch = Serial.read();
+  serBuffer[serBufferCount] = ch;
+  serBufferCount++;
+
+  if (ch == 13 || ch == 10) { // ASCII code 13 = "CR",  10 = "LF"
+    serBuffer[serBufferCount] = '\0'; // string nullterminieren
+    Serial.print("serial cmd: ");
+    Serial.println(serBuffer);
+    strlcpy(cmdPayload, serBuffer, 101);
+    cmdInQueue = true;
+    evalCmd();
+    serBufferCount = 0;
+  }
+}
+
+
+
+void evalCmd() {
+  if (cmdInQueue) {
+
+    //Serial.print("cmdPayload: ");
+    //Serial.println(cmdPayload);
+
+    if (strncmp(cmdPayload, "PC_POWER_H", 10) == 0 && strlen(cmdPayload) == 10) {
+      PC_pwrSwitchHold();
+    }
+    else if (strncmp(cmdPayload, "PC_POWER", 8) == 0 && strlen(cmdPayload) == 8) {
+      PC_pwrSwitchShort();
+    }
+    else if (strncmp(cmdPayload, "PC_RESET", 8) == 0 && strlen(cmdPayload) == 8) {
+      PC_resSwitch();
+    }
+    else if (strncmp(cmdPayload, "PC_ON", 5) == 0 && strlen(cmdPayload) == 5) {
+      PC_toOn();
+    }
+    else if (strncmp(cmdPayload, "PC_SLEEP", 10) == 0 && strlen(cmdPayload) == 10) {
+      PC_toSleep();
+    }
+
+    else if (strncmp(cmdPayload, "loadconf", 8) == 0) {
+      loadConfigWeb();
+      loadConfigMqtt();
+      mqttPrepareConnection();
+    }
+
+    else if (strncmp(cmdPayload, "set ", 4) == 0) {
+      char buf[81];
+      char setconfCmd[16];
+      char setconfPayload[62];
+
+      int len = strlen(cmdPayload) - 4;
+      for (int i = 0; i < len; i++) {
+        if (i < 81) buf[i] = cmdPayload[i + 4];
+      }
+      if (len <= 81) buf[len] = '\0';
+      else buf[81] = '\0';
+
+      //      Serial.print("Buf: ");
+      //      Serial.println(buf);
+
+      int setconfCmdLen = 0;
+      for (int i = 0; i < len; i++) {
+        if (buf[i] == 32 || buf[i] == '\0') break; // if SPACE command name is finished, if \0 command parameter is missing
+        setconfCmd[i] = buf[i];
+        setconfCmdLen++;
+      }
+      setconfCmd[setconfCmdLen] = '\0';
+      yield();
+
+      int setconfPayloadLen = 0;
+      for (int i = 0; i < len; i++) {
+        if (buf[i + setconfCmdLen + 1] == 32 || buf[i + setconfCmdLen + 1] == '\0') break; // if SPACE command name is finished, if \0 command parameter is missing
+        setconfPayload[i] = buf[i + setconfCmdLen + 1];
+        setconfPayloadLen++;
+      }
+      setconfPayload[setconfPayloadLen] = '\0';
+
+      setConfig(setconfCmd, setconfPayload);
+
+      //      else if (strncmp(cmdPayload, "set wifissid ", 13) == 0) {
+      //        char buf[50];
+      //        int len = strlen(cmdPayload) - 13;
+      //        for (int i = 0; i < len; i++) {
+      //          buf[i] = cmdPayload[i + 13];
+      //        }
+      //        buf[len - 1] = '\0';
+      //        Serial.print("WiFi SSID: '");
+      //        Serial.print(buf);
+      //        Serial.println("' set");
+      //      }
+      //      else if (strncmp(cmdPayload, "set wifipass ", 13) == 0) {
+      //        char buf[50];
+      //        int len = strlen(cmdPayload) - 13;
+      //        for (int i = 0; i < len; i++) {
+      //          buf[i] = cmdPayload[i + 13];
+      //        }
+      //        buf[len - 1] = '\0';
+      //        Serial.print("WiFi Password: '");
+      //        Serial.print(buf);
+      //        Serial.println("' set");
+      //      }
+    }
+    else if (strncmp(cmdPayload, "restart", 7) == 0) {
+      Serial.print("restarting...");
+      delay(100);
+      ESP.restart();
+    }
+    else if (strncmp(cmdPayload, "save", 4) == 0) {
+      saveConfigWeb();
+      yield();
+      saveConfigMqtt();
+      yield();
+      Serial.println("saved config to SPIFFS");
+      Serial.println("reloading config to check...");
+      loadConfigWeb();
+      yield();
+      loadConfigMqtt();
+      yield();
+      //      loadConfigHw();
+      //      yield();
+    }
+    else if (strncmp(cmdPayload, "getconf", 7) == 0) {
+      printConfigWeb();
+      printConfigMqtt();
+    }
+    else if (strncmp(cmdPayload, "delconf", 7) == 0) {
+      deleteConfig();
+    }
+
+
+    cmdInQueue = false;
+  }
+}

+ 380 - 0
Software/src/WiFiPCController/config.ino

@@ -0,0 +1,380 @@
+
+bool setConfig(char* param, char* value) {
+  // sets the corresponding config variable for 'param' to new value
+  // does not trigger saving to flash
+  // does not distinguish between config and config2 as this is only split on flash and web-interface
+
+  Serial.print("setConfig - '");
+  Serial.print(param);
+  Serial.print("' to '");
+  Serial.print(value);
+  Serial.println("'");
+
+  //confdata
+  if ( strcmp(param, "devName") == 0 ) {
+    strlcpy(deviceName, value, 31);
+  }
+  else if ( strcmp(param, "apiToken") == 0 ) {
+    strlcpy(http_token, value, 31);
+  }
+  else if ( strcmp(param, "httpUA") == 0 ) {
+    strlcpy(http_user, value, 31);
+  }
+  else if ( strcmp(param, "httpPA") == 0 ) {
+    strlcpy(http_pass, value, 31);
+  }
+  else if ( strcmp(param, "httpAuth") == 0 ) {
+    if (atoi(value) == 1) http_user_auth = true;
+    else http_user_auth = false;
+  }
+  else if ( strcmp(param, "httpU1") == 0 ) {
+    strlcpy(http_user1, value, 31);
+  }
+  else if ( strcmp(param, "httpP1") == 0 ) {
+    strlcpy(http_pass1, value, 31);
+  }
+  else if ( strcmp(param, "httpU2") == 0 ) {
+    strlcpy(http_user2, value, 31);
+  }
+  else if ( strcmp(param, "httpP2") == 0 ) {
+    strlcpy(http_pass2, value, 31);
+  }
+  else if ( strcmp(param, "mqttHost") == 0 ) {
+    strlcpy(mqtt_server, value, 41);
+    //mqtt_server[40] = '\0';
+  }
+  else if ( strcmp(param, "mqttPort") == 0 ) {
+    mqtt_port = atoi(value);
+  }
+  else if ( strcmp(param, "mqttUser") == 0 ) {
+    strlcpy(mqtt_user, value, 31);
+  }
+  else if ( strcmp(param, "mqttPass") == 0 ) {
+    strlcpy(mqtt_pass, value, 31);
+  }
+  else if ( strcmp(param, "inTop") == 0 ) {
+    strlcpy(mqtt_topic_in, value, 51);
+  }
+  else if ( strcmp(param, "outTop") == 0 ) {
+    strlcpy(mqtt_topic_out, value, 51);
+  }
+  else if ( strcmp(param, "outRet") == 0 ) {
+    //Serial.print("outRet DEBG: '"); Serial.print(value); Serial.print("' -> ");
+    if (atoi(value) == 1) mqtt_outRetain = true;
+    else mqtt_outRetain = false;
+    //Serial.println(mqtt_outRetain);
+  }
+  else if ( strcmp(param, "willTop") == 0 ) {
+    strlcpy(mqtt_willTopic, value, 51);
+  }
+  else if ( strcmp(param, "willQos") == 0 ) {
+    int tmpval = atoi(value);
+    if (tmpval >= 0 && tmpval <= 2) mqtt_willQos = tmpval;
+  }
+  else if ( strcmp(param, "willRet") == 0 ) {
+    if (atoi(value) == 1) mqtt_willRetain = true;
+    else mqtt_willRetain = false;
+  }
+  else if ( strcmp(param, "willMsg") == 0 ) {
+    strlcpy(mqtt_willMsg, value, 31);
+  }
+  else if ( strcmp(param, "connMsg") == 0 ) {
+    strlcpy(mqtt_connMsg, value, 31);
+  }
+}
+
+void printConfigWeb() {
+  // prints current config vars to serial
+  Serial.println("\nconfDataWeb:");
+  Serial.print("devName: ");
+  Serial.println(deviceName);
+  Serial.print("apiToken: ");
+  Serial.println(http_token);
+  Serial.print("httpUA: ");
+  Serial.println(http_user);
+  Serial.print("httpPA: ");
+  Serial.println(http_pass);
+  Serial.print("httpAuth: ");
+  Serial.println(http_user_auth);
+  Serial.print("httpU1: ");
+  Serial.println(http_user1);
+  Serial.print("httpP1: ");
+  Serial.println(http_pass1);
+  Serial.print("httpU2: ");
+  Serial.println(http_user2);
+  Serial.print("httpP2: ");
+  Serial.println(http_pass2);
+}
+
+void printConfigMqtt() {
+  // prints current config vars to serial
+  Serial.println("\nconfDataMqtt:");
+  Serial.print("mqttHost: ");
+  Serial.println(mqtt_server);
+  Serial.print("mqttPort: ");
+  Serial.println(mqtt_port);
+  Serial.print("mqttUser: ");
+  Serial.println(mqtt_user);
+  Serial.print("mqttPass: ");
+  Serial.println(mqtt_pass);
+  Serial.print("inTop: ");
+  Serial.println(mqtt_topic_in);
+  Serial.print("outTop: ");
+  Serial.println(mqtt_topic_out);
+  Serial.print("outRet: ");
+  Serial.println(mqtt_outRetain);
+  Serial.print("willTop: ");
+  Serial.println(mqtt_willTopic);
+  Serial.print("willQos: ");
+  Serial.println(mqtt_willQos);
+  Serial.print("willRet: ");
+  Serial.println(mqtt_willRetain);
+  Serial.print("willMsg: ");
+  Serial.println(mqtt_willMsg);
+  Serial.print("connMsg: ");
+  Serial.println(mqtt_connMsg);
+}
+
+bool loadConfigWeb() { // loadConfigWeb
+  if (SPIFFS.exists("/confWeb.json")) {
+    File configFile = SPIFFS.open("/confWeb.json", "r");
+    if (!configFile) {
+      Serial.println("ERR: Failed to open file /confWeb.json");
+      return false;
+    }
+    else {
+      Serial.println("file /confWeb.json opened");
+      size_t size = configFile.size();
+
+      Serial.print("file size: ");
+      Serial.println(size);
+
+      //      Serial.println("file content:");
+      //
+      //      while (configFile.available()) {
+      //        //Lets read line by line from the file
+      //        String line = configFile.readStringUntil('\n');
+      //        Serial.println(line);
+      //      }
+      //      Serial.println();
+
+      if (size > 1000) {
+        Serial.println("Config file size is too large");
+        return false;
+      }
+
+      Serial.println("allocate buffer");
+      // Allocate a buffer to store contents of the file.
+      std::unique_ptr<char[]> buf(new char[size]);
+
+      Serial.println("read file bytes to buffer");
+      // We don't use String here because ArduinoJson library requires the input
+      // buffer to be mutable. If you don't use ArduinoJson, you may as well
+      // use configFile.readString instead.
+      configFile.readBytes(buf.get(), size);
+
+      StaticJsonBuffer<1050> jsonBuffer;
+      JsonObject& json = jsonBuffer.parseObject(buf.get());
+
+      if (!json.success()) {
+        Serial.println("Failed to parse config file");
+        return false;
+      }
+
+      strlcpy(deviceName, json["devName"] | "", 31);
+      strlcpy(http_token, json["apiToken"] | "", 31);
+      strlcpy(http_user, json["httpUA"] | "", 31);
+      strlcpy(http_pass, json["httpPA"] | "", 31);
+
+      if (atoi(json["httpAuth"] | "0") == 1) http_user_auth = true;
+      else http_user_auth = false;
+      
+      strlcpy(http_user1, json["httpU1"] | "", 31);
+      strlcpy(http_pass1, json["httpP1"] | "", 31);
+      strlcpy(http_user2, json["httpU2"] | "", 31);
+      strlcpy(http_pass2, json["httpP2"] | "", 31);
+      
+      Serial.println("Loaded config values:");
+      printConfigWeb();
+      return true;
+    }
+    configFile.close();
+  }
+  else {
+    Serial.println("file /confWeb.json file does not exist");
+    return false;
+  }
+
+
+} // loadConfigWeb
+
+
+
+bool loadConfigMqtt() { // loadConfigMqtt
+  if (SPIFFS.exists("/confMqtt.json")) {
+    File configFile = SPIFFS.open("/confMqtt.json", "r");
+    if (!configFile) {
+      Serial.println("ERR: Failed to open file /confMqtt.json");
+      return false;
+    }
+    else {
+      Serial.println("file /confMqtt.json opened");
+      size_t size = configFile.size();
+
+      Serial.print("file size: ");
+      Serial.println(size);
+
+      //      Serial.println("file content:");
+      //
+      //      while (configFile.available()) {
+      //        //Lets read line by line from the file
+      //        String line = configFile.readStringUntil('\n');
+      //        Serial.println(line);
+      //      }
+      //      Serial.println();
+
+      if (size > 1000) {
+        Serial.println("Config file size is too large");
+        return false;
+      }
+
+      Serial.println("allocate buffer");
+      // Allocate a buffer to store contents of the file.
+      std::unique_ptr<char[]> buf(new char[size]);
+
+      Serial.println("read file bytes to buffer");
+      // We don't use String here because ArduinoJson library requires the input
+      // buffer to be mutable. If you don't use ArduinoJson, you may as well
+      // use configFile.readString instead.
+      configFile.readBytes(buf.get(), size);
+
+      StaticJsonBuffer<1050> jsonBuffer;
+      JsonObject& json = jsonBuffer.parseObject(buf.get());
+
+      if (!json.success()) {
+        Serial.println("Failed to parse config file");
+        return false;
+      }
+      
+      strlcpy(mqtt_server, json["mqttHost"] | "", 41);
+      mqtt_port = atoi(json["mqttPort"] | "1883");
+      strlcpy(mqtt_user, json["mqttUser"] | "", 31);
+      strlcpy(mqtt_pass, json["mqttPass"] | "", 31);
+      strlcpy(mqtt_topic_in, json["inTop"] | "", 51);
+      strlcpy(mqtt_topic_out, json["outTop"] | "", 51);
+
+      //char outrettmp[8];
+      //strcpy(outrettmp, json["outRet"]);
+      //Serial.print("outRet='"); Serial.print(outrettmp); Serial.print("', atoi(outRet)='"); Serial.print(atoi(outrettmp));Serial.println("'");
+      if (atoi(json["outRet"] | "0") == 1) mqtt_outRetain = true;
+      else mqtt_outRetain = false;
+
+      strlcpy(mqtt_willTopic, json["willTop"] | "", 51);
+      mqtt_willQos = atoi(json["willQos"] | "0");
+
+      if (atoi(json["willRet"] | "0") == 1) mqtt_willRetain = true;
+      else mqtt_willRetain = false;
+
+      strlcpy(mqtt_willMsg, json["willMsg"] | "", 31);
+      strlcpy(mqtt_connMsg, json["connMsg"] | "", 31);
+
+      Serial.println("Loaded config values:");
+      printConfigMqtt();
+      return true;
+    }
+    configFile.close();
+  }
+  else {
+    Serial.println("file /confMqtt.json file does not exist");
+    return false;
+  }
+} // loadConfigMqtt
+
+
+bool saveConfigWeb() { // safeConfig
+  StaticJsonBuffer<1050> jsonBuffer;
+  JsonObject& json = jsonBuffer.createObject();
+
+  json["devName"] = deviceName;
+  json["apiToken"] = http_token;
+  json["httpUA"] = http_user;
+  json["httpPA"] = http_pass;
+  
+  if(http_user_auth) json["httpAuth"] = 1;
+  else json["httpAuth"] = 0;
+  
+  json["httpU1"] = http_user1;
+  json["httpP1"] = http_pass1;
+  json["httpU2"] = http_user2;
+  json["httpP2"] = http_pass2;
+
+  yield();
+
+  File configFile = SPIFFS.open("/confWeb.json", "w");
+  if (!configFile) {
+    Serial.println("Failed to open file confWeb.json for writing");
+    return false;
+  }
+
+  json.printTo(configFile);
+  return true;
+} // safeConfig
+
+bool saveConfigMqtt() { // safeConfig
+  StaticJsonBuffer<1050> jsonBuffer;
+  JsonObject& json = jsonBuffer.createObject();
+  json["mqttHost"] = mqtt_server;
+  json["mqttPort"] = mqtt_port;
+  json["mqttUser"] = mqtt_user;
+  json["mqttPass"] = mqtt_pass;
+  json["inTop"] = mqtt_topic_in;
+  json["outTop"] = mqtt_topic_out;
+
+  if(mqtt_outRetain) json["outRet"] = 1;
+  else json["outRet"] = 0;
+  
+  json["willTop"] = mqtt_willTopic;
+  json["willQos"] = mqtt_willQos;
+  
+  if(mqtt_willRetain) json["willRet"] = 1;
+  else json["willRet"] = 0;
+  
+  json["willMsg"] = mqtt_willMsg;
+  json["connMsg"] = mqtt_connMsg;
+
+  yield();
+
+  File configFile = SPIFFS.open("/confMqtt.json", "w");
+  if (!configFile) {
+    Serial.println("Failed to open file confMqtt.json for writing");
+    return false;
+  }
+
+  json.printTo(configFile);
+  return true;
+} // safeConfig
+
+
+void checkSaveConfigTriggered() {
+  if (saveConfigWebToFlash) {
+    saveConfigWebToFlash = false;
+    saveConfigWeb();
+  }
+  if (saveConfigMqttToFlash) {
+    saveConfigMqttToFlash = false;
+    saveConfigMqtt();
+  }
+  //  if (saveConfigHwToFlash) {
+  //    saveConfigHwToFlash = false;
+  //    saveConfigHw();
+  //  }
+}
+
+void deleteConfig() {
+  Serial.println("deleting configuration");
+  if (SPIFFS.remove("/formatComplete.txt")) {
+    Serial.println("config will be deleted after reboot");
+    delay(100);
+    ESP.restart();
+  }
+}

+ 782 - 0
Software/src/WiFiPCController/httpServer.ino

@@ -0,0 +1,782 @@
+//extern ESP8266WebServer httpServer;
+
+static const char html_head_part1[] PROGMEM =
+  "<html><head>"
+  "<meta charset='utf-8'>"
+  "<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
+  "<title>WiFi-PC-Controller - ";
+
+static const char html_head_part2[] PROGMEM =
+  "</title>"
+  "<style>div,fieldset,input,select{padding:5px;font-size:1em;}fieldset{background:#f2f2f2;}p{margin:0.5em 0;}input{width:100%;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;background:#ffffff;color:#000000;}input[type=checkbox],input[type=radio]{width:1em;margin-right:6px;vertical-align:-1px;}input[type=range]{width:99%;}select{width:100%;background:#ffffff;color:#000000;}textarea{resize:none;width:98%;height:318px;padding:5px;overflow:auto;background:#ffffff;color:#000000;}body{text-align:center;font-family:verdana,sans-serif;background:#ffffff;}td{padding:0px;}button{border:0;border-radius:0.3rem;background:#1fa3ec;color:#ffffff;line-height:2.4rem;font-size:1.2rem;width:100%;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;}button:hover{background:#0e70a4;}.bred{background:#d43535;}.bred:hover{background:#931f1f;}.bgrn{background:#47c266;}.bgrn:hover{background:#5aaf6f;}a{color:#1fa3ec;text-decoration:none;}.p{float:left;text-align:left;}.q{float:right;text-align:right;}.r{border-radius:0.3em;padding:2px;margin:6px 2px;}</style>";
+
+static const char html_root_script[] PROGMEM = R"(
+<script>
+  function g(i) { return document.getElementById(i) };
+  var xhttp, updateTime, reqTime, reqFin;
+  function sendBtn(btn, conf) {
+  var frmn='BtnFrm'+btn;
+  var form = g(frmn);
+  if(conf !== undefined) {
+  if(confirm(conf)) return transmit(form);
+    else return false;
+    }
+    else return transmit(form);
+  }
+  function transmit(f) {
+    if (!xhttp) { 
+      reqTime = 0;
+      reqFin = false;
+      xhttp = new XMLHttpRequest();
+      xhttp.timeout = 1000;
+    xhttp.overrideMimeType("application/json");
+      xhttp.open('POST', 'api');
+      xhttp.send(f ? (new FormData(f)) : '');
+      xhttp.onreadystatechange = function () {
+        if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+          var data = JSON.parse(xhttp.responseText);
+          g('PCstate').innerHTML = data.PCstate;
+      if(data.PCstate == "ON") {
+        g('PCstate').style.fontWeight = "bold";
+      }
+      else g('PCstate').style.fontWeight = "normal";
+      if(data.ssid !== undefined) g('ssid').innerHTML = data.ssid;
+      if(data.mqttstate !== undefined) { 
+        if(data.mqttstate == "CONNECTED" && data.mqtthost !== undefined) {
+          g('mqttstate').innerHTML = data.mqttstate + ' to <i>' + data.mqtthost + '</i>';
+          }
+        else g('mqttstate').innerHTML = data.mqttstate;
+      }
+      if(data.uptime !== undefined) g('uptime').innerHTML = data.uptime;
+      if(data.mqttreconn !== undefined) g('mqttreconn').innerHTML = data.mqttreconn;
+          xhttp = null;
+          updateTime = 0;
+          reqFin = true;
+          }
+        else {
+          if(!reqFin && reqTime > 10) {
+            xhttp = null;
+            reqFin = true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+  function init() {
+    transmit();
+  }
+  setInterval(transmit, 2500);
+</script>
+)"; // html_root_script
+
+static const char html_confweb_script[] PROGMEM = R"(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    updCbxVal(g('httpAuth'));
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confdweb');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('devName').value = data.devName;
+        g('apiToken').value = data.apiToken;
+        g('httpUA').value = data.httpUA;
+        setCbx(g('httpAuth'), data.httpAuth);
+        g('httpU1').value = data.httpU1;
+        g('httpU2').value = data.httpU2;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    updCbxVal(g('httpAuth'));
+    if(g('httpPA').value != g('httpPAC').value) {
+      alert("Admin password verification failed!");
+    }
+    else g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+    setCbx(g('httpPASet'), 0);
+    setCbx(g('httpP1Set'), 0);
+    setCbx(g('httpP2Set'), 0);
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)"; // html_confweb_script
+
+static const char html_confmqtt_script[] PROGMEM = R"(
+<script>
+  function g(i) { return document.getElementById(i) };
+  function sp(i){g(i).type=(g(i).type==='text'?'password':'text');}
+  var xhttp, reqTime, reqFin;
+  function setCbx(el, da) {
+    if(da == '1') {
+      el.checked = true;
+      el.style.visibility = 'visible';
+    }
+    else {
+      el.checked = false;
+      el.style.visibility = 'visible';
+    }
+  }
+  function updCbxVal(el) {
+    if (el.checked) el.value = '1';
+    else {
+      el.checked = true;
+    el.value = '0';
+      el.style.visibility = 'hidden';
+    }
+  }
+  
+  function transmit(f) {
+    if (!xhttp) {   
+    reqTime = 0;
+    reqFin = false;
+    updCbxVal(g('outRet'));
+    updCbxVal(g('willRet'));
+    xhttp = new XMLHttpRequest();
+    xhttp.timeout = 1000;
+    xhttp.overrideMimeType('application/json');
+    xhttp.open('POST', 'confdmqtt');
+    xhttp.send(f ? (new FormData(f)) : '');
+    xhttp.onreadystatechange = function () {
+      if (xhttp.readyState === XMLHttpRequest.DONE && xhttp.status === 200) {
+        var data = JSON.parse(xhttp.responseText);
+        g('mqttHost').value = data.mqttHost;
+        g('mqttPort').value = data.mqttPort;
+        g('mqttUser').value = data.mqttUser;
+        g('inTop').value = data.inTop;
+        g('outTop').value = data.outTop;
+        g('willTop').value = data.willTop;
+        g('willQos').value = data.willQos;
+        setCbx(g('outRet'), data.outRet);
+        setCbx(g('willRet'), data.willRet);
+        g('willMsg').value = data.willMsg;
+        g('connMsg').value = data.connMsg;
+        xhttp = null;
+        reqFin = true;
+       }
+       else {
+         if(!reqFin && reqTime > 10) {
+           xhttp = null;
+           reqFin = true;
+         }
+       }
+     }
+   }
+    return false;
+  }
+  //transmit();
+  function saveConf() {
+    updCbxVal(g('outRet'));
+    updCbxVal(g('willRet'));
+  g('frmConf').submit();
+  }
+  function init() {
+    transmit();
+    setCbx(g('mqttPassSet'), 0);
+  }
+  setInterval(function () { ++reqTime; }, 1000);
+</script>
+)"; // html_confmqtt_script
+
+static const char html_head_part3[] PROGMEM =
+  R"(</head>)"; // html_head_part3
+
+static const char html_bodytag_jsinit[] PROGMEM = R"(
+<body onload='init()'>)";
+
+static const char html_bodytag[] PROGMEM = R"(
+<body>)";
+
+static const char html_body_part1[] PROGMEM = R"(
+<div style='text-align:left;display:inline-block;color:#000000;min-width:340px;'>
+<div style='text-align:center;color:#000000;'><noscript>Please enable JavaScript<br></noscript>
+<h3>WiFi-PC-Controller</h3>
+<h2>
+)"; // html_body_part1
+
+static const char html_body_part2[] PROGMEM = R"(</h2>
+</div>)"; // html_body_part2
+
+static const char html_root_body[] PROGMEM = R"(
+<div id="PCstate" style="text-align:center;font-weight:normal;font-size:50px"></div>
+<p><form id='BtnFrmPwr'><input type='hidden' name='BtnPwr' value='1'><button onclick='return sendBtn("Pwr")'>PC Power-Button</button></form></p>
+<p><form id='BtnFrmPwrH'><input type='hidden' name='BtnPwrH' value='1'><button onclick='return sendBtn("PwrH","Confirm PC Power Hold")' class='button bred'>PC Power-Button HOLD</button></form></p>
+<p><form id='BtnFrmRes'><input type='hidden' name='BtnRes' value='1'><button onclick='return sendBtn("Res", "Confirm PC Reset")' class='button bred'>PC Reset-Button</button></form></p>
+<div></div>
+<div></div>
+)"; // html_root_body
+
+static const char html_root_body_adminonly[] PROGMEM = R"(
+<p><form action='conf' method='get'><button>Configuration</button></form></p>
+<p><form action='update' method='get'><button>Firmware update</button></form></p>
+)"; // html_root_body_adminonly
+
+static const char html_root_body2[] PROGMEM = R"(
+<p><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='button bred'>Restart</button></form></p>
+<hr/>
+<p>WiFi connected to <i><span id='ssid'></span></i></p>
+<p>MQTT <span id='mqttstate'></span></p>
+<p>MQTT reconnects: <span id='mqttreconn'></span></p>
+<p>Uptime: <span id='uptime'></span></p>
+)"; // html_root_body2
+
+static const char html_conf_body[] PROGMEM = R"(
+<p><form action='wifi.htm' method='get'><button>Configure WiFi</button></form></p>
+<p><form action='confweb' method='get'><button>Configure Web</button></form></p>
+<p><form action='confmqtt' method='get'><button>Configure MQTT</button></form></p>
+<div></div>
+<p><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='button bred'>Restart</button></form></p>
+<p><form action='./' method='get'><button>Main Menu</button></form></p>
+)"; // html_conf_body
+
+static const char html_confweb_body[] PROGMEM = R"(
+<fieldset>
+<legend><b>&nbsp;Web Configuration&nbsp;</b></legend>
+<form id='frmConf' action='setConfWeb' method='POST'>
+<p><b>Device Name</b><br><input type='text' name='devName' id='devName'></p>
+<p><b>API Token</b><br><input type='text' name='apiToken' id='apiToken'></p>
+<div><hr></div>
+<p><b>HTTP Admin Username *</b><br><input type='text' name='httpUA' id='httpUA'></p>
+<p><b>HTTP Admin Password *</b><input type='checkbox' id='httpPASet' name='httpPASet' onclick='sp("httpPA")'><br><input type='password' name='httpPA' id='httpPA'></p>
+<p><b>Confirm Password *</b><br><input type='password' name='httpPAC' id='httpPAC'></p>
+<div><hr></div>
+<p><b>Enable User-Authentication *</b>&nbsp;<input type='checkbox' name='httpAuth' id='httpAuth'></p>
+<p><b>HTTP User 1 *</b><br><input type='text' name='httpU1' id='httpU1'></p>
+<p><b>HTTP User 1 Password *</b><input type='checkbox' id='httpP1Set' name='httpP1Set' onclick='sp("httpP1")'><br><input type='password' name='httpP1' id='httpP1'></p>
+<div><hr></div>
+<p><b>HTTP User 2 *</b><br><input type='text' name='httpU2' id='httpU2'></p>
+<p><b>HTTP User 2 Password *</b><input type='checkbox' id='httpP2Set' name='httpP2Set' onclick='sp("httpP2")'><br><input type='password' name='httpP2' id='httpP2'></p>
+</form>
+<p><button onclick='return saveConf()'>Save</button></p>
+<p><button onclick='return transmit()'>Reload</button></p>
+</fieldset>
+<p><form action='conf' method='get'><button>Configuration</button></form></p>
+<p><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='button bred'>Restart</button></form></p>
+)"; // html_confweb_body
+
+static const char html_confmqtt_body[] PROGMEM = R"(
+<fieldset>
+<legend><b>&nbsp;MQTT Configuration&nbsp;</b></legend>
+<form id='frmConf' action='setConfMqtt' method='POST'>
+<p><b>MQTT Server *</b><br><input type='text' name='mqttHost' id='mqttHost'></p>
+<p><b>MQTT Port *</b><br><input type='number' name='mqttPort' id='mqttPort'></p>
+<p><b>MQTT User *</b><br><input type='text' name='mqttUser' id='mqttUser'></p>
+<p><b>MQTT Password *</b><input type='checkbox' id='mqttPassSet' name='mqttPassSet' onclick='sp("mqttPass")'><br><input type='password' name='mqttPass' id='mqttPass'></p>
+<p><b>In Topic *</b><br><input type='text' name='inTop' id='inTop'></p>
+<p><b>Out Topic</b><br><input type='text' name='outTop' id='outTop'></p>
+<p><b>Out Retain *</b>&nbsp;<input type='checkbox' name='outRet' id='outRet'></p>
+<p><b>LastWill Topic *</b><br><input type='text' name='willTop' id='willTop'></p>
+<p><b>LastWill Qos *</b><br><select name='willQos' id='willQos'><option>0</option><option>1</option><option selected='selected'>2</option></select></p>
+<p><b>LastWill Retain *</b>&nbsp;<input type='checkbox' name='willRet' id='willRet'></p>
+<p><b>LastWill Message *</b><br><input type='text' name='willMsg' id='willMsg'></p>
+<p><b>Connect Message *</b><br><input type='text' name='connMsg' id='connMsg'></p>
+</form>
+<p><button onclick='return saveConf()'>Save</button></p>
+<p><button onclick='return transmit()'>Reload</button></p>
+</fieldset>
+<p><form action='conf' method='get'><button>Configuration</button></form></p>
+<p><form action='/' method='get' onsubmit='return confirm("Confirm Restart");'><button name='restart' class='button bred'>Restart</button></form></p>
+)"; // html_confmqtt_body
+
+static const char html_confsaved_body[] PROGMEM = R"(
+<script>setTimeout(function(){window.location.href = '.';}, 7000);</script>
+<div>Config saved. Restarting...</div>
+<p><form action='./' method='get'><button>Main Menu</button></form></p>
+)"; // html_confsaved_body
+
+static const char html_restarting_body[] PROGMEM = R"(
+<script>setTimeout(function(){window.location.href = '.';}, 7000);</script>
+<div>Restarting...</div>
+<p><form action='./' method='get'><button>Main Menu</button></form></p>
+)"; // html_restarting_body
+
+static const char html_footer[] PROGMEM = R"(
+<div style='text-align:right;font-size:11px;'><hr/><a href='https://git.flokra.at/WiFiPCController' target='_blank' style='color:#aaa;'>WiFi-PC-Controller 1.0.0 by Flo Kra</a></div>
+</div></body></html>
+)";
+
+
+
+void httpServerHandleRoot() {
+    //httpServer.send_P(200, "text/html", httpRoot);
+    httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN);   //Enable Chunked Transfer
+    httpServer.send(200, "text/html", html_head_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_head_part2);
+    httpServer.sendContent_P(html_root_script);
+    httpServer.sendContent_P(html_head_part3);
+    httpServer.sendContent_P(html_bodytag_jsinit);
+    httpServer.sendContent_P(html_body_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_body_part2);
+    httpServer.sendContent_P(html_root_body);
+
+    if(httpIsAuthenticatedAdmin()) httpServer.sendContent_P(html_root_body_adminonly);
+    
+    httpServer.sendContent_P(html_root_body2);
+    httpServer.sendContent_P(html_footer);
+    httpServer.sendContent("");
+    httpServer.client().stop();
+}
+
+void httpServerHandleConfPage() {
+    //httpServer.send_P(200, "text/html", httpConfPage);
+    httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN);   //Enable Chunked Transfer
+    httpServer.send(200, "text/html", html_head_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_head_part2);
+    //httpServer.sendContent_P(html_conf_script);
+    httpServer.sendContent_P(html_head_part3);
+    httpServer.sendContent_P(html_bodytag);
+    httpServer.sendContent_P(html_body_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_body_part2);
+    httpServer.sendContent_P(html_conf_body);
+    httpServer.sendContent_P(html_footer);
+    httpServer.sendContent("");
+    httpServer.client().stop();
+}
+
+void httpServerHandleConfWebPage() {
+    //httpServer.send_P(200, "text/html", httpConfPage);
+    httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN);   //Enable Chunked Transfer
+    httpServer.send(200, "text/html", html_head_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_head_part2);
+    httpServer.sendContent_P(html_confweb_script);
+    httpServer.sendContent_P(html_head_part3);
+    httpServer.sendContent_P(html_bodytag_jsinit);
+    httpServer.sendContent_P(html_body_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_body_part2);
+    httpServer.sendContent_P(html_confweb_body);
+    httpServer.sendContent_P(html_footer);
+    httpServer.sendContent("");
+    httpServer.client().stop();
+}
+
+void httpServerHandleConfMqttPage() {
+    //httpServer.send_P(200, "text/html", httpConfPage);
+    httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN);   //Enable Chunked Transfer
+    httpServer.send(200, "text/html", html_head_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_head_part2);
+    httpServer.sendContent_P(html_confmqtt_script);
+    httpServer.sendContent_P(html_head_part3);
+    httpServer.sendContent_P(html_bodytag_jsinit);
+    httpServer.sendContent_P(html_body_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_body_part2);
+    httpServer.sendContent_P(html_confmqtt_body);
+    httpServer.sendContent_P(html_footer);
+    httpServer.sendContent("");
+    httpServer.client().stop();
+}
+
+void httpServerHandleNotFound() {
+  //  if (strlen(http_user) > 0 && strlen(http_pass) > 0) {
+  //    if (!httpServer.authenticate(http_user, http_pass))
+  //      return httpServer.requestAuthentication();
+  httpServer.send(404, "text/plain", "");
+  //}
+}
+
+
+boolean httpIsAuthenticated() {
+  if(http_user_auth) {
+    boolean auth=false;
+    if ((strlen(http_user) > 0 && strlen(http_pass) > 0) || (strlen(http_user1) > 0 && strlen(http_pass1) > 0) || (strlen(http_user2) > 0 && strlen(http_pass2) > 0)) auth=true;
+    if (auth) {
+      if (!httpServer.authenticate(http_user, http_pass) && !httpServer.authenticate(http_user1, http_pass1) && !httpServer.authenticate(http_user2, http_pass2)) return false;
+      else return true;
+    }
+    else return true;
+  }
+  else return true;
+}
+
+boolean httpIsAuthenticatedAdmin() {
+  boolean auth=false;
+  if ((strlen(http_user) > 0 && strlen(http_pass) > 0)) auth=true;
+  if(auth) {
+    if (!httpServer.authenticate(http_user, http_pass)) return false;
+    else return true;
+  }
+  else return true;
+}
+
+void httpSendUnauthorized() {
+  httpServer.send (401, "text/plain", "UNAUTHORIZED");
+}
+
+void httpSend200OK() {
+  httpServer.send (200, "text/plain", "OK");
+}
+
+boolean httpCheckToken() {
+  if (http_token[0] != '\0') { // dont accept empty token
+    if (httpServer.hasArg("token")) {
+      char buf[20];
+      httpServer.arg("token").toCharArray(buf, 20);
+      if (strcmp(buf, http_token) == 0) return true;
+      else return false;
+    }
+  }
+}
+
+
+void httpServerInit() {
+  httpServer.on("/delconf", []() {
+    Serial.println("httpServer.on /delconf");
+    if ( httpIsAuthenticatedAdmin() || (!httpIsAuthenticatedAdmin() && httpCheckToken()) ) {
+        deleteConfig();
+      }
+    else {
+      httpSendUnauthorized();
+    }
+  });
+  httpServer.on("/api", []() {
+    if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
+      //Serial.println("httpServer.on /api");
+      if (httpServer.hasArg("BtnPwr")) {
+        Serial.println("web BtnPwr");
+        PC_pwrSwitchShort();
+      }
+      if (httpServer.hasArg("BtnPwrH")) {
+        Serial.println("web BtnPwrH");
+        PC_pwrSwitchHold();
+      }
+      if (httpServer.hasArg("BtnRes")) {
+        Serial.println("web BtnRes");
+        PC_resSwitch();
+      }
+      yield();
+
+      //build json object of program data
+      StaticJsonBuffer<200> jsonBuffer;
+      JsonObject &json = jsonBuffer.createObject();
+      json["ssid"] = WiFi.SSID();
+      json["devname"] = deviceName;
+      json["uptime"] = uptimeStr;
+      json["freeheap"] = ESP.getFreeHeap();
+
+      //if(mqttclient.state() == 0) json["mqttstate"] = "connected";
+      //else json["mqttstate"] = mqttclient.state();
+      if(mqttclient.state() == -4) json["mqttstate"] = "CONNECTION_TIMEOUT";
+      else if(mqttclient.state() == -3) json["mqttstate"] = "CONNECTION_LOST";
+      else if(mqttclient.state() == -2) json["mqttstate"] = "CONNECT_FAILED";
+      else if(mqttclient.state() == -1) json["mqttstate"] = "DISCONNECTED";
+      else if(mqttclient.state() == 0) json["mqttstate"] = "CONNECTED";
+      else if(mqttclient.state() == 1) json["mqttstate"] = "CONNECT_BAD_PROTOCOL";
+      else if(mqttclient.state() == 2) json["mqttstate"] = "CONNECT_BAD_CLIENT_ID";
+      else if(mqttclient.state() == 3) json["mqttstate"] = "CONNECT_UNAVAILABLE";
+      else if(mqttclient.state() == 4) json["mqttstate"] = "CONNECT_BAD_CREDENTIALS";
+      else if(mqttclient.state() == 5) json["mqttstate"] = "CONNECT_UNAUTHORIZED";
+      
+      json["mqtthost"] = mqtt_server;
+      
+      json["mqttreconn"] = mqttReconnects - 1;
+      
+      if (PCstate == 0) json["PCstate"] = "OFF";
+      else if (PCstate == 1) json["PCstate"] = "ON";
+      else if (PCstate == 2) json["PCstate"] = "SLEEP";
+      else json["PCstate"] = "unknown";
+
+      yield();
+
+      char jsonchar[200];
+      json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece
+      httpServer.send(200, "application/json", jsonchar);
+    }
+    else {
+      httpSendUnauthorized();
+    }
+  }); //httpServer.on /api
+
+//  httpServer.on("/restart", []() {
+//    if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
+//      //Serial.println("web triggered restart");
+//      ESP.restart();
+//    }
+//    else httpSendUnauthorized();
+//  });
+
+  httpServer.on("/mqttReconnect", []() {
+    if ( httpIsAuthenticated() || (!httpIsAuthenticated() && httpCheckToken()) ) {
+      //Serial.println("web triggered mqttReconnect");
+      mqttReconnect();
+      httpServer.sendHeader("Location", "/", true);
+      httpServer.send(303);
+    }
+    else httpSendUnauthorized();
+  });
+
+  httpServer.on("/confdweb", []() {
+    if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
+    else {
+      Serial.println("httpServer.on /confdata");
+      //build json object of program data
+      StaticJsonBuffer<1000> jsonBuffer;
+      JsonObject &json = jsonBuffer.createObject();
+      json["devName"] = deviceName;
+      json["apiToken"] = http_token;
+      json["httpUA"] =  http_user;
+      //json["httpPA"] =  http_pass;
+      
+      if(http_user_auth) json["httpAuth"] = "1";
+      else json["httpAuth"] = "0";
+      
+      json["httpU1"] =  http_user1;
+      //json["httpP1"] =  http_pass1;
+      json["httpU2"] =  http_user2;
+      //json["httpP2"] =  http_pass2;
+      yield();
+
+      char jsonchar[1000];
+      json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece
+      httpServer.send(200, "application/json", jsonchar);
+    }
+  }); //httpServer.on /confdweb
+
+    httpServer.on("/confdmqtt", []() {
+    if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
+    else {
+      Serial.println("httpServer.on /confdata");
+      //build json object of program data
+      StaticJsonBuffer<1000> jsonBuffer;
+      JsonObject &json = jsonBuffer.createObject();
+      json["mqttHost"] = mqtt_server;
+      json["mqttPort"] = mqtt_port;
+      json["mqttUser"] =  mqtt_user;
+      //json["mqttPass"] =  mqtt_pass;
+      json["inTop"] = mqtt_topic_in;
+      json["outTop"] = mqtt_topic_out;
+      
+      if(mqtt_outRetain) json["outRet"] = "1";
+      else json["outRet"] = "0";
+      
+      json["willTop"] = mqtt_willTopic;
+      json["willQos"] = mqtt_willQos;
+
+      if(mqtt_willRetain) json["willRet"] = "1";
+      else json["willRet"] = "0";
+      
+      json["willMsg"] = mqtt_willMsg;
+      json["connMsg"] = mqtt_connMsg;
+
+      yield();
+
+      char jsonchar[1000];
+      json.printTo(jsonchar); //print to char array, takes more memory but sends in one piece
+      httpServer.send(200, "application/json", jsonchar);
+    }
+  }); //httpServer.on /confdmqtt
+
+  httpServer.on("/setConfWeb", []() {
+    if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
+    else {
+      Serial.println("httpServer.on /setConfWeb");
+      bool httpPASet, httpP1Set, httpP2Set;
+      httpPASet = false;
+      httpP1Set = false;
+      httpP2Set = false;
+      if(httpServer.hasArg("httpPASet")) httpPASet = true;
+      if(httpServer.hasArg("httpP1Set")) httpP1Set = true;
+      if(httpServer.hasArg("httpP2Set")) httpP2Set = true;
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+        
+        if (strlen(bufName) > 0) {
+          Serial.print("web update ");
+          Serial.print(bufName);
+          Serial.print(" = ");
+          Serial.println(bufValue);
+
+          if(strcmp(bufName, "httpUA") == 0 || strcmp(bufName, "httpPA") == 0) {
+            if(httpPASet) setConfig(bufName, bufValue);
+          }
+          else if(strcmp(bufName, "httpU1") == 0 || strcmp(bufName, "httpP1") == 0) {
+            if(httpP1Set) setConfig(bufName, bufValue);
+          }
+          else if(strcmp(bufName, "httpU2") == 0 || strcmp(bufName, "httpP2") == 0) {
+            if(httpP2Set) setConfig(bufName, bufValue);
+          }
+          else if(strcmp(bufName, "httpPASet") != 0 && strcmp(bufName, "httpP1Set") != 0 && strcmp(bufName, "httpP2Set") != 0) setConfig(bufName, bufValue);
+          //else setConfig(bufName, bufValue);
+        }
+        saveConfigWebToFlash = true; // will be saved in next loop()
+        Serial.println("web triggered saveConfigWebToFlash");
+      }
+      yield();
+
+    saveConfigWeb();
+    
+    httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN);   //Enable Chunked Transfer
+    httpServer.send(200, "text/html", html_head_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_head_part2);
+    httpServer.sendContent_P(html_head_part3);
+    httpServer.sendContent_P(html_bodytag_jsinit);
+    httpServer.sendContent_P(html_body_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_body_part2);
+    httpServer.sendContent_P(html_confsaved_body);
+    httpServer.sendContent_P(html_footer);
+    httpServer.sendContent("");
+    httpServer.client().stop();
+    yield();
+    ESP.restart();
+    }
+  }); //httpServer.on /setConfWeb
+
+  httpServer.on("/setConfMqtt", []() {
+    if (!httpIsAuthenticatedAdmin()) httpSendUnauthorized();
+    else {
+      Serial.println("httpServer.on /setConfMqtt");
+      bool mqttPassSet;
+      mqttPassSet = false;
+      if(httpServer.hasArg("mqttPassSet")) mqttPassSet = true;
+
+      for (int i = 0; i < httpServer.args(); i++) {
+        char bufName[20];
+        char bufValue[101];
+        httpServer.argName(i).toCharArray(bufName, 20);
+        httpServer.arg(i).toCharArray(bufValue, 101);
+        
+        if (strlen(bufName) > 0) {
+          Serial.print("web update ");
+          Serial.print(bufName);
+          Serial.print(" = ");
+          Serial.println(bufValue);
+
+          if(strcmp(bufName, "mqttUser") == 0 || strcmp(bufName, "mqttPass") == 0) {
+            if(mqttPassSet) setConfig(bufName, bufValue);
+          }
+          else setConfig(bufName, bufValue);
+        }
+        saveConfigMqttToFlash = true; // will be saved in next loop()
+        Serial.println("web triggered saveConfigMqttToFlash");
+      }
+      yield();
+
+    saveConfigMqtt();
+    
+    httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN);   //Enable Chunked Transfer
+    httpServer.send(200, "text/html", html_head_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_head_part2);
+    httpServer.sendContent_P(html_head_part3);
+    httpServer.sendContent_P(html_bodytag);
+    httpServer.sendContent_P(html_body_part1);
+    httpServer.sendContent(deviceName);
+    httpServer.sendContent_P(html_body_part2);
+    httpServer.sendContent_P(html_confsaved_body);
+    httpServer.sendContent_P(html_footer);
+    httpServer.sendContent("");
+    httpServer.client().stop();
+    yield();
+    ESP.restart();
+    }
+  }); //httpServer.on /setConfMqtt
+  
+  httpServer.on("/", []() {
+    if (httpServer.hasArg("restart")) {
+      httpServer.setContentLength(CONTENT_LENGTH_UNKNOWN);   //Enable Chunked Transfer
+      httpServer.send(200, "text/html", html_head_part1);
+      httpServer.sendContent(deviceName);
+      httpServer.sendContent_P(html_head_part2);
+      httpServer.sendContent_P(html_head_part3);
+      httpServer.sendContent_P(html_bodytag);
+      httpServer.sendContent_P(html_body_part1);
+      httpServer.sendContent(deviceName);
+      httpServer.sendContent_P(html_body_part2);
+      httpServer.sendContent_P(html_restarting_body);
+      httpServer.sendContent_P(html_footer);
+      httpServer.sendContent("");
+      httpServer.client().stop();
+      yield();
+      ESP.restart();
+    }
+    else if (!httpIsAuthenticated()) return httpServer.requestAuthentication();
+      else {
+        httpServerHandleRoot();
+      }
+  });
+
+  httpServer.on("/conf", []() {
+    if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
+    else {
+      httpServerHandleConfPage();
+    }
+  });
+
+  httpServer.on("/confweb", []() {
+    if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
+    else {
+      httpServerHandleConfWebPage();
+    }
+  });
+
+  httpServer.on("/confmqtt", []() {
+    if (!httpIsAuthenticatedAdmin()) return httpServer.requestAuthentication();
+    else {
+      httpServerHandleConfMqttPage();
+    }
+  });
+
+  httpServer.onNotFound([]() {
+    httpServerHandleNotFound();
+  }); //httpServer.onNotFound
+
+  
+  // HTTP Updater at /update
+  httpUpdater.setup(&httpServer, "/update", http_user, http_pass);
+
+  httpServer.begin();
+}

+ 79 - 0
Software/src/WiFiPCController/inputs.ino

@@ -0,0 +1,79 @@
+void checkPowerLEDInput() {
+  in_pwrled_currState = digitalRead(PIN_IN_PWRLED);
+  if (in_pwrled_lastState == !IN_PWRLED_ONSTATE && in_pwrled_currState == IN_PWRLED_ONSTATE) {
+    // PC power LED changed to ON
+    in_pwrled_lastState = in_pwrled_currState;
+    in_pwrled_millis = millis();
+  }
+  else if (in_pwrled_lastState == IN_PWRLED_ONSTATE && in_pwrled_currState == !IN_PWRLED_ONSTATE) {
+    // PC power LED changed to OFF
+    in_pwrled_lastState = in_pwrled_currState;
+    in_pwrled_millis = millis();
+  }
+  else if (in_pwrled_millis == 0) { // get initial state after boot
+    in_pwrled_lastState = in_pwrled_currState;
+    in_pwrled_millis = millis();
+  }
+
+  if (in_pwrled_millis > 0 && (millis() - in_pwrled_millis) > 2000) {
+    // last Power LED status change was more than 2s ago
+    if (in_pwrled_currState == IN_PWRLED_ONSTATE) { //LED is ON
+      if (PCstate != 1) {
+        PCstate = 1;
+        Serial.println("PC state changed to ON");
+        publishPCPowerState(false);
+        PC_standby_pending_millis = 0;
+      }
+    }
+    else if (in_pwrled_currState == !IN_PWRLED_ONSTATE) { //LED is OFF
+      if (PCstate != 0) {
+        PCstate = 0;
+        Serial.println("PC state changed to OFF");
+        publishPCPowerState(false);
+        PC_standby_pending_millis = 0;
+      }
+    }
+  }
+  else if ( PCstate != 2 && in_pwrled_millis > 0 && (millis() - in_pwrled_millis) > 100 ) {
+    // Power LED toggles permanently -> it blinks because PC is in standby
+    if ( PCstate != 2 && PC_standby_pending_millis == 0 ) {
+      PC_standby_pending_millis = millis();
+      //Serial.println("PC state - pending status");
+    }
+    else if ( PC_standby_pending_millis > 0 && (millis() - PC_standby_pending_millis) > 2500 ) {
+      PCstate = 2;
+      Serial.println("PC state changed to SLEEP");
+      publishPCPowerState(false);
+    }
+  }
+}
+
+void publishPCPowerState(boolean force) {
+  if(!mqtt_outRetain || PCstate != PCstate_lastPublished || force) {
+    PCstate_lastPublished = PCstate;
+    if(PCstate == 0) {
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-PowerStateText");
+      mqttclient.publish(tmp_topic_pub, "OFF", mqtt_outRetain);
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-SleepState");
+      mqttclient.publish(tmp_topic_pub, "OFF", mqtt_outRetain);
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-PowerOnState");
+      mqttclient.publish(tmp_topic_pub, "OFF", mqtt_outRetain);
+    }
+    else if(PCstate == 1) {
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-PowerStateText");
+      mqttclient.publish(tmp_topic_pub, "ON", mqtt_outRetain);
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-SleepState");
+      mqttclient.publish(tmp_topic_pub, "OFF", mqtt_outRetain);
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-PowerOnState");
+      mqttclient.publish(tmp_topic_pub, "ON", mqtt_outRetain);
+    }
+    else if(PCstate == 2) {
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-PowerStateText");
+      mqttclient.publish(tmp_topic_pub, "SLEEP", mqtt_outRetain);
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-SleepState");
+      mqttclient.publish(tmp_topic_pub, "ON", mqtt_outRetain);
+      sprintf(tmp_topic_pub, "%s/%s", mqtt_topic_out, "PC-PowerOnState");
+      mqttclient.publish(tmp_topic_pub, "OFF", mqtt_outRetain);
+    }
+  }
+}

+ 59 - 0
Software/src/WiFiPCController/lib/##AM2320/AM2320.cpp

@@ -0,0 +1,59 @@
+#include "AM2320.h"
+#include <Wire.h>
+// 
+// AM2321 Temperature & Humidity Sensor library for Arduino
+// Сделана Тимофеевым Е.Н. из AM2320-master
+
+unsigned int CRC16(byte *ptr, byte length) 
+{ 
+      unsigned int crc = 0xFFFF; 
+      uint8_t s = 0x00; 
+
+      while(length--) {
+        crc ^= *ptr++; 
+        for(s = 0; s < 8; s++) {
+          if((crc & 0x01) != 0) {
+            crc >>= 1; 
+            crc ^= 0xA001; 
+          } else crc >>= 1; 
+        } 
+      } 
+      return crc; 
+} 
+
+AM2320::AM2320()
+{
+}
+
+int AM2320::Read()
+{
+	byte buf[8];
+	for(int s = 0; s < 8; s++) buf[s] = 0x00; 
+
+	Wire.beginTransmission(AM2320_address);
+	Wire.endTransmission();
+	// запрос 4 байт (температуры и влажности)
+	Wire.beginTransmission(AM2320_address);
+	Wire.write(0x03);// запрос
+	Wire.write(0x00); // с 0-го адреса
+	Wire.write(0x04); // 4 байта
+	if (Wire.endTransmission(1) != 0) return 1;
+	delayMicroseconds(1600); //>1.5ms
+	// считываем результаты запроса
+	Wire.requestFrom(AM2320_address, 0x08); 
+	for (int i = 0; i < 0x08; i++) buf[i] = Wire.read();
+
+	// CRC check
+	unsigned int Rcrc = buf[7] << 8;
+	Rcrc += buf[6];
+	if (Rcrc == CRC16(buf, 6)) {
+		unsigned int temperature = ((buf[4] & 0x7F) << 8) + buf[5];
+		t = temperature / 10.0;
+		t = ((buf[4] & 0x80) >> 7) == 1 ? t * (-1) : t;
+
+		unsigned int humidity = (buf[2] << 8) + buf[3];
+		h = humidity / 10.0;
+		return 0;
+	}
+        return 2;
+}

+ 17 - 0
Software/src/WiFiPCController/lib/##AM2320/AM2320.h

@@ -0,0 +1,17 @@
+#ifndef AM2320_H
+#define AM2320_H
+
+#include <Arduino.h>
+
+#define AM2320_address (0xB8 >> 1) 
+
+class AM2320
+{
+	public:
+		AM2320();
+		float t;
+		float h;
+		int Read(void); 
+};
+
+#endif

+ 29 - 0
Software/src/WiFiPCController/lib/##AM2320/example/AM2320lib_test/AM2320lib_test.ino

@@ -0,0 +1,29 @@
+#include <Wire.h>
+#include <AM2320.h>
+
+AM2320 th;
+
+void setup() {
+  Serial.begin(9600);
+  Wire.begin();
+}
+
+void loop() {
+  switch(th.Read()) {
+    case 2:
+      Serial.println("CRC failed");
+      break;
+    case 1:
+      Serial.println("Sensor offline");
+      break;
+    case 0:
+      Serial.print("humidity: ");
+      Serial.print(th.h);
+      Serial.print("%, temperature: ");
+      Serial.print(th.t);
+      Serial.println("*C");
+      break;
+  }
+
+  delay(200);
+}

+ 4 - 0
Software/src/WiFiPCController/lib/##AM2320/keywords.txt

@@ -0,0 +1,4 @@
+AM2320          KEYWORD1
+getTemperature	KEYWORD2
+getHumidity     KEYWORD2
+CRCCheck        KEYWORD2

+ 13 - 0
Software/src/WiFiPCController/lib/AS_BH1750/.gitignore

@@ -0,0 +1,13 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a

+ 476 - 0
Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750.cpp

@@ -0,0 +1,476 @@
+/*
+ This is a (Arduino) library for the BH1750FVI Digital Light Sensor.
+ 
+ Description:
+ http://www.rohm.com/web/global/products/-/product/BH1750FVI
+ 
+ Datasheet:
+ http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf
+ 
+ Copyright (c) 2013 Alexander Schulz.  All right reserved.
+ 
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ 
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
+ Lesser General Public License for more details.
+ 
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA	 02110-1301	 USA
+ */
+
+#include "AS_BH1750.h"
+
+// Debug-Flag
+#define BH1750_DEBUG 0
+
+/**
+ * Constructor.
+ * Erlaubt die I2C-Adresse des Sensors zu ändern. 
+ * Standardadresse: 0x23, Alternativadresse: 0x5C. 
+ * Es sind entsprechende Konstanten definiert: BH1750_DEFAULT_I2CADDR  und BH1750_SECOND_I2CADDR.
+ * Bei Nichtangabe wird die Standardadresse verwendet. 
+ * Um die Alternativadresse zu nutzen, muss der Sensorpin 'ADR' des Chips auf VCC gelegt werden.
+ */
+AS_BH1750::AS_BH1750(uint8_t address) {
+  _address = address;
+  _hardwareMode = 255;
+}
+
+/**
+ * Führt die anfängliche Initialisierung des Sensors.
+ * Mögliche Parameter: 
+ *  - Modus für die Sensorauflösung:
+ *    -- RESOLUTION_LOW:         Physische Sensormodus mit 4 lx Auflösung. Messzeit ca. 16ms. Bereich 0-54612.
+ *    -- RESOLUTION_NORMAL:      Physische Sensormodus mit 1 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+ *    -- RESOLUTION_HIGH:        Physische Sensormodus mit 0,5 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+ *                               (Die Messbereiche können durch Änderung des MTreg verschoben werden.)
+ *    -- RESOLUTION_AUTO_HIGH:   Die Werte im MTreg werden je nach Helligkeit automatisch so angepasst, 
+ *                               dass eine maximalmögliche Auflösung und Messbereich erziehlt werden.
+ *                               Die messbaren Werte fangen von 0,11 lx und gehen bis über 100000 lx.
+ *                               (ich weis nicht, wie genau die Werte in Grenzbereichen sind, 
+ *                               besonders bei hohen Werte habe ich da meine Zweifel.
+ *                               Die Werte scheinen jedoch weitgehend linear mit der steigenden Helligkeit zu wachsen.)
+ *                               Auflösung im Unteren Bereich ca. 0,13 lx, im mittleren 0,5 lx, im oberen etwa 1-2 lx.
+ *                               Die Messzeiten verlängern sich durch mehrfache Messungen und 
+ *                               die Änderungen von Measurement Time (MTreg) bis max. ca. 500 ms.
+ *   
+ * - AutoPowerDown: true = Der Sensor wird nach der Messung in den Stromsparmodus versetzt. 
+ *   Das spätere Aufwecken wird ggf. automatisch vorgenommen, braucht jedoch geringfügig mehr Zeit.
+ *
+ * Defaultwerte: RESOLUTION_AUTO_HIGH, true
+ *
+ */
+bool AS_BH1750::begin(sensors_resolution_t mode, bool autoPowerDown) {
+#if BH1750_DEBUG == 1
+  Serial.print("  sensors_resolution_mode (virtual): ");
+  Serial.println(mode, DEC);
+#endif
+  _virtualMode = mode;
+  _autoPowerDown = autoPowerDown;
+  
+  Wire.begin();
+
+  defineMTReg(BH1750_MTREG_DEFAULT); // eigentlich normalerweise unnötig, da standard
+
+  // Hardware-Modus zum gewünschten VitrualModus ermitteltn
+  switch (_virtualMode) {
+  case RESOLUTION_LOW:
+    _hardwareMode = autoPowerDown?BH1750_ONE_TIME_LOW_RES_MODE:BH1750_CONTINUOUS_LOW_RES_MODE;
+    break;
+  case RESOLUTION_NORMAL:
+    _hardwareMode = autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE;
+    break;
+  case RESOLUTION_HIGH:
+    _hardwareMode = autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2;
+    break;
+  case RESOLUTION_AUTO_HIGH:
+    _hardwareMode = BH1750_CONTINUOUS_LOW_RES_MODE;
+    break;
+  default:
+    // darf eigentlich nicht passieren...
+    _hardwareMode = 255;
+    break;
+  }
+
+#if BH1750_DEBUG == 1
+  Serial.print("hardware mode: ");
+  Serial.println(_hardwareMode, DEC);
+#endif
+
+  if(_hardwareMode==255) {
+    return false;
+  }
+
+  // Versuchen, den gewählten Hardwaremodus zu aktivieren
+  if(selectResolutionMode(_hardwareMode)){
+#if BH1750_DEBUG == 1
+    Serial.print("hardware mode defined successfully");
+    Serial.println(_hardwareMode, DEC);
+#endif
+    return true;
+  }
+
+  // Initialisierung fehlgeschlagen
+#if BH1750_DEBUG == 1
+  Serial.print("failure to aktivate hardware mode ");
+  Serial.println(_hardwareMode, DEC);
+#endif
+  _hardwareMode = 255;
+  return false;
+}
+
+/**
+ * Erlaub eine Prüfung, ob ein (ansprechbarer) BH1750-Sensor vorhanden ist.
+ */
+bool AS_BH1750::isPresent() {
+  // Check I2C Adresse
+  Wire.beginTransmission(_address);
+  if(Wire.endTransmission()!=0) {
+    return false; 
+  }
+
+  // Check device: ist es ein BH1750
+  if(!isInitialized()) {
+    // zuvor inaktiv, daher zu Testen schnelltes einmal-Mode aktivieren
+    //write8(BH1750_POWER_ON);
+    selectResolutionMode(BH1750_ONE_TIME_LOW_RES_MODE);
+    _hardwareMode=255;
+  } 
+  else {
+    // falls einmal-modus aktiv war, muss der Sensor geweckt werden
+    powerOn(); 
+  }
+
+  // Prüfen, ob Werte auch wirklich geliefert werden (letztes Modus, ggf. wird auto-PowerDown ausgeführt)
+  return (readLightLevel()>=0);
+}
+
+/**
+ * Weckt ein im PowerDown-Modus befindlichen Sensor auf (schadet auch dem 'wachen' Sensor nicht).
+ * Funktionier nur, wenn der Sensor bereits initialisiert wurde.
+ */
+void AS_BH1750::powerOn() {
+  if(!isInitialized()) {
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+    return;
+  }
+
+  _valueReaded=false;
+  //write8(BH1750_POWER_ON); //
+  //fDelayPtr(10); // Nötig?
+  // Scheinbar reicht das Setzen von HardwareMode auch ohne PowerON-Befehl aus
+  selectResolutionMode(_hardwareMode); // letzten Modus wieder aktivieren
+}
+
+/**
+ * Schickt den Sensor in Stromsparmodus.
+ * Funktionier nur, wenn der Sensor bereits initialisiert wurde.
+ */
+void AS_BH1750::powerDown() {
+  if(!isInitialized()) {
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+    return;
+  }
+
+  write8(BH1750_POWER_DOWN);
+}
+
+/**
+ * Sendet zum Sensor ein Befehl zum Auswahl von HardwareMode.
+ *
+ * Parameter:
+ * - mode: s.o. 
+ * - DelayFuncPtr: delay(n) Möglichkeit, eigene Delay-Funktion mitzugeben (z.B. um sleep-Modus zu verwenden).
+ * 
+ * Defaultwerte: delay()
+ *
+ */
+bool AS_BH1750::selectResolutionMode(uint8_t mode, DelayFuncPtr fDelayPtr) {
+#if BH1750_DEBUG == 1
+    Serial.print("selectResolutionMode: ");
+    Serial.println(mode, DEC);
+#endif
+  if(!isInitialized()) {
+    return false;
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+  }
+
+  _hardwareMode=mode;
+  _valueReaded=false;
+
+  // Prüfen, ob ein valides Modus vorliegt und im positiven Fall das gewünschte Modus aktivieren
+  switch (mode) {
+  case BH1750_CONTINUOUS_HIGH_RES_MODE:
+  case BH1750_CONTINUOUS_HIGH_RES_MODE_2:
+  case BH1750_CONTINUOUS_LOW_RES_MODE:
+  case BH1750_ONE_TIME_HIGH_RES_MODE:
+  case BH1750_ONE_TIME_HIGH_RES_MODE_2:
+  case BH1750_ONE_TIME_LOW_RES_MODE:
+    // Modus aktivieren
+    if(write8(mode)) {
+    // Kurze pause nötig, sonst wird das Modus nicht sicher Aktiviert
+    // (z.B. liefert Sensor im AutoHigh Modus abwechselnd übersteuerte Werte, etwa so: 54612.5, 68123.4, 54612.5, 69345.3,..)
+    fDelayPtr(5);
+      return true;
+    }
+    break;
+  default:
+    // Invalid measurement mode
+#if BH1750_DEBUG == 1
+    Serial.println("Invalid measurement mode");
+#endif
+    break;
+  }
+
+  return false;
+}
+
+/**
+ * Liefert aktuell gemessenen Wert für Helligkeit in lux (lx).
+ * Falls sich der Sensorf in Stromsparmodus befindet, wird er automatisch geweckt.
+ *
+ * Wurde der Sensor (noch) nicht initialisiert (begin), wird der Wert -1 geliefert.
+ */
+float AS_BH1750::readLightLevel(DelayFuncPtr fDelayPtr) {
+#if BH1750_DEBUG == 1
+    Serial.print("call: readLightLevel. virtualMode: ");
+    Serial.println(_virtualMode, DEC);
+#endif
+
+  if(!isInitialized()) {
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+    return -1;
+  }
+
+  // ggf. PowerOn  
+  if(_autoPowerDown && _valueReaded){
+    powerOn();
+  }
+
+  // Das Automatische Modus benötigt eine Sonderbehandlung.
+  // Zuerst wird die Helligkeit im LowRes-Modus gelesen, 
+  // je nach Bereich (dunkel, normal, sehr hell) werden die Werte von MTreg gesetzt und
+  // danach wird die eigentliche Messung vorgenommen.
+  /*
+     Die feste Grenzwerte verursachen möglicherweise einen 'Sprung' in der Messkurve. 
+   In diesem Fall wäre eine laufende Anpassung von MTreg in Grenzbereichen vermutlich besser.
+   Für meine Zwecke ist das jedoch ohne Bedeutung.
+   */
+  if(_virtualMode==RESOLUTION_AUTO_HIGH) {
+    defineMTReg(BH1750_MTREG_DEFAULT);
+    selectResolutionMode(BH1750_CONTINUOUS_LOW_RES_MODE, fDelayPtr);
+    fDelayPtr(16); // Lesezeit in LowResMode
+    uint16_t level = readRawLevel();
+#if BH1750_DEBUG == 1
+    Serial.print("AutoHighMode: check level read: ");
+    Serial.println(level, DEC);
+#endif
+    if(level<10) {
+#if BH1750_DEBUG == 1
+    Serial.println("level 0: dark");
+#endif    
+      // Dunkel, Empfindlichkeit auf Maximum. 
+      // Der Wert ist zufällig. Ab ca. 16000 wäre diese Vorgehnsweise möglich.
+      // Ich brauche diese Genauigkeit aber nur in den ganz dunklen Bereichen (zu erkennen, wann wirklich 'dunkel' ist).
+      defineMTReg(BH1750_MTREG_MAX);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2, fDelayPtr);
+      fDelayPtr(120*3.68); // TODO: Wert prüfen
+      //fDelayPtr(122);
+    }
+    else if(level<32767) {
+#if BH1750_DEBUG == 1
+    Serial.println("level 1: normal");
+#endif    
+      // Bis hierher reicht die 0,5 lx Modus. Normale Empfindlichkeit.
+      defineMTReg(BH1750_MTREG_DEFAULT);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2, fDelayPtr);
+      fDelayPtr(120); // TODO: Wert prüfen
+    } 
+    else if(level<60000) {
+#if BH1750_DEBUG == 1
+    Serial.println("level 2: bright");
+#endif    
+      // hoher Bereich, 1 lx Modus, normale Empfindlichkeit. Der Wert von 60000 ist mehr oder weniger zufällig, es mus einfach ein hoher Wert, nah an der Grenze sein.
+      defineMTReg(BH1750_MTREG_DEFAULT);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE, fDelayPtr);
+      fDelayPtr(120); // TODO: Wert prüfen
+    }
+    else {
+#if BH1750_DEBUG == 1
+    Serial.println("level 3: very bright");
+#endif    
+      // sehr hoher Bereich, Empfindlichkeit verringern
+      defineMTReg(32); // Min+1, bei dem Minimum aus Doku spielt der Sensor (zumindest meiner) verrückt: Die Werte sind ca. 1/10 von den Erwarteten.
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE, fDelayPtr);
+      fDelayPtr(120); // TODO: Wert prüfen
+    }
+  } 
+
+  // Hardware Wert lesen und in Lux umrechnen.
+  uint16_t raw = readRawLevel();
+  if(raw==65535) {
+    // Wert verdächtig hoch. Sensor prüfen. 
+    // Check I2C Adresse
+    Wire.beginTransmission(_address);
+    if(Wire.endTransmission()!=0) {
+      return -1; 
+    }
+  }
+  return convertRawValue(raw); 
+}
+
+/**
+ * Roh-Wert der Helligkeit auslesen. 
+ * Wertebereich 0-65535.
+ */
+uint16_t AS_BH1750::readRawLevel(void) {
+  uint16_t level;
+  Wire.beginTransmission(_address);
+  Wire.requestFrom(_address, 2);
+#if (ARDUINO >= 100)
+  level = Wire.read();
+  level <<= 8;
+  level |= Wire.read();
+#else
+  level = Wire.receive();
+  level <<= 8;
+  level |= Wire.receive();
+#endif
+  if(Wire.endTransmission()!=0) {
+#if BH1750_DEBUG == 1
+    Serial.println("I2C read error");
+#endif
+    return 65535; // Error marker
+  }
+
+#if BH1750_DEBUG == 1
+  Serial.print("Raw light level: ");
+  Serial.println(level);
+#endif
+
+  _valueReaded=true;
+
+  return level;
+}
+
+/**
+ * Rechnet Roh-Werte in Lux um.
+ */
+float AS_BH1750::convertRawValue(uint16_t raw) {
+  // Basisumrechnung
+  float flevel = raw/1.2;
+
+#if BH1750_DEBUG == 1
+  Serial.print("convert light level (1): ");
+  Serial.println(flevel);
+#endif
+
+  // MTreg-Einfluss berechnen
+  if(_MTreg!=BH1750_MTREG_DEFAULT) { // bei 69 ist der Faktor = 1
+    flevel = flevel * _MTregFactor;
+#if BH1750_DEBUG == 1
+    Serial.print("convert light level (2): ");
+    Serial.println(flevel);
+#endif
+  }
+
+  // je nach Modus ist eine weitere Umrechnung nötig
+  switch (_hardwareMode) {
+  case BH1750_CONTINUOUS_HIGH_RES_MODE_2:
+  case BH1750_ONE_TIME_HIGH_RES_MODE_2:
+    flevel = flevel/2;
+#if BH1750_DEBUG == 1
+    Serial.print("convert light level (3): ");
+    Serial.println(flevel);
+#endif
+    break;
+  default:
+    // nothing to do
+    break;
+  }
+
+#if BH1750_DEBUG == 1
+  Serial.print("Light level: ");
+  Serial.println(flevel);
+#endif
+
+  return flevel;
+}
+
+/**
+ * MTreg setzen. Definiert Empfindlichkeit des Sensors.
+ * Min.Wert (BH1750_MTREG_MIN) =  31 (Empfindlichkeit: default*0,45)
+ * Max.Wert (BH1750_MTREG_MAX) = 254 (Empfindlichkeit: default*3,68)
+ * Default (BH1750_MTREG_DEFAULT) = 69.
+ * Mit der Empfindlichkeit verändert sich die Lesezeit (höhere Empfindlichkeit bedeutet längere Zeitspanne).
+ */
+void AS_BH1750::defineMTReg(uint8_t val) {
+  if(val<BH1750_MTREG_MIN) {
+    val = BH1750_MTREG_MIN;
+  }
+  if(val>BH1750_MTREG_MAX) {
+    val = BH1750_MTREG_MAX;
+  }
+  if(val!=_MTreg) {
+    _MTreg = val;
+    _MTregFactor = (float)BH1750_MTREG_DEFAULT / _MTreg;
+
+    // Change Measurement time
+    // Übertragung in zwei Schritten: 3 Bit und 5 Bit, jeweils mit einem Prefix.
+    //   High bit: 01000_MT[7,6,5]
+    //   Low bit:  011_MT[4,3,2,1,0]
+    uint8_t hiByte = val>>5;
+    hiByte |= 0b01000000;
+#if BH1750_DEBUG == 1
+    Serial.print("MGTreg high byte: ");
+    Serial.println(hiByte, BIN);
+#endif
+    write8(hiByte);
+    //fDelayPtr(10);
+    // Pause nötig?
+    uint8_t loByte = val&0b00011111;
+    loByte |= 0b01100000;
+#if BH1750_DEBUG == 1
+    Serial.print("MGTreg low byte: ");
+    Serial.println(loByte, BIN);
+#endif
+    write8(hiByte);
+    //fDelayPtr(10);
+  }
+}
+
+/**
+ * Gibt an, ob der Sensor initialisiert ist.
+ */
+bool AS_BH1750::isInitialized() {
+  return _hardwareMode!=255; 
+}
+
+/**
+ * Schreibt ein Byte auf I2C Bus (auf die Adresse des Sensors).
+ */
+bool AS_BH1750::write8(uint8_t d) {
+  Wire.beginTransmission(_address);
+#if (ARDUINO >= 100)
+  Wire.write(d);
+#else
+  Wire.send(d);
+#endif
+  return (Wire.endTransmission()==0);
+}
+
+

+ 183 - 0
Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750.h

@@ -0,0 +1,183 @@
+/*
+ This is a (Arduino) library for the BH1750FVI Digital Light Sensor.
+ 
+ Description:
+ http://www.rohm.com/web/global/products/-/product/BH1750FVI
+ 
+ Datasheet:
+ http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf
+ 
+ Copyright (c) 2013 Alexander Schulz.  All right reserved.
+ 
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ 
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
+ Lesser General Public License for more details.
+ 
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA	 02110-1301	 USA
+ */
+
+#ifndef AS_BH1750_h
+#define AS_BH1750_h
+
+#if (ARDUINO >= 100)
+#include <Arduino.h>
+#else
+#include <WProgram.h>
+#endif
+#include "Wire.h"
+
+// Mögliche I2C Adressen
+#define BH1750_DEFAULT_I2CADDR 0x23
+#define BH1750_SECOND_I2CADDR 0x5C
+
+// MTreg Werte
+// Default
+#define BH1750_MTREG_DEFAULT 69
+// Sensitivity : default = 0.45
+#define BH1750_MTREG_MIN 31
+// Sensitivity : default = 3.68
+#define BH1750_MTREG_MAX 254
+
+// Hardware Modi
+// No active state
+#define BH1750_POWER_DOWN 0x00
+
+// Wating for measurment command
+#define BH1750_POWER_ON 0x01
+
+// Reset data register value - not accepted in POWER_DOWN mode
+#define BH1750_RESET 0x07
+
+// Start measurement at 1lx resolution. Measurement time is approx 120ms.
+#define BH1750_CONTINUOUS_HIGH_RES_MODE  0x10
+
+// Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
+#define BH1750_CONTINUOUS_HIGH_RES_MODE_2  0x11
+
+// Start measurement at 4lx resolution. Measurement time is approx 16ms.
+#define BH1750_CONTINUOUS_LOW_RES_MODE  0x13
+
+// Start measurement at 1lx resolution. Measurement time is approx 120ms.
+// Device is automatically set to Power Down after measurement.
+#define BH1750_ONE_TIME_HIGH_RES_MODE  0x20
+
+// Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
+// Device is automatically set to Power Down after measurement.
+#define BH1750_ONE_TIME_HIGH_RES_MODE_2  0x21
+
+// Start measurement at 1lx resolution. Measurement time is approx 120ms.
+// Device is automatically set to Power Down after measurement.
+#define BH1750_ONE_TIME_LOW_RES_MODE  0x23
+
+/** Virtual Modi */
+typedef enum
+{
+  RESOLUTION_LOW         = (1), /** 4lx resolution. Measurement time is approx 16ms. */  
+  RESOLUTION_NORMAL      = (2), /** 1lx resolution. Measurement time is approx 120ms. */
+  RESOLUTION_HIGH        = (3), /** 0,5lx resolution. Measurement time is approx 120ms. */
+  RESOLUTION_AUTO_HIGH   = (99) /** 0,11-1lx resolution. Measurement time is above 250ms. */
+  }  
+  sensors_resolution_t;
+
+typedef void (*DelayFuncPtr)(unsigned long);
+typedef unsigned long (*TimeFuncPtr)(void);
+
+/**
+ * BH1750 driver class.
+ */
+class AS_BH1750 {
+public:
+  /**
+   * Constructor.
+   * Erlaubt die I2C-Adresse des Sensors zu ändern. 
+   * Standardadresse: 0x23, Alternativadresse: 0x5C. 
+   * Es sind entsprechende Konstanten definiert: BH1750_DEFAULT_I2CADDR  und BH1750_SECOND_I2CADDR.
+   * Bei Nichtangabe wird die Standardadresse verwendet. 
+   * Um die Alternativadresse zu nutzen, muss der Sensorpin 'ADR' des Chips auf VCC gelegt werden.
+   */
+  AS_BH1750(uint8_t address = BH1750_DEFAULT_I2CADDR);
+
+  /**
+   * Führt die anfängliche Initialisierung des Sensors.
+   * Mögliche Parameter: 
+   *  - Modus für die Sensorauflösung:
+   *    -- RESOLUTION_LOW:         Physische Sensormodus mit 4 lx Auflösung. Messzeit ca. 16ms. Bereich 0-54612.
+   *    -- RESOLUTION_NORMAL:      Physische Sensormodus mit 1 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+   *    -- RESOLUTION_HIGH:        Physische Sensormodus mit 0,5 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+   *                               (Die Messbereiche können durch Änderung des MTreg verschoben werden.)
+   *    -- RESOLUTION_AUTO_HIGH:   Die Werte im MTreg werden je nach Helligkeit automatisch so angepasst, 
+   *                               dass eine maximalmögliche Auflösung und Messbereich erziehlt werden.
+   *                               Die messbaren Werte fangen von 0,11 lx und gehen bis über 100000 lx.
+   *                               (ich weis nicht, wie genau die Werte in Grenzbereichen sind, 
+   *                               besonders bei hohen Werte habe ich meine Zweifel.)
+   *                               Auflösung im Unteren Bereich 0,13 lx, im mittleren 0,5 lx, im oberen 1-2 lx.
+   *                               Die Messzeiten verlängern sich durch mehrfache Messungen und 
+   *                               die Änderungen von Measurement Time (MTreg) bis max. ca. 500 ms.
+   *   
+   * - AutoPowerDown: true = Der Sensor wird nach der Messung in den Stromsparmodus versetzt. 
+   *   Das spätere Aufwecken wird ggf. automatisch vorgenommen, braucht jedoch geringfügig mehr Zeit.
+   *
+   * Defaultwerte: RESOLUTION_AUTO_HIGH, true, delay()
+   *
+   */
+  bool begin(sensors_resolution_t mode = RESOLUTION_AUTO_HIGH, bool autoPowerDown = true);
+
+  /**
+   * Erlaub eine Prüfung, ob ein (ansprechbarer) BH1750-Sensor vorhanden ist.
+   */
+  bool isPresent(void);
+
+  /**
+   * Liefert aktuell gemessenen Wert für Helligkeit in lux (lx).
+   * Falls sich der Sensorf in Stromsparmodus befindet, wird er automatisch geweckt.
+   *
+   * Wurde der Sensor (noch) nicht initialisiert (begin), wird der Wert -1 geliefert.
+   *
+   * Mögliche Parameter: 
+   *
+   * - DelayFuncPtr: delay(n) Möglichkeit, eigene Delay-Funktion mitzugeben (z.B. um sleep-Modus zu verwenden).
+   * 
+   * Defaultwerte: delay()
+   *
+   */
+  float readLightLevel(DelayFuncPtr fDelayPtr = &delay);
+
+  /**
+   * Schickt den Sensor in Stromsparmodus.
+   * Funktionier nur, wenn der Sensor bereits initialisiert wurde.
+   */
+  void powerDown(void);
+
+private:
+  int _address;
+  uint8_t _hardwareMode;
+
+  uint8_t _MTreg;
+  float _MTregFactor;
+
+  sensors_resolution_t _virtualMode;
+  bool _autoPowerDown;
+
+  bool _valueReaded;
+
+  bool selectResolutionMode(uint8_t mode, DelayFuncPtr fDelayPtr = &delay);
+  void defineMTReg(uint8_t val);
+  void powerOn(void);
+  void reset(void);
+  uint16_t readRawLevel(void);
+  float convertRawValue(uint16_t raw);
+  bool isInitialized();
+  bool write8(uint8_t data);
+};
+
+#endif
+
+

+ 705 - 0
Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750A.cpp

@@ -0,0 +1,705 @@
+/*
+ This is a (Arduino) library for the BH1750FVI Digital Light Sensor.
+ 
+ Description:
+ http://www.rohm.com/web/global/products/-/product/BH1750FVI
+ 
+ Datasheet:
+ http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf
+ 
+ Copyright (c) 2013 Alexander Schulz.  All right reserved.
+ 
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ 
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
+ Lesser General Public License for more details.
+ 
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA	 02110-1301	 USA
+ */
+
+#include "AS_BH1750A.h"
+
+// Debug-Flag
+#define BH1750_DEBUG 0
+
+/**
+ * Constructor.
+ * Erlaubt die I2C-Adresse des Sensors zu ändern. 
+ * Standardadresse: 0x23, Alternativadresse: 0x5C. 
+ * Es sind entsprechende Konstanten definiert: BH1750_DEFAULT_I2CADDR  und BH1750_SECOND_I2CADDR.
+ * Bei Nichtangabe wird die Standardadresse verwendet. 
+ * Um die Alternativadresse zu nutzen, muss der Sensorpin 'ADR' des Chips auf VCC gelegt werden.
+ */
+AS_BH1750A::AS_BH1750A(uint8_t address) {
+  _address = address;
+  _hardwareMode = 255;
+}
+
+/**
+ * Führt die anfängliche Initialisierung des Sensors.
+ * Mögliche Parameter: 
+ *  - Modus für die Sensorauflösung:
+ *    -- RESOLUTION_LOW:         Physische Sensormodus mit 4 lx Auflösung. Messzeit ca. 16ms. Bereich 0-54612.
+ *    -- RESOLUTION_NORMAL:      Physische Sensormodus mit 1 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+ *    -- RESOLUTION_HIGH:        Physische Sensormodus mit 0,5 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+ *                               (Die Messbereiche können durch Änderung des MTreg verschoben werden.)
+ *    -- RESOLUTION_AUTO_HIGH:   Die Werte im MTreg werden je nach Helligkeit automatisch so angepasst, 
+ *                               dass eine maximalmögliche Auflösung und Messbereich erziehlt werden.
+ *                               Die messbaren Werte fangen von 0,11 lx und gehen bis über 100000 lx.
+ *                               (ich weis nicht, wie genau die Werte in Grenzbereichen sind, 
+ *                               besonders bei hohen Werte habe ich da meine Zweifel.
+ *                               Die Werte scheinen jedoch weitgehend linear mit der steigenden Helligkeit zu wachsen.)
+ *                               Auflösung im Unteren Bereich ca. 0,13 lx, im mittleren 0,5 lx, im oberen etwa 1-2 lx.
+ *                               Die Messzeiten verlängern sich durch mehrfache Messungen und 
+ *                               die Änderungen von Measurement Time (MTreg) bis max. ca. 500 ms.
+ *   
+ * - AutoPowerDown: true = Der Sensor wird nach der Messung in den Stromsparmodus versetzt. 
+ *   Das spätere Aufwecken wird ggf. automatisch vorgenommen, braucht jedoch geringfügig mehr Zeit.
+ *
+ * Defaultwerte: RESOLUTION_AUTO_HIGH, true
+ *
+ */
+bool AS_BH1750A::begin(sensors_resolution_t mode, bool autoPowerDown) {
+#if BH1750_DEBUG == 1
+  Serial.print("  sensors_resolution_mode (virtual): ");
+  Serial.println(mode, DEC);
+#endif
+  _virtualMode = mode;
+  _autoPowerDown = autoPowerDown;
+  
+  Wire.begin();
+
+  defineMTReg(BH1750_MTREG_DEFAULT); // eigentlich normalerweise unnötig, da standard
+
+  // Hardware-Modus zum gewünschten VitrualModus ermitteltn
+  switch (_virtualMode) {
+  case RESOLUTION_LOW:
+    _hardwareMode = autoPowerDown?BH1750_ONE_TIME_LOW_RES_MODE:BH1750_CONTINUOUS_LOW_RES_MODE;
+    break;
+  case RESOLUTION_NORMAL:
+    _hardwareMode = autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE;
+    break;
+  case RESOLUTION_HIGH:
+    _hardwareMode = autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2;
+    break;
+  case RESOLUTION_AUTO_HIGH:
+    _hardwareMode = BH1750_CONTINUOUS_LOW_RES_MODE;
+    break;
+  default:
+    // darf eigentlich nicht passieren...
+    _hardwareMode = 255;
+    break;
+  }
+
+#if BH1750_DEBUG == 1
+  Serial.print("hardware mode: ");
+  Serial.println(_hardwareMode, DEC);
+#endif
+
+  if(_hardwareMode==255) {
+    return false;
+  }
+
+  // Versuchen, den gewählten Hardwaremodus zu aktivieren
+  if(selectResolutionMode(_hardwareMode)){
+#if BH1750_DEBUG == 1
+    Serial.print("hardware mode defined successfully");
+    Serial.println(_hardwareMode, DEC);
+#endif
+    return true;
+  }
+
+  // Initialisierung fehlgeschlagen
+#if BH1750_DEBUG == 1
+  Serial.print("failure to aktivate hardware mode ");
+  Serial.println(_hardwareMode, DEC);
+#endif
+  _hardwareMode = 255;
+  return false;
+}
+
+/**
+ * Erlaub eine Prüfung, ob ein (ansprechbarer) BH1750-Sensor vorhanden ist.
+ */
+bool AS_BH1750A::isPresent() {
+  // Check I2C Adresse
+  Wire.beginTransmission(_address);
+  if(Wire.endTransmission()!=0) {
+    return false; 
+  }
+
+  // Check device: ist es ein BH1750
+  if(!isInitialized()) {
+    // zuvor inaktiv, daher zu Testen schnelltes einmal-Mode aktivieren
+    //write8(BH1750_POWER_ON);
+    selectResolutionMode(BH1750_ONE_TIME_LOW_RES_MODE);
+    delay(5);// Hier muss man in jedem Fall warten, daher die kurze Pause erstmal ok 
+    _hardwareMode=255;
+  } 
+  else {
+    // falls einmal-modus aktiv war, muss der Sensor geweckt werden
+    powerOn(); 
+    unsigned long dd = getModeDelay(); if(dd>0) {delay(dd);}
+    //delay(getModeDelay()); // s.o.
+  }
+
+  // Prüfen, ob Werte auch wirklich geliefert werden (letztes Modus, ggf. wird auto-PowerDown ausgeführt)
+  return (readLightLevel()>=0);
+}
+
+/**
+ * Weckt ein im PowerDown-Modus befindlichen Sensor auf (schadet auch dem 'wachen' Sensor nicht).
+ * Funktionier nur, wenn der Sensor bereits initialisiert wurde.
+ */
+void AS_BH1750A::powerOn() {
+  if(!isInitialized()) {
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+    return;
+  }
+
+  _valueReaded=false;
+  //write8(BH1750_POWER_ON); //
+  //fDelayPtr(10); // Nötig?
+  // Scheinbar reicht das Setzen von HardwareMode auch ohne PowerON-Befehl aus
+  selectResolutionMode(_hardwareMode); // letzten Modus wieder aktivieren
+}
+
+/**
+ * Schickt den Sensor in Stromsparmodus.
+ * Funktionier nur, wenn der Sensor bereits initialisiert wurde.
+ */
+void AS_BH1750A::powerDown() {
+  if(!isInitialized()) {
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+    return;
+  }
+
+  write8(BH1750_POWER_DOWN);
+}
+
+/**
+ * Sendet zum Sensor ein Befehl zum Auswahl von HardwareMode.
+ *
+ * Parameter:
+ * - mode: s.o. 
+ * - DelayFuncPtr: delay(n) Möglichkeit, eigene Delay-Funktion mitzugeben (z.B. um sleep-Modus zu verwenden).
+ * 
+ * Defaultwerte: delay()
+ *
+ */
+bool AS_BH1750A::selectResolutionMode(uint8_t mode) {
+#if BH1750_DEBUG == 1
+    Serial.print("selectResolutionMode: ");
+    Serial.println(mode, DEC);
+#endif
+  if(!isInitialized()) {
+    return false;
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+  }
+
+  _hardwareMode=mode;
+  _valueReaded=false;
+
+  // Prüfen, ob ein valides Modus vorliegt und im positiven Fall das gewünschte Modus aktivieren
+  switch (mode) {
+  case BH1750_CONTINUOUS_HIGH_RES_MODE:
+  case BH1750_CONTINUOUS_HIGH_RES_MODE_2:
+  case BH1750_CONTINUOUS_LOW_RES_MODE:
+  case BH1750_ONE_TIME_HIGH_RES_MODE:
+  case BH1750_ONE_TIME_HIGH_RES_MODE_2:
+  case BH1750_ONE_TIME_LOW_RES_MODE:
+    // Modus aktivieren
+    if(write8(mode)) {
+    // Kurze pause nötig, sonst wird das Modus nicht sicher Aktiviert
+    // (z.B. liefert Sensor im AutoHigh Modus abwechselnd übersteuerte Werte, etwa so: 54612.5, 68123.4, 54612.5, 69345.3,..)
+    //fDelayPtr(5); => Aufrufer soll 5ms extra warten
+      return true;
+    }
+    break;
+  default:
+    // Invalid measurement mode
+#if BH1750_DEBUG == 1
+    Serial.println("Invalid measurement mode");
+#endif
+    break;
+  }
+
+  return false;
+}
+
+/**
+ * Liefert aktuell gemessenen Wert für Helligkeit in lux (lx).
+ * Falls sich der Sensorf in Stromsparmodus befindet, wird er automatisch geweckt.
+ *
+ * Wurde der Sensor (noch) nicht initialisiert (begin), wird der Wert -1 geliefert.
+ */
+/*float AS_BH1750A::readLightLevel_alt(DelayFuncPtr fDelayPtr) {
+#if BH1750_DEBUG == 1
+    Serial.print("call: readLightLevel. virtualMode: ");
+    Serial.println(_virtualMode, DEC);
+#endif
+
+  if(!isInitialized()) {
+#if BH1750_DEBUG == 1
+    Serial.println("sensor not initialized");
+#endif
+    return -1;
+  }
+
+  // ggf. PowerOn  
+  if(_autoPowerDown && _valueReaded){
+    powerOn();
+    unsigned long dd = getModeDelay(); if(dd>0) {delay(dd);}
+  }
+
+  // Das Automatische Modus benötigt eine Sonderbehandlung.
+  // Zuerst wird die Helligkeit im LowRes-Modus gelesen, 
+  // je nach Bereich (dunkel, normal, sehr hell) werden die Werte von MTreg gesetzt und
+  // danach wird die eigentliche Messung vorgenommen.
+  
+  // Die feste Grenzwerte verursachen möglicherweise einen 'Sprung' in der Messkurve. 
+  // In diesem Fall wäre eine laufende Anpassung von MTreg in Grenzbereichen vermutlich besser.
+  // Für meine Zwecke ist das jedoch ohne Bedeutung.
+   
+  if(_virtualMode==RESOLUTION_AUTO_HIGH) {
+    defineMTReg(BH1750_MTREG_DEFAULT);
+    selectResolutionMode(BH1750_CONTINUOUS_LOW_RES_MODE);
+    //fDelayPtr(16+5); // Lesezeit in LowResMode
+    fDelayPtr(getModeDelay());
+    uint16_t level = readRawLevel();
+#if BH1750_DEBUG == 1
+    Serial.print("AutoHighMode: check level read: ");
+    Serial.println(level, DEC);
+#endif
+    if(level<10) {
+#if BH1750_DEBUG == 1
+    Serial.println("level 0: dark");
+#endif    
+      // Dunkel, Empfindlichkeit auf Maximum. 
+      // Der Wert ist zufällig. Ab ca. 16000 wäre diese Vorgehnsweise möglich.
+      // Ich brauche diese Genauigkeit aber nur in den ganz dunklen Bereichen (zu erkennen, wann wirklich 'dunkel' ist).
+      defineMTReg(BH1750_MTREG_MAX);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2);
+      //fDelayPtr(120*3.68+5); // TODO: Wert prüfen
+      fDelayPtr(getModeDelay());
+      //fDelayPtr(122);
+    }
+    else if(level<32767) {
+#if BH1750_DEBUG == 1
+    Serial.println("level 1: normal");
+#endif    
+      // Bis hierher reicht die 0,5 lx Modus. Normale Empfindlichkeit.
+      defineMTReg(BH1750_MTREG_DEFAULT);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2);
+      //fDelayPtr(120+5); // TODO: Wert prüfen
+      fDelayPtr(getModeDelay());
+    } 
+    else if(level<60000) {
+#if BH1750_DEBUG == 1
+    Serial.println("level 2: bright");
+#endif    
+      // hoher Bereich, 1 lx Modus, normale Empfindlichkeit. Der Wert von 60000 ist mehr oder weniger zufällig, es mus einfach ein hoher Wert, nah an der Grenze sein.
+      defineMTReg(BH1750_MTREG_DEFAULT);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE);
+      //fDelayPtr(120+5); // TODO: Wert prüfen
+      fDelayPtr(getModeDelay());
+    }
+    else {
+#if BH1750_DEBUG == 1
+    Serial.println("level 3: very bright");
+#endif    
+      // sehr hoher Bereich, Empfindlichkeit verringern
+      defineMTReg(32); // Min+1, bei dem Minimum aus Doku spielt der Sensor (zumindest meiner) verrückt: Die Werte sind ca. 1/10 von den Erwarteten.
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE);
+      //fDelayPtr(120+5); // TODO: Wert prüfen
+      fDelayPtr(getModeDelay());
+    }
+  } 
+
+  // Hardware Wert lesen und in Lux umrechnen.
+  uint16_t raw = readRawLevel();
+  if(raw==65535) {
+    // Wert verdächtig hoch. Sensor prüfen. 
+    // Check I2C Adresse
+    Wire.beginTransmission(_address);
+    if(Wire.endTransmission()!=0) {
+      return -1; 
+    }
+  }
+  return convertRawValue(raw);
+}*/
+
+/**
+ * Roh-Wert der Helligkeit auslesen. 
+ * Wertebereich 0-65535.
+ */
+uint16_t AS_BH1750A::readRawLevel(void) {
+  uint16_t level;
+  Wire.beginTransmission(_address);
+  Wire.requestFrom(_address, 2);
+#if (ARDUINO >= 100)
+  level = Wire.read();
+  level <<= 8;
+  level |= Wire.read();
+#else
+  level = Wire.receive();
+  level <<= 8;
+  level |= Wire.receive();
+#endif
+  if(Wire.endTransmission()!=0) {
+#if BH1750_DEBUG == 1
+    Serial.println("I2C read error");
+#endif
+    return 65535; // Error marker
+  }
+
+#if BH1750_DEBUG == 1
+  Serial.print("Raw light level: ");
+  Serial.println(level);
+#endif
+
+  _valueReaded=true;
+
+  return level;
+}
+
+/**
+ * Rechnet Roh-Werte in Lux um.
+ */
+float AS_BH1750A::convertRawValue(uint16_t raw) {
+  // Basisumrechnung
+  float flevel = raw/1.2;
+
+#if BH1750_DEBUG == 1
+  Serial.print("convert light level (1): ");
+  Serial.println(flevel);
+#endif
+
+  // MTreg-Einfluss berechnen
+  if(_MTreg!=BH1750_MTREG_DEFAULT) { // bei 69 ist der Faktor = 1
+    flevel = flevel * _MTregFactor;
+#if BH1750_DEBUG == 1
+    Serial.print("convert light level (2): ");
+    Serial.println(flevel);
+#endif
+  }
+
+  // je nach Modus ist eine weitere Umrechnung nötig
+  switch (_hardwareMode) {
+  case BH1750_CONTINUOUS_HIGH_RES_MODE_2:
+  case BH1750_ONE_TIME_HIGH_RES_MODE_2:
+    flevel = flevel/2;
+#if BH1750_DEBUG == 1
+    Serial.print("convert light level (3): ");
+    Serial.println(flevel);
+#endif
+    break;
+  default:
+    // nothing to do
+    break;
+  }
+
+#if BH1750_DEBUG == 1
+  Serial.print("Light level: ");
+  Serial.println(flevel);
+#endif
+
+  return flevel;
+}
+
+/**
+ * MTreg setzen. Definiert Empfindlichkeit des Sensors.
+ * Min.Wert (BH1750_MTREG_MIN) =  31 (Empfindlichkeit: default*0,45)
+ * Max.Wert (BH1750_MTREG_MAX) = 254 (Empfindlichkeit: default*3,68)
+ * Default (BH1750_MTREG_DEFAULT) = 69.
+ * Mit der Empfindlichkeit verändert sich die Lesezeit (höhere Empfindlichkeit bedeutet längere Zeitspanne).
+ */
+void AS_BH1750A::defineMTReg(uint8_t val) {
+  if(val<BH1750_MTREG_MIN) {
+    val = BH1750_MTREG_MIN;
+  }
+  if(val>BH1750_MTREG_MAX) {
+    val = BH1750_MTREG_MAX;
+  }
+  if(val!=_MTreg) {
+    _MTreg = val;
+    _MTregFactor = (float)BH1750_MTREG_DEFAULT / _MTreg;
+
+    // Change Measurement time
+    // Übertragung in zwei Schritten: 3 Bit und 5 Bit, jeweils mit einem Prefix.
+    //   High bit: 01000_MT[7,6,5]
+    //   Low bit:  011_MT[4,3,2,1,0]
+    uint8_t hiByte = val>>5;
+    hiByte |= 0b01000000;
+#if BH1750_DEBUG == 1
+    Serial.print("MGTreg high byte: ");
+    Serial.println(hiByte, BIN);
+#endif
+    write8(hiByte);
+    //fDelayPtr(10);
+    // Pause nötig?
+    uint8_t loByte = val&0b00011111;
+    loByte |= 0b01100000;
+#if BH1750_DEBUG == 1
+    Serial.print("MGTreg low byte: ");
+    Serial.println(loByte, BIN);
+#endif
+    write8(hiByte);
+    //fDelayPtr(10);
+  }
+}
+
+/**
+ * Gibt an, ob der Sensor initialisiert ist.
+ */
+bool AS_BH1750A::isInitialized() {
+  return _hardwareMode!=255; 
+}
+
+/**
+ * Schreibt ein Byte auf I2C Bus (auf die Adresse des Sensors).
+ */
+bool AS_BH1750A::write8(uint8_t d) {
+  Wire.beginTransmission(_address);
+#if (ARDUINO >= 100)
+  Wire.write(d);
+#else
+  Wire.send(d);
+#endif
+  return (Wire.endTransmission()==0);
+}
+
+
+float AS_BH1750A::readLightLevel(DelayFuncPtr fDelayPtr, TimeFuncPtr fTimePtr) {
+  startMeasurementAsync(fTimePtr);
+  while(!isMeasurementReady()) {
+    fDelayPtr(_nextDelay);
+  }
+  return readLightLevelAsync();
+}
+
+/*float AS_BH1750A::checkAndReadLightLevelAsync(TimeFuncPtr fTimePtr) {
+  //Serial.print("--stage: ");
+  //Serial.println(_stage);
+  if(_stage==0 || _stage>=100) {
+    if(startMeasurementAsync(fTimePtr)) return -100;
+    return -1; // Marker "nicht vorhanden"
+  } else if(_stage==99) {
+    _stage=100;
+    return readLightLevelAsync();
+  }
+  return readLightLevelAsync();
+}*/
+
+unsigned long AS_BH1750A::nextDelay(void) {
+  return _nextDelay;
+}
+//void AS_BH1750A::reset(void) {
+//_stage==0;
+//}
+
+bool AS_BH1750A::startMeasurementAsync(TimeFuncPtr fTimePtr) {
+  _fTimePtr = fTimePtr;
+  _stage = 0;
+  _nextDelay = 0;
+  int _lastResult = -1;
+  _lastTimestamp=fTimePtr();
+  return readLightLevelAsync()!=-1;
+}
+
+bool AS_BH1750A::isMeasurementReady(void) {
+  return readLightLevelAsync() >= -1; // -1 ist 'not initialized'-Marker (prevent hangs)
+}
+
+bool AS_BH1750A::delayExpired() {
+  unsigned long timestamp = _fTimePtr();
+
+  // Zeitdifferenz
+  unsigned long delayTime = 0;
+  // Auf Ueberlauf pruefen
+  if (timestamp < _lastTimestamp)
+  {
+    // Ueberlauf: Delay ist Zeit zum MaxWert plus Zeit ab Null
+    delayTime = MAX_U_LONG - _lastTimestamp + timestamp;
+  } else {
+    // Kein Ueberlauf: einfache Differenz
+    delayTime = timestamp - _lastTimestamp;
+  }
+
+  return (delayTime >= _nextDelay);
+}
+
+float AS_BH1750A::readLightLevelAsync() {
+  if(_stage>=99) return _lastResult;
+  
+  if(_stage==0) {
+    #if BH1750_DEBUG == 1
+      Serial.print("call: readLightLevel. virtualMode: ");
+      Serial.println(_virtualMode, DEC);
+    #endif
+
+    if(!isInitialized()) {
+    #if BH1750_DEBUG == 1
+      Serial.println("sensor not initialized");
+    #endif
+      return -1;
+    }
+
+    // ggf. PowerOn  
+    if(_autoPowerDown && _valueReaded){
+      powerOn();
+      _nextDelay = getModeDelay();
+      // TEST delay(getModeDelay());_stage=99;
+      return -100;
+    } else {
+      _nextDelay = 0;
+    }
+    _stage++;
+
+  }
+  
+  // Das Automatische Modus benötigt eine Sonderbehandlung.
+  // Zuerst wird die Helligkeit im LowRes-Modus gelesen, 
+  // je nach Bereich (dunkel, normal, sehr hell) werden die Werte von MTreg gesetzt und
+  // danach wird die eigentliche Messung vorgenommen.
+  /*
+     Die feste Grenzwerte verursachen möglicherweise einen 'Sprung' in der Messkurve. 
+   In diesem Fall wäre eine laufende Anpassung von MTreg in Grenzbereichen vermutlich besser.
+   Für meine Zwecke ist das jedoch ohne Bedeutung.
+   */
+  if(_virtualMode==RESOLUTION_AUTO_HIGH) {
+    selectAutoMode();
+  } else {
+    if(delayExpired()) {_stage=99;} // Fertig
+  }
+  if(_stage<99) return -100; // Marker: wait for next step
+
+  // Hardware Wert lesen und in Lux umrechnen.
+  uint16_t raw = readRawLevel();
+  if(raw==65535) {
+    // Wert verdächtig hoch. Sensor prüfen. 
+    // Check I2C Adresse
+    Wire.beginTransmission(_address);
+    if(Wire.endTransmission()!=0) {
+      return -1; 
+    }
+  }
+  
+  _lastResult = convertRawValue(raw);
+  return _lastResult;
+}
+
+void AS_BH1750A::selectAutoMode() {
+
+  //if(!delayExpired()) return;
+   
+  if(_stage==1) {   
+    defineMTReg(BH1750_MTREG_DEFAULT);
+    selectResolutionMode(BH1750_CONTINUOUS_LOW_RES_MODE);
+    //fDelayPtr(16+5); // Lesezeit in LowResMode
+    //fDelayPtr(getModeDelay());
+    _nextDelay=getModeDelay();
+    _stage++;
+    return;
+  }
+  
+  if(!delayExpired()) return;
+
+  if(_stage==2) {
+    uint16_t level = readRawLevel();
+    #if BH1750_DEBUG == 1
+    Serial.print("AutoHighMode: check level read: ");
+    Serial.println(level, DEC);
+    #endif
+    if(level<10) {
+    #if BH1750_DEBUG == 1
+    Serial.println("level 0: dark");
+    #endif    
+      // Dunkel, Empfindlichkeit auf Maximum. 
+      // Der Wert ist zufällig. Ab ca. 16000 wäre diese Vorgehnsweise möglich.
+      // Ich brauche diese Genauigkeit aber nur in den ganz dunklen Bereichen (zu erkennen, wann wirklich 'dunkel' ist).
+      defineMTReg(BH1750_MTREG_MAX);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2);
+      //fDelayPtr(120*3.68+5); // TODO: Wert prüfen
+      //fDelayPtr(getModeDelay());
+      _nextDelay=getModeDelay();
+      //fDelayPtr(122);
+    } else if(level<32767) {
+    #if BH1750_DEBUG == 1
+    Serial.println("level 1: normal");
+    #endif    
+      // Bis hierher reicht die 0,5 lx Modus. Normale Empfindlichkeit.
+      defineMTReg(BH1750_MTREG_DEFAULT);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE_2:BH1750_CONTINUOUS_HIGH_RES_MODE_2);
+      //fDelayPtr(120+5); // TODO: Wert prüfen
+      //fDelayPtr(getModeDelay());
+      _nextDelay=getModeDelay();
+    } else if(level<60000) {
+    #if BH1750_DEBUG == 1
+    Serial.println("level 2: bright");
+    #endif    
+      // hoher Bereich, 1 lx Modus, normale Empfindlichkeit. Der Wert von 60000 ist mehr oder weniger zufällig, es mus einfach ein hoher Wert, nah an der Grenze sein.
+      defineMTReg(BH1750_MTREG_DEFAULT);
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE);
+      //fDelayPtr(120+5); // TODO: Wert prüfen
+      //fDelayPtr(getModeDelay());
+      _nextDelay=getModeDelay();
+    } else {
+    #if BH1750_DEBUG == 1
+    Serial.println("level 3: very bright");
+    #endif    
+      // sehr hoher Bereich, Empfindlichkeit verringern
+      defineMTReg(32); // Min+1, bei dem Minimum aus Doku spielt der Sensor (zumindest meiner) verrückt: Die Werte sind ca. 1/10 von den Erwarteten.
+      selectResolutionMode(_autoPowerDown?BH1750_ONE_TIME_HIGH_RES_MODE:BH1750_CONTINUOUS_HIGH_RES_MODE);
+      //fDelayPtr(120+5); // TODO: Wert prüfen
+      //fDelayPtr(getModeDelay());
+      _nextDelay=getModeDelay();
+    }
+    
+    _stage++;
+    return;
+  }
+    
+  _stage = 99; // fertig
+
+}
+
+unsigned long AS_BH1750A::getModeDelay() {
+
+  float ml;
+  
+  if(_MTreg<=BH1750_MTREG_MIN+1) { ml = 0.45; }
+  else if(_MTreg<=BH1750_MTREG_DEFAULT+1) { ml = 1.0; }
+  else { ml = 3.68; }
+  
+  switch (_hardwareMode) {
+  case BH1750_CONTINUOUS_HIGH_RES_MODE:
+  case BH1750_ONE_TIME_HIGH_RES_MODE:
+    return ml*120+5;
+    
+  case BH1750_ONE_TIME_HIGH_RES_MODE_2:
+  case BH1750_CONTINUOUS_HIGH_RES_MODE_2:
+    return ml*120+5;
+    
+  case BH1750_CONTINUOUS_LOW_RES_MODE:
+  case BH1750_ONE_TIME_LOW_RES_MODE:
+    return ml*16+5;
+  
+  default:
+    return 0;
+  }
+
+}

+ 207 - 0
Software/src/WiFiPCController/lib/AS_BH1750/AS_BH1750A.h

@@ -0,0 +1,207 @@
+/*
+ This is a (Arduino) library for the BH1750FVI Digital Light Sensor.
+ 
+ Description:
+ http://www.rohm.com/web/global/products/-/product/BH1750FVI
+ 
+ Datasheet:
+ http://rohmfs.rohm.com/en/products/databook/datasheet/ic/sensor/light/bh1750fvi-e.pdf
+ 
+ Copyright (c) 2013 Alexander Schulz.  All right reserved.
+ 
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+ 
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the GNU
+ Lesser General Public License for more details.
+ 
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA	 02110-1301	 USA
+ */
+
+#ifndef AS_BH1750A_h
+#define AS_BH1750A_h
+
+#if (ARDUINO >= 100)
+#include <Arduino.h>
+#else
+#include <WProgram.h>
+#endif
+#include "Wire.h"
+
+// Mögliche I2C Adressen
+#define BH1750_DEFAULT_I2CADDR 0x23
+#define BH1750_SECOND_I2CADDR 0x5C
+
+// MTreg Werte
+// Default
+#define BH1750_MTREG_DEFAULT 69
+// Sensitivity : default = 0.45
+#define BH1750_MTREG_MIN 31
+// Sensitivity : default = 3.68
+#define BH1750_MTREG_MAX 254
+
+// Hardware Modi
+// No active state
+#define BH1750_POWER_DOWN 0x00
+
+// Wating for measurment command
+#define BH1750_POWER_ON 0x01
+
+// Reset data register value - not accepted in POWER_DOWN mode
+#define BH1750_RESET 0x07
+
+// Start measurement at 1lx resolution. Measurement time is approx 120ms.
+#define BH1750_CONTINUOUS_HIGH_RES_MODE  0x10
+
+// Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
+#define BH1750_CONTINUOUS_HIGH_RES_MODE_2  0x11
+
+// Start measurement at 4lx resolution. Measurement time is approx 16ms.
+#define BH1750_CONTINUOUS_LOW_RES_MODE  0x13
+
+// Start measurement at 1lx resolution. Measurement time is approx 120ms.
+// Device is automatically set to Power Down after measurement.
+#define BH1750_ONE_TIME_HIGH_RES_MODE  0x20
+
+// Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
+// Device is automatically set to Power Down after measurement.
+#define BH1750_ONE_TIME_HIGH_RES_MODE_2  0x21
+
+// Start measurement at 1lx resolution. Measurement time is approx 120ms.
+// Device is automatically set to Power Down after measurement.
+#define BH1750_ONE_TIME_LOW_RES_MODE  0x23
+
+#define MAX_U_LONG 4294967295;
+
+/** Virtual Modi */
+typedef enum
+{
+  RESOLUTION_LOW         = (1), /** 4lx resolution. Measurement time is approx 16ms. */  
+  RESOLUTION_NORMAL      = (2), /** 1lx resolution. Measurement time is approx 120ms. */
+  RESOLUTION_HIGH        = (3), /** 0,5lx resolution. Measurement time is approx 120ms. */
+  RESOLUTION_AUTO_HIGH   = (99) /** 0,11-1lx resolution. Measurement time is above 250ms. */
+  }  
+  sensors_resolution_t;
+
+typedef void (*DelayFuncPtr)(unsigned long);
+typedef unsigned long (*TimeFuncPtr)(void);
+
+/**
+ * BH1750 driver class.
+ */
+class AS_BH1750A {
+public:
+  /**
+   * Constructor.
+   * Erlaubt die I2C-Adresse des Sensors zu ändern. 
+   * Standardadresse: 0x23, Alternativadresse: 0x5C. 
+   * Es sind entsprechende Konstanten definiert: BH1750_DEFAULT_I2CADDR  und BH1750_SECOND_I2CADDR.
+   * Bei Nichtangabe wird die Standardadresse verwendet. 
+   * Um die Alternativadresse zu nutzen, muss der Sensorpin 'ADR' des Chips auf VCC gelegt werden.
+   */
+  AS_BH1750A(uint8_t address = BH1750_DEFAULT_I2CADDR);
+
+  /**
+   * Führt die anfängliche Initialisierung des Sensors.
+   * Mögliche Parameter: 
+   *  - Modus für die Sensorauflösung:
+   *    -- RESOLUTION_LOW:         Physische Sensormodus mit 4 lx Auflösung. Messzeit ca. 16ms. Bereich 0-54612.
+   *    -- RESOLUTION_NORMAL:      Physische Sensormodus mit 1 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+   *    -- RESOLUTION_HIGH:        Physische Sensormodus mit 0,5 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+   *                               (Die Messbereiche können durch Änderung des MTreg verschoben werden.)
+   *    -- RESOLUTION_AUTO_HIGH:   Die Werte im MTreg werden je nach Helligkeit automatisch so angepasst, 
+   *                               dass eine maximalmögliche Auflösung und Messbereich erziehlt werden.
+   *                               Die messbaren Werte fangen von 0,11 lx und gehen bis über 100000 lx.
+   *                               (ich weis nicht, wie genau die Werte in Grenzbereichen sind, 
+   *                               besonders bei hohen Werte habe ich meine Zweifel.)
+   *                               Auflösung im Unteren Bereich 0,13 lx, im mittleren 0,5 lx, im oberen 1-2 lx.
+   *                               Die Messzeiten verlängern sich durch mehrfache Messungen und 
+   *                               die Änderungen von Measurement Time (MTreg) bis max. ca. 500 ms.
+   *   
+   * - AutoPowerDown: true = Der Sensor wird nach der Messung in den Stromsparmodus versetzt. 
+   *   Das spätere Aufwecken wird ggf. automatisch vorgenommen, braucht jedoch geringfügig mehr Zeit.
+   *
+   * Defaultwerte: RESOLUTION_AUTO_HIGH, true, delay()
+   *
+   */
+  bool begin(sensors_resolution_t mode = RESOLUTION_AUTO_HIGH, bool autoPowerDown = true);
+
+  /**
+   * Erlaub eine Prüfung, ob ein (ansprechbarer) BH1750-Sensor vorhanden ist.
+   */
+  bool isPresent(void);
+
+  /**
+   * Liefert aktuell gemessenen Wert für Helligkeit in lux (lx).
+   * Falls sich der Sensorf in Stromsparmodus befindet, wird er automatisch geweckt.
+   *
+   * Wurde der Sensor (noch) nicht initialisiert (begin), wird der Wert -1 geliefert.
+   *
+   * Mögliche Parameter: 
+   *
+   * - DelayFuncPtr: delay(n) Möglichkeit, eigene Delay-Funktion mitzugeben (z.B. um sleep-Modus zu verwenden).
+   * 
+   * Defaultwerte: delay()
+   *
+   */
+  float readLightLevel(DelayFuncPtr fDelayPtr = &delay, TimeFuncPtr fTimePtr = &millis);
+
+  bool startMeasurementAsync(TimeFuncPtr fTimePtr = &millis);
+  bool isMeasurementReady(void);
+  float readLightLevelAsync();
+  
+  //float checkAndReadLightLevelAsync(TimeFuncPtr fTimePtr);
+
+  //void reset(void);
+  unsigned long nextDelay(void);
+
+  /**
+   * Schickt den Sensor in Stromsparmodus.
+   * Funktionier nur, wenn der Sensor bereits initialisiert wurde.
+   */
+  void powerDown(void);
+
+//bool delayExpired(); // TEST
+
+private:
+  int _address;
+  uint8_t _hardwareMode;
+
+  uint8_t _MTreg;
+  float _MTregFactor;
+
+  sensors_resolution_t _virtualMode;
+  bool _autoPowerDown;
+
+  bool _valueReaded;
+
+  bool selectResolutionMode(uint8_t mode);
+  void defineMTReg(uint8_t val);
+  void powerOn();
+  //void reset(void);
+  uint16_t readRawLevel(void);
+  float convertRawValue(uint16_t raw);
+  bool isInitialized();
+  bool write8(uint8_t data);
+  
+  TimeFuncPtr _fTimePtr;
+  int _stage = 0;
+  int _nextDelay = 0;
+  int _lastTimestamp = 0;
+  float _lastResult = -100;
+  bool delayExpired();
+  void selectAutoMode();
+  
+  // TEST
+  //float readLightLevel_alt(DelayFuncPtr fDelayPtr = &delay);
+  unsigned long getModeDelay();
+  
+};
+
+#endif

+ 502 - 0
Software/src/WiFiPCController/lib/AS_BH1750/LICENSE

@@ -0,0 +1,502 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    a (Arduino) library for the BH1750FVI Digital Light Sensor
+    Copyright (C) 2013  Alex Hexenmeister
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  {signature of Ty Coon}, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!

+ 37 - 0
Software/src/WiFiPCController/lib/AS_BH1750/README.md

@@ -0,0 +1,37 @@
+AS_BH1750
+=========
+
+a (Arduino) library for the BH1750FVI Digital Light Sensor.
+
+(http://s6z.de/joomla3/index.php/arduino/sensoren/15-umgebungslichtsensor-bh1750)
+
+
+Features:
+
+ - unterstützt beide möglichen I2C-Adressen des Sensors:
+   Standardadresse: 0x23, Alternativadresse: 0x5C. 
+   Es sind entsprechende Konstanten definiert: BH1750_DEFAULT_I2CADDR und BH1750_SECOND_I2CADDR.
+ 
+ - alle Hardware-Auflösungsmodi:
+   * RESOLUTION_LOW:         Physische Sensormodus mit 4 lx Auflösung. Messzeit ca. 16ms. Bereich 0-54612. 
+   * RESOLUTION_NORMAL:      Physische Sensormodus mit 1 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+   * RESOLUTION_HIGH:        Physische Sensormodus mit 0,5 lx Auflösung. Messzeit ca. 120ms. Bereich 0-54612.
+
+ - Virtueller Modus:
+   * RESOLUTION_AUTO_HIGH:   Die Werte im MTreg ('Measurement Time'-Register) werden je nach Helligkeit 
+                              automatisch so angepasst, dass eine maximalmögliche Auflösung 
+                              und Messbereich erziehlt werden.
+                              Die messbaren Werte fangen von 0,11 lx und gehen bis über 100000 lx.
+                              (ich weis nicht, wie genau die Werte in Grenzbereichen sind, 
+                              besonders bei hohen Werte habe ich da meine Zweifel. 
+                              Die Werte scheinen jedoch weitgehend linear mit der steigenden Helligkeit zu wachsen.)
+                              Auflösung im Unteren Bereich ca. 0,13 lx, im mittleren 0,5 lx, im oberen etwa 1-2 lx.
+                              Die Messzeiten verlängern sich durch mehrfache Messungen und 
+                              die Änderungen von Measurement Time (MTreg) bis max. ca. 500 ms.
+ 
+ - Methode zur Änderung von 'Measurement Time'-Registers. Damit kann die Empfindlichkeit beeinflusst werden.
+ 
+ - auto power down: Der Sensor wird nach der Messung in den Stromsparmodus versetzt. 
+   Das spätere Aufwecken wird ggf. automatisch vorgenommen, braucht jedoch geringfügig mehr Zeit.
+
+ Defaultwerte: Modus = RESOLUTION_AUTO_HIGH, AutoPowerDown = true

+ 68 - 0
Software/src/WiFiPCController/lib/AS_BH1750/examples/BH1750Simple/BH1750Simple.ino

@@ -0,0 +1,68 @@
+/*
+ *  Example of AS_BH1750 library usage.
+ *  
+ *  This example initalises the BH1750 object using the default
+ *  adaptive auto-high-resolution mode and then makes 
+ *  a light level reading about every second.
+ *  (the sensitivity and resolution are automatically adapted 
+ *  the current light conditions)
+ *  
+ *  Wiring:
+ *  VCC-5v
+ *  GND-GND
+ *  SCL-SCL(analog pin 5)
+ *  SDA-SDA(analog pin 4)
+ *  ADD-NC or GND
+ *
+ *  Copyright (c) 2013 Alexander Schulz.  All right reserved.
+ *  
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ * 
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <Wire.h>
+#include <AS_BH1750.h>
+
+AS_BH1750 sensor;
+
+void setup(){
+  Serial.begin(9600);
+  delay(50);
+  
+  // for normal sensor resolution (1 lx resolution, 0-65535 lx, 120ms, no PowerDown) use: sensor.begin(RESOLUTION_NORMAL, false);
+  // Initialize sensor. if sensor is not present, false is returned
+  if(sensor.begin()) {
+    Serial.println("Sensor initialized");
+  } 
+  else {
+    Serial.println("Sensor not present");
+  }
+  
+  /*
+  // to check the sensor present
+  if(sensor.isPresent()) {
+    Serial.println("Sensor present");
+  } 
+  else {
+    Serial.println("Sensor not present");
+  }*/
+}
+
+void loop() {
+  float lux = sensor.readLightLevel();
+  Serial.print("Light level: ");
+  Serial.print(lux);
+  Serial.println(" lx");
+  delay(1000);
+}
+

+ 96 - 0
Software/src/WiFiPCController/lib/AS_BH1750/examples/LightMeter_LCD/LightMeter_LCD.ino

@@ -0,0 +1,96 @@
+#include <Wire.h>
+#include <AS_BH1750.h>
+#include <LiquidCrystal.h>
+
+/* 
+ * LightMeter_LCD
+ * 
+ * Version 1.2
+ * Datum: 05.08.2013
+ * 
+ * Das Programm benutzt den BH1750 (Umgebiungslichtsensor)
+ * und zeigt die Werte in Lux auf einem 16x2-Symbol-LCD.
+ * 
+ * Verdrahtung (UNO, Nano...)
+ * 
+ * BH1750:
+ *     Sensor SCL pin an A5
+ *     Sensor SDA pin an A4
+ *     Sensor VDD pin an 5V
+ *     Sensor GND pin an GND
+ *     Sensor ADDR pin frei
+ *  
+ * LCD in 4-Bit-Modus:
+ *     LCD RS pin an digital pin 8
+ *     LCD RW pin an digital pin 13
+ *     LCD Enable pin an digital pin 9
+ *     LCD D4 pin an digital pin 4
+ *     LCD D5 pin an digital pin 5
+ *     LCD D6 pin an digital pin 6
+ *     LCD D7 pin an digital pin 7
+ * 
+ *
+ *   Copyright (c) 2013 Alexander Schulz.  All right reserved.
+ *  
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ * 
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+AS_BH1750 lightMeter;
+
+// Setup LCD-Shield
+LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);
+
+void setup() {
+  // Display initialisieren
+  lcd.clear(); 
+  lcd.begin(16, 2); // 16x2 Zeichen
+  lcd.setCursor(0,0); 
+  lcd.print("LightMeter v1.0"); 
+  lcd.setCursor(0,1); 
+  lcd.print("Initializing..."); 
+  delay(1000);
+  lcd.clear();
+
+  if(!lightMeter.begin()){
+    // Prüfen, ob Sensor vorhanden ist
+    lcd.clear();
+    lcd.setCursor(0,0); 
+    lcd.print("BH1750 not found");
+    lcd.setCursor(0,1); 
+    lcd.print("check wiring!");
+    while (1) {
+      delay(1000);
+    }
+  }
+
+}
+
+void loop() {
+  char clux[9];
+
+  // Werte auslesen und aufbereiten
+  float lux = lightMeter.readLightLevel();
+  dtostrf(lux, 8, 1, clux);
+
+  lcd.setCursor(0,0); 
+  lcd.print("Light level: ");
+  lcd.setCursor(5,1); 
+  lcd.print(clux);
+  lcd.print(" lx");
+
+  delay(500);
+}
+
+
+

+ 30 - 0
Software/src/WiFiPCController/lib/AS_BH1750/keywords.txt

@@ -0,0 +1,30 @@
+#######################################
+# Syntax Coloring Map For BH1750
+#######################################
+
+#######################################
+# Datatypes (KEYWORD1)
+#######################################
+
+AS_BH1750            KEYWORD1
+sensors_resolution_t KEYWORD1
+
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+
+begin          KEYWORD2
+isPresent      KEYWORD2
+readLightLevel KEYWORD2
+powerDown      KEYWORD2
+
+
+#######################################
+# Instances (KEYWORD2)
+#######################################
+
+
+#######################################
+# Constants (LITERAL1)
+#######################################

BIN
Software/src/WiFiPCController/lib/Arduino-DHT22-master.zip


BIN
Software/src/WiFiPCController/lib/DHT-sensor-library-master.zip


BIN
Software/src/WiFiPCController/lib/DHT.7z


+ 46 - 0
Software/src/WiFiPCController/lib/DHT/.github/ISSUE_TEMPLATE.md

@@ -0,0 +1,46 @@
+Thank you for opening an issue on an Adafruit Arduino library repository.  To
+improve the speed of resolution please review the following guidelines and
+common troubleshooting steps below before creating the issue:
+
+- **Do not use GitHub issues for troubleshooting projects and issues.**  Instead use
+  the forums at http://forums.adafruit.com to ask questions and troubleshoot why
+  something isn't working as expected.  In many cases the problem is a common issue
+  that you will more quickly receive help from the forum community.  GitHub issues
+  are meant for known defects in the code.  If you don't know if there is a defect
+  in the code then start with troubleshooting on the forum first.
+
+- **If following a tutorial or guide be sure you didn't miss a step.** Carefully
+  check all of the steps and commands to run have been followed.  Consult the
+  forum if you're unsure or have questions about steps in a guide/tutorial.
+
+- **For Arduino projects check these very common issues to ensure they don't apply**:
+
+  - For uploading sketches or communicating with the board make sure you're using
+    a **USB data cable** and **not** a **USB charge-only cable**.  It is sometimes
+    very hard to tell the difference between a data and charge cable!  Try using the
+    cable with other devices or swapping to another cable to confirm it is not
+    the problem.
+
+  - **Be sure you are supplying adequate power to the board.**  Check the specs of
+    your board and plug in an external power supply.  In many cases just
+    plugging a board into your computer is not enough to power it and other
+    peripherals.
+
+  - **Double check all soldering joints and connections.**  Flakey connections
+    cause many mysterious problems.  See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints.
+
+  - **Ensure you are using an official Arduino or Adafruit board.** We can't
+    guarantee a clone board will have the same functionality and work as expected
+    with this code and don't support them.
+
+If you're sure this issue is a defect in the code and checked the steps above
+please fill in the following fields to provide enough troubleshooting information.
+You may delete the guideline and text above to just leave the following details:
+
+- Arduino board:  **INSERT ARDUINO BOARD NAME/TYPE HERE**
+
+- Arduino IDE version (found in Arduino -> About Arduino menu):  **INSERT ARDUINO
+  VERSION HERE**
+
+- List the steps to reproduce the problem below (if possible attach a sketch or
+  copy the sketch code in too): **LIST REPRO STEPS BELOW**

+ 26 - 0
Software/src/WiFiPCController/lib/DHT/.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,26 @@
+Thank you for creating a pull request to contribute to Adafruit's GitHub code!
+Before you open the request please review the following guidelines and tips to
+help it be more easily integrated:
+
+- **Describe the scope of your change--i.e. what the change does and what parts
+  of the code were modified.**  This will help us understand any risks of integrating
+  the code.
+
+- **Describe any known limitations with your change.**  For example if the change
+  doesn't apply to a supported platform of the library please mention it.
+
+- **Please run any tests or examples that can exercise your modified code.**  We
+  strive to not break users of the code and running tests/examples helps with this
+  process.
+
+Thank you again for contributing!  We will try to test and integrate the change
+as soon as we can, but be aware we have many GitHub repositories to manage and
+can't immediately respond to every request.  There is no need to bump or check in
+on a pull request (it will clutter the discussion of the request).
+
+Also don't be worried if the request is closed or not integrated--sometimes the
+priorities of Adafruit's GitHub code (education, ease of use) might not match the
+priorities of the pull request.  Don't fret, the open source community thrives on
+forks and GitHub makes it easy to keep your changes in a forked repo.
+
+After reviewing the guidelines above you can delete this text from the pull request.

+ 259 - 0
Software/src/WiFiPCController/lib/DHT/DHT.cpp

@@ -0,0 +1,259 @@
+/* DHT library
+
+MIT license
+written by Adafruit Industries
+*/
+
+#include "DHT.h"
+
+#define MIN_INTERVAL 2000
+
+DHT::DHT(uint8_t pin, uint8_t type, uint8_t count) {
+  _pin = pin;
+  _type = type;
+  #ifdef __AVR
+    _bit = digitalPinToBitMask(pin);
+    _port = digitalPinToPort(pin);
+  #endif
+  _maxcycles = microsecondsToClockCycles(1000);  // 1 millisecond timeout for
+                                                 // reading pulses from DHT sensor.
+  // Note that count is now ignored as the DHT reading algorithm adjusts itself
+  // basd on the speed of the processor.
+}
+
+void DHT::begin(void) {
+  // set up the pins!
+  pinMode(_pin, INPUT_PULLUP);
+  // Using this value makes sure that millis() - lastreadtime will be
+  // >= MIN_INTERVAL right away. Note that this assignment wraps around,
+  // but so will the subtraction.
+  _lastreadtime = -MIN_INTERVAL;
+  DEBUG_PRINT("Max clock cycles: "); DEBUG_PRINTLN(_maxcycles, DEC);
+}
+
+//boolean S == Scale.  True == Fahrenheit; False == Celcius
+float DHT::readTemperature(bool S, bool force) {
+  float f = NAN;
+
+  if (read(force)) {
+    switch (_type) {
+    case DHT11:
+      f = data[2];
+      if(S) {
+        f = convertCtoF(f);
+      }
+      break;
+    case DHT22:
+    case DHT21:
+      f = data[2] & 0x7F;
+      f *= 256;
+      f += data[3];
+      f *= 0.1;
+      if (data[2] & 0x80) {
+        f *= -1;
+      }
+      if(S) {
+        f = convertCtoF(f);
+      }
+      break;
+    }
+  }
+  return f;
+}
+
+float DHT::convertCtoF(float c) {
+  return c * 1.8 + 32;
+}
+
+float DHT::convertFtoC(float f) {
+  return (f - 32) * 0.55555;
+}
+
+float DHT::readHumidity(bool force) {
+  float f = NAN;
+  if (read()) {
+    switch (_type) {
+    case DHT11:
+      f = data[0];
+      break;
+    case DHT22:
+    case DHT21:
+      f = data[0];
+      f *= 256;
+      f += data[1];
+      f *= 0.1;
+      break;
+    }
+  }
+  return f;
+}
+
+//boolean isFahrenheit: True == Fahrenheit; False == Celcius
+float DHT::computeHeatIndex(float temperature, float percentHumidity, bool isFahrenheit) {
+  // Using both Rothfusz and Steadman's equations
+  // http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
+  float hi;
+
+  if (!isFahrenheit)
+    temperature = convertCtoF(temperature);
+
+  hi = 0.5 * (temperature + 61.0 + ((temperature - 68.0) * 1.2) + (percentHumidity * 0.094));
+
+  if (hi > 79) {
+    hi = -42.379 +
+             2.04901523 * temperature +
+            10.14333127 * percentHumidity +
+            -0.22475541 * temperature*percentHumidity +
+            -0.00683783 * pow(temperature, 2) +
+            -0.05481717 * pow(percentHumidity, 2) +
+             0.00122874 * pow(temperature, 2) * percentHumidity +
+             0.00085282 * temperature*pow(percentHumidity, 2) +
+            -0.00000199 * pow(temperature, 2) * pow(percentHumidity, 2);
+
+    if((percentHumidity < 13) && (temperature >= 80.0) && (temperature <= 112.0))
+      hi -= ((13.0 - percentHumidity) * 0.25) * sqrt((17.0 - abs(temperature - 95.0)) * 0.05882);
+
+    else if((percentHumidity > 85.0) && (temperature >= 80.0) && (temperature <= 87.0))
+      hi += ((percentHumidity - 85.0) * 0.1) * ((87.0 - temperature) * 0.2);
+  }
+
+  return isFahrenheit ? hi : convertFtoC(hi);
+}
+
+boolean DHT::read(bool force) {
+  // Check if sensor was read less than two seconds ago and return early
+  // to use last reading.
+  uint32_t currenttime = millis();
+  if (!force && ((currenttime - _lastreadtime) < 2000)) {
+    return _lastresult; // return last correct measurement
+  }
+  _lastreadtime = currenttime;
+
+  // Reset 40 bits of received data to zero.
+  data[0] = data[1] = data[2] = data[3] = data[4] = 0;
+
+  // Send start signal.  See DHT datasheet for full signal diagram:
+  //   http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf
+
+  // Go into high impedence state to let pull-up raise data line level and
+  // start the reading process.
+  digitalWrite(_pin, HIGH);
+  delay(250);
+
+  // First set data line low for 20 milliseconds.
+  pinMode(_pin, OUTPUT);
+  digitalWrite(_pin, LOW);
+  delay(20);
+
+  uint32_t cycles[80];
+  {
+    // Turn off interrupts temporarily because the next sections are timing critical
+    // and we don't want any interruptions.
+    InterruptLock lock;
+
+    // End the start signal by setting data line high for 40 microseconds.
+    digitalWrite(_pin, HIGH);
+    delayMicroseconds(40);
+
+    // Now start reading the data line to get the value from the DHT sensor.
+    pinMode(_pin, INPUT_PULLUP);
+    delayMicroseconds(10);  // Delay a bit to let sensor pull data line low.
+
+    // First expect a low signal for ~80 microseconds followed by a high signal
+    // for ~80 microseconds again.
+    if (expectPulse(LOW) == 0) {
+      DEBUG_PRINTLN(F("Timeout waiting for start signal low pulse."));
+      _lastresult = false;
+      return _lastresult;
+    }
+    if (expectPulse(HIGH) == 0) {
+      DEBUG_PRINTLN(F("Timeout waiting for start signal high pulse."));
+      _lastresult = false;
+      return _lastresult;
+    }
+
+    // Now read the 40 bits sent by the sensor.  Each bit is sent as a 50
+    // microsecond low pulse followed by a variable length high pulse.  If the
+    // high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
+    // then it's a 1.  We measure the cycle count of the initial 50us low pulse
+    // and use that to compare to the cycle count of the high pulse to determine
+    // if the bit is a 0 (high state cycle count < low state cycle count), or a
+    // 1 (high state cycle count > low state cycle count). Note that for speed all
+    // the pulses are read into a array and then examined in a later step.
+    for (int i=0; i<80; i+=2) {
+      cycles[i]   = expectPulse(LOW);
+      cycles[i+1] = expectPulse(HIGH);
+    }
+  } // Timing critical code is now complete.
+
+  // Inspect pulses and determine which ones are 0 (high state cycle count < low
+  // state cycle count), or 1 (high state cycle count > low state cycle count).
+  for (int i=0; i<40; ++i) {
+    uint32_t lowCycles  = cycles[2*i];
+    uint32_t highCycles = cycles[2*i+1];
+    if ((lowCycles == 0) || (highCycles == 0)) {
+      DEBUG_PRINTLN(F("Timeout waiting for pulse."));
+      _lastresult = false;
+      return _lastresult;
+    }
+    data[i/8] <<= 1;
+    // Now compare the low and high cycle times to see if the bit is a 0 or 1.
+    if (highCycles > lowCycles) {
+      // High cycles are greater than 50us low cycle count, must be a 1.
+      data[i/8] |= 1;
+    }
+    // Else high cycles are less than (or equal to, a weird case) the 50us low
+    // cycle count so this must be a zero.  Nothing needs to be changed in the
+    // stored data.
+  }
+
+  DEBUG_PRINTLN(F("Received:"));
+  DEBUG_PRINT(data[0], HEX); DEBUG_PRINT(F(", "));
+  DEBUG_PRINT(data[1], HEX); DEBUG_PRINT(F(", "));
+  DEBUG_PRINT(data[2], HEX); DEBUG_PRINT(F(", "));
+  DEBUG_PRINT(data[3], HEX); DEBUG_PRINT(F(", "));
+  DEBUG_PRINT(data[4], HEX); DEBUG_PRINT(F(" =? "));
+  DEBUG_PRINTLN((data[0] + data[1] + data[2] + data[3]) & 0xFF, HEX);
+
+  // Check we read 40 bits and that the checksum matches.
+  if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
+    _lastresult = true;
+    return _lastresult;
+  }
+  else {
+    DEBUG_PRINTLN(F("Checksum failure!"));
+    _lastresult = false;
+    return _lastresult;
+  }
+}
+
+// Expect the signal line to be at the specified level for a period of time and
+// return a count of loop cycles spent at that level (this cycle count can be
+// used to compare the relative time of two pulses).  If more than a millisecond
+// ellapses without the level changing then the call fails with a 0 response.
+// This is adapted from Arduino's pulseInLong function (which is only available
+// in the very latest IDE versions):
+//   https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_pulse.c
+uint32_t DHT::expectPulse(bool level) {
+  uint32_t count = 0;
+  // On AVR platforms use direct GPIO port access as it's much faster and better
+  // for catching pulses that are 10's of microseconds in length:
+  #ifdef __AVR
+    uint8_t portState = level ? _bit : 0;
+    while ((*portInputRegister(_port) & _bit) == portState) {
+      if (count++ >= _maxcycles) {
+        return 0; // Exceeded timeout, fail.
+      }
+    }
+  // Otherwise fall back to using digitalRead (this seems to be necessary on ESP8266
+  // right now, perhaps bugs in direct port access functions?).
+  #else
+    while (digitalRead(_pin) == level) {
+      if (count++ >= _maxcycles) {
+        return 0; // Exceeded timeout, fail.
+      }
+    }
+  #endif
+
+  return count;
+}

+ 75 - 0
Software/src/WiFiPCController/lib/DHT/DHT.h

@@ -0,0 +1,75 @@
+/* DHT library
+
+MIT license
+written by Adafruit Industries
+*/
+#ifndef DHT_H
+#define DHT_H
+
+#if ARDUINO >= 100
+ #include "Arduino.h"
+#else
+ #include "WProgram.h"
+#endif
+
+
+// Uncomment to enable printing out nice debug messages.
+//#define DHT_DEBUG
+
+// Define where debug output will be printed.
+#define DEBUG_PRINTER Serial
+
+// Setup debug printing macros.
+#ifdef DHT_DEBUG
+  #define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); }
+  #define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); }
+#else
+  #define DEBUG_PRINT(...) {}
+  #define DEBUG_PRINTLN(...) {}
+#endif
+
+// Define types of sensors.
+#define DHT11 11
+#define DHT22 22
+#define DHT21 21
+#define AM2301 21
+
+
+class DHT {
+  public:
+   DHT(uint8_t pin, uint8_t type, uint8_t count=6);
+   void begin(void);
+   float readTemperature(bool S=false, bool force=false);
+   float convertCtoF(float);
+   float convertFtoC(float);
+   float computeHeatIndex(float temperature, float percentHumidity, bool isFahrenheit=true);
+   float readHumidity(bool force=false);
+   boolean read(bool force=false);
+
+ private:
+  uint8_t data[5];
+  uint8_t _pin, _type;
+  #ifdef __AVR
+    // Use direct GPIO access on an 8-bit AVR so keep track of the port and bitmask
+    // for the digital pin connected to the DHT.  Other platforms will use digitalRead.
+    uint8_t _bit, _port;
+  #endif
+  uint32_t _lastreadtime, _maxcycles;
+  bool _lastresult;
+
+  uint32_t expectPulse(bool level);
+
+};
+
+class InterruptLock {
+  public:
+   InterruptLock() {
+    noInterrupts();
+   }
+   ~InterruptLock() {
+    interrupts();
+   }
+
+};
+
+#endif

+ 179 - 0
Software/src/WiFiPCController/lib/DHT/DHT_U.cpp

@@ -0,0 +1,179 @@
+// DHT Temperature & Humidity Unified Sensor Library
+// Copyright (c) 2014 Adafruit Industries
+// Author: Tony DiCola
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#include "DHT_U.h"
+
+DHT_Unified::DHT_Unified(uint8_t pin, uint8_t type, uint8_t count, int32_t tempSensorId, int32_t humiditySensorId):
+  _dht(pin, type, count),
+  _type(type),
+  _temp(this, tempSensorId),
+  _humidity(this, humiditySensorId)
+{}
+
+void DHT_Unified::begin() {
+  _dht.begin();
+}
+
+void DHT_Unified::setName(sensor_t* sensor) {
+  switch(_type) {
+    case DHT11:
+      strncpy(sensor->name, "DHT11", sizeof(sensor->name) - 1);
+      break;
+    case DHT21:
+      strncpy(sensor->name, "DHT21", sizeof(sensor->name) - 1);
+      break;
+    case DHT22:
+      strncpy(sensor->name, "DHT22", sizeof(sensor->name) - 1);
+      break;
+    default:
+      // TODO: Perhaps this should be an error?  However main DHT library doesn't enforce
+      // restrictions on the sensor type value.  Pick a generic name for now.
+      strncpy(sensor->name, "DHT?", sizeof(sensor->name) - 1);
+      break;
+  }
+  sensor->name[sizeof(sensor->name)- 1] = 0;
+}
+
+void DHT_Unified::setMinDelay(sensor_t* sensor) {
+  switch(_type) {
+    case DHT11:
+      sensor->min_delay = 1000000L;  // 1 second (in microseconds)
+      break;
+    case DHT21:
+      sensor->min_delay = 2000000L;  // 2 seconds (in microseconds)
+      break;
+    case DHT22:
+      sensor->min_delay = 2000000L;  // 2 seconds (in microseconds)
+      break;
+    default:
+      // Default to slowest sample rate in case of unknown type.
+      sensor->min_delay = 2000000L;  // 2 seconds (in microseconds)
+      break;
+  }
+}
+
+DHT_Unified::Temperature::Temperature(DHT_Unified* parent, int32_t id):
+  _parent(parent),
+  _id(id)
+{}
+
+bool DHT_Unified::Temperature::getEvent(sensors_event_t* event) {
+  // Clear event definition.
+  memset(event, 0, sizeof(sensors_event_t));
+  // Populate sensor reading values.
+  event->version     = sizeof(sensors_event_t);
+  event->sensor_id   = _id;
+  event->type        = SENSOR_TYPE_AMBIENT_TEMPERATURE;
+  event->timestamp   = millis();
+  event->temperature = _parent->_dht.readTemperature();
+  
+  return true;
+}
+
+void DHT_Unified::Temperature::getSensor(sensor_t* sensor) {
+  // Clear sensor definition.
+  memset(sensor, 0, sizeof(sensor_t));
+  // Set sensor name.
+  _parent->setName(sensor);
+  // Set version and ID
+  sensor->version         = DHT_SENSOR_VERSION;
+  sensor->sensor_id       = _id;
+  // Set type and characteristics.
+  sensor->type            = SENSOR_TYPE_AMBIENT_TEMPERATURE;
+  _parent->setMinDelay(sensor);
+  switch (_parent->_type) {
+    case DHT11:
+      sensor->max_value   = 50.0F;
+      sensor->min_value   = 0.0F;
+      sensor->resolution  = 2.0F;
+      break;
+    case DHT21:
+      sensor->max_value   = 80.0F;
+      sensor->min_value   = -40.0F;
+      sensor->resolution  = 0.1F;
+      break;
+    case DHT22:
+      sensor->max_value   = 125.0F;
+      sensor->min_value   = -40.0F;
+      sensor->resolution  = 0.1F;
+      break;
+    default:
+      // Unknown type, default to 0.
+      sensor->max_value   = 0.0F;
+      sensor->min_value   = 0.0F;
+      sensor->resolution  = 0.0F;
+      break;
+  }
+}
+
+DHT_Unified::Humidity::Humidity(DHT_Unified* parent, int32_t id):
+  _parent(parent),
+  _id(id)
+{}
+
+bool DHT_Unified::Humidity::getEvent(sensors_event_t* event) {
+  // Clear event definition.
+  memset(event, 0, sizeof(sensors_event_t));
+  // Populate sensor reading values.
+  event->version           = sizeof(sensors_event_t);
+  event->sensor_id         = _id;
+  event->type              = SENSOR_TYPE_RELATIVE_HUMIDITY;
+  event->timestamp         = millis();
+  event->relative_humidity = _parent->_dht.readHumidity();
+  
+  return true;
+}
+
+void DHT_Unified::Humidity::getSensor(sensor_t* sensor) {
+  // Clear sensor definition.
+  memset(sensor, 0, sizeof(sensor_t));
+  // Set sensor name.
+  _parent->setName(sensor);
+  // Set version and ID
+  sensor->version         = DHT_SENSOR_VERSION;
+  sensor->sensor_id       = _id;
+  // Set type and characteristics.
+  sensor->type            = SENSOR_TYPE_RELATIVE_HUMIDITY;
+  _parent->setMinDelay(sensor);
+  switch (_parent->_type) {
+    case DHT11:
+      sensor->max_value   = 80.0F;
+      sensor->min_value   = 20.0F;
+      sensor->resolution  = 5.0F;
+      break;
+    case DHT21:
+      sensor->max_value   = 100.0F;
+      sensor->min_value   = 0.0F;
+      sensor->resolution  = 0.1F;
+      break;
+    case DHT22:
+      sensor->max_value   = 100.0F;
+      sensor->min_value   = 0.0F;
+      sensor->resolution  = 0.1F;
+      break;
+    default:
+      // Unknown type, default to 0.
+      sensor->max_value   = 0.0F;
+      sensor->min_value   = 0.0F;
+      sensor->resolution  = 0.0F;
+      break;
+  }
+}

+ 78 - 0
Software/src/WiFiPCController/lib/DHT/DHT_U.h

@@ -0,0 +1,78 @@
+// DHT Temperature & Humidity Unified Sensor Library
+// Copyright (c) 2014 Adafruit Industries
+// Author: Tony DiCola
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+#ifndef DHT_U_H
+#define DHT_U_H
+
+#include <Adafruit_Sensor.h>
+#include <DHT.h>
+
+#define DHT_SENSOR_VERSION 1
+
+class DHT_Unified {
+public:
+  DHT_Unified(uint8_t pin, uint8_t type, uint8_t count=6, int32_t tempSensorId=-1, int32_t humiditySensorId=-1);
+  void begin();
+
+  class Temperature : public Adafruit_Sensor {
+  public:
+    Temperature(DHT_Unified* parent, int32_t id);
+    bool getEvent(sensors_event_t* event);
+    void getSensor(sensor_t* sensor);
+
+  private:
+    DHT_Unified* _parent;
+    int32_t _id;
+
+  };
+
+  class Humidity : public Adafruit_Sensor {
+  public:
+    Humidity(DHT_Unified* parent, int32_t id);
+    bool getEvent(sensors_event_t* event);
+    void getSensor(sensor_t* sensor);
+
+  private:
+    DHT_Unified* _parent;
+    int32_t _id;
+
+  };
+
+  Temperature temperature() {
+    return _temp;
+  }
+
+  Humidity humidity() {
+    return _humidity;
+  }
+
+private:
+  DHT _dht;
+  uint8_t _type;
+  Temperature _temp;
+  Humidity _humidity;
+
+  void setName(sensor_t* sensor);
+  void setMinDelay(sensor_t* sensor);
+
+};
+
+#endif

+ 15 - 0
Software/src/WiFiPCController/lib/DHT/README.md

@@ -0,0 +1,15 @@
+This is an Arduino library for the DHT series of low cost temperature/humidity sensors.
+
+Tutorial: https://learn.adafruit.com/dht
+
+To download. click the DOWNLOADS button in the top right corner, rename the uncompressed folder DHT. Check that the DHT folder contains DHT.cpp and DHT.h. Place the DHT library folder your <arduinosketchfolder>/libraries/ folder. You may need to create the libraries subfolder if its your first library. Restart the IDE.
+
+# Adafruit DHT Humidity & Temperature Unified Sensor Library
+
+This library also includes an optional class for the
+[DHT humidity and temperature sensor](https://learn.adafruit.com/dht/overview)
+which is designed to work with the [Adafruit unified sensor library](https://learn.adafruit.com/using-the-adafruit-unified-sensor-driver/introduction).
+
+You must have the following Arduino libraries installed to use this class:
+
+- [Adafruit Unified Sensor Library](https://github.com/adafruit/Adafruit_Sensor)

+ 84 - 0
Software/src/WiFiPCController/lib/DHT/examples/DHT_Unified_Sensor/DHT_Unified_Sensor.ino

@@ -0,0 +1,84 @@
+// DHT Temperature & Humidity Sensor
+// Unified Sensor Library Example
+// Written by Tony DiCola for Adafruit Industries
+// Released under an MIT license.
+
+// Depends on the following Arduino libraries:
+// - Adafruit Unified Sensor Library: https://github.com/adafruit/Adafruit_Sensor
+// - DHT Sensor Library: https://github.com/adafruit/DHT-sensor-library
+
+#include <Adafruit_Sensor.h>
+#include <DHT.h>
+#include <DHT_U.h>
+
+#define DHTPIN            2         // Pin which is connected to the DHT sensor.
+
+// Uncomment the type of sensor in use:
+//#define DHTTYPE           DHT11     // DHT 11 
+#define DHTTYPE           DHT22     // DHT 22 (AM2302)
+//#define DHTTYPE           DHT21     // DHT 21 (AM2301)
+
+// See guide for details on sensor wiring and usage:
+//   https://learn.adafruit.com/dht/overview
+
+DHT_Unified dht(DHTPIN, DHTTYPE);
+
+uint32_t delayMS;
+
+void setup() {
+  Serial.begin(9600); 
+  // Initialize device.
+  dht.begin();
+  Serial.println("DHTxx Unified Sensor Example");
+  // Print temperature sensor details.
+  sensor_t sensor;
+  dht.temperature().getSensor(&sensor);
+  Serial.println("------------------------------------");
+  Serial.println("Temperature");
+  Serial.print  ("Sensor:       "); Serial.println(sensor.name);
+  Serial.print  ("Driver Ver:   "); Serial.println(sensor.version);
+  Serial.print  ("Unique ID:    "); Serial.println(sensor.sensor_id);
+  Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" *C");
+  Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" *C");
+  Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" *C");  
+  Serial.println("------------------------------------");
+  // Print humidity sensor details.
+  dht.humidity().getSensor(&sensor);
+  Serial.println("------------------------------------");
+  Serial.println("Humidity");
+  Serial.print  ("Sensor:       "); Serial.println(sensor.name);
+  Serial.print  ("Driver Ver:   "); Serial.println(sensor.version);
+  Serial.print  ("Unique ID:    "); Serial.println(sensor.sensor_id);
+  Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println("%");
+  Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println("%");
+  Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println("%");  
+  Serial.println("------------------------------------");
+  // Set delay between sensor readings based on sensor details.
+  delayMS = sensor.min_delay / 1000;
+}
+
+void loop() {
+  // Delay between measurements.
+  delay(delayMS);
+  // Get temperature event and print its value.
+  sensors_event_t event;  
+  dht.temperature().getEvent(&event);
+  if (isnan(event.temperature)) {
+    Serial.println("Error reading temperature!");
+  }
+  else {
+    Serial.print("Temperature: ");
+    Serial.print(event.temperature);
+    Serial.println(" *C");
+  }
+  // Get humidity event and print its value.
+  dht.humidity().getEvent(&event);
+  if (isnan(event.relative_humidity)) {
+    Serial.println("Error reading humidity!");
+  }
+  else {
+    Serial.print("Humidity: ");
+    Serial.print(event.relative_humidity);
+    Serial.println("%");
+  }
+}

+ 69 - 0
Software/src/WiFiPCController/lib/DHT/examples/DHTtester/DHTtester.ino

@@ -0,0 +1,69 @@
+// Example testing sketch for various DHT humidity/temperature sensors
+// Written by ladyada, public domain
+
+#include "DHT.h"
+
+#define DHTPIN 2     // what digital pin we're connected to
+
+// Uncomment whatever type you're using!
+//#define DHTTYPE DHT11   // DHT 11
+#define DHTTYPE DHT22   // DHT 22  (AM2302), AM2321
+//#define DHTTYPE DHT21   // DHT 21 (AM2301)
+
+// Connect pin 1 (on the left) of the sensor to +5V
+// NOTE: If using a board with 3.3V logic like an Arduino Due connect pin 1
+// to 3.3V instead of 5V!
+// Connect pin 2 of the sensor to whatever your DHTPIN is
+// Connect pin 4 (on the right) of the sensor to GROUND
+// Connect a 10K resistor from pin 2 (data) to pin 1 (power) of the sensor
+
+// Initialize DHT sensor.
+// Note that older versions of this library took an optional third parameter to
+// tweak the timings for faster processors.  This parameter is no longer needed
+// as the current DHT reading algorithm adjusts itself to work on faster procs.
+DHT dht(DHTPIN, DHTTYPE);
+
+void setup() {
+  Serial.begin(9600);
+  Serial.println("DHTxx test!");
+
+  dht.begin();
+}
+
+void loop() {
+  // Wait a few seconds between measurements.
+  delay(2000);
+
+  // Reading temperature or humidity takes about 250 milliseconds!
+  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
+  float h = dht.readHumidity();
+  // Read temperature as Celsius (the default)
+  float t = dht.readTemperature();
+  // Read temperature as Fahrenheit (isFahrenheit = true)
+  float f = dht.readTemperature(true);
+
+  // Check if any reads failed and exit early (to try again).
+  if (isnan(h) || isnan(t) || isnan(f)) {
+    Serial.println("Failed to read from DHT sensor!");
+    return;
+  }
+
+  // Compute heat index in Fahrenheit (the default)
+  float hif = dht.computeHeatIndex(f, h);
+  // Compute heat index in Celsius (isFahreheit = false)
+  float hic = dht.computeHeatIndex(t, h, false);
+
+  Serial.print("Humidity: ");
+  Serial.print(h);
+  Serial.print(" %\t");
+  Serial.print("Temperature: ");
+  Serial.print(t);
+  Serial.print(" *C ");
+  Serial.print(f);
+  Serial.print(" *F\t");
+  Serial.print("Heat index: ");
+  Serial.print(hic);
+  Serial.print(" *C ");
+  Serial.print(hif);
+  Serial.println(" *F");
+}

+ 22 - 0
Software/src/WiFiPCController/lib/DHT/keywords.txt

@@ -0,0 +1,22 @@
+###########################################
+# Syntax Coloring Map For DHT-sensor-library
+###########################################
+
+###########################################
+# Datatypes (KEYWORD1)
+###########################################
+
+DHT	KEYWORD1
+
+###########################################
+# Methods and Functions (KEYWORD2)
+###########################################
+
+begin KEYWORD2
+readTemperature KEYWORD2
+convertCtoF KEYWORD2
+convertFtoC KEYWORD2
+computeHeatIndex KEYWORD2
+readHumidity KEYWORD2
+read KEYWORD2
+

+ 9 - 0
Software/src/WiFiPCController/lib/DHT/library.properties

@@ -0,0 +1,9 @@
+name=DHT sensor library
+version=1.3.0
+author=Adafruit
+maintainer=Adafruit <info@adafruit.com>
+sentence=Arduino library for DHT11, DHT22, etc Temp & Humidity Sensors
+paragraph=Arduino library for DHT11, DHT22, etc Temp & Humidity Sensors
+category=Sensors
+url=https://github.com/adafruit/DHT-sensor-library
+architectures=*

BIN
Software/src/WiFiPCController/lib/ESPmanager-master.zip


+ 322 - 0
Software/src/WiFiPCController/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.cpp

@@ -0,0 +1,322 @@
+//www.DFRobot.com
+//last updated on 21/12/2011
+//Tim Starling Fix the reset bug (Thanks Tim)
+//wiki doc http://www.dfrobot.com/wiki/index.php?title=I2C/TWI_LCD1602_Module_(SKU:_DFR0063)
+//Support Forum: http://www.dfrobot.com/forum/
+//Compatible with the Arduino IDE 1.0
+//Library version:1.1
+
+
+#include "LiquidCrystal_I2C.h"
+#include <inttypes.h>
+#if defined(ARDUINO) && ARDUINO >= 100
+
+#include "Arduino.h"
+
+#define printIIC(args)	Wire.write(args)
+inline size_t LiquidCrystal_I2C::write(uint8_t value) {
+	send(value, Rs);
+	return 0;
+}
+
+#else
+#include "WProgram.h"
+
+#define printIIC(args)	Wire.send(args)
+inline void LiquidCrystal_I2C::write(uint8_t value) {
+	send(value, Rs);
+}
+
+#endif
+#include "Wire.h"
+
+
+
+// When the display powers up, it is configured as follows:
+//
+// 1. Display clear
+// 2. Function set: 
+//    DL = 1; 8-bit interface data 
+//    N = 0; 1-line display 
+//    F = 0; 5x8 dot character font 
+// 3. Display on/off control: 
+//    D = 0; Display off 
+//    C = 0; Cursor off 
+//    B = 0; Blinking off 
+// 4. Entry mode set: 
+//    I/D = 1; Increment by 1
+//    S = 0; No shift 
+//
+// Note, however, that resetting the Arduino doesn't reset the LCD, so we
+// can't assume that its in that state when a sketch starts (and the
+// LiquidCrystal constructor is called).
+
+LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows)
+{
+  _Addr = lcd_Addr;
+  _cols = lcd_cols;
+  _rows = lcd_rows;
+  _backlightval = LCD_NOBACKLIGHT;
+}
+
+void LiquidCrystal_I2C::init(){
+	init_priv();
+}
+
+void LiquidCrystal_I2C::init_priv()
+{
+	Wire.begin();
+	_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
+	begin(_cols, _rows);  
+}
+
+void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
+	if (lines > 1) {
+		_displayfunction |= LCD_2LINE;
+	}
+	_numlines = lines;
+
+	// for some 1 line displays you can select a 10 pixel high font
+	if ((dotsize != 0) && (lines == 1)) {
+		_displayfunction |= LCD_5x10DOTS;
+	}
+
+	// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
+	// according to datasheet, we need at least 40ms after power rises above 2.7V
+	// before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
+	delay(50); 
+  
+	// Now we pull both RS and R/W low to begin commands
+	expanderWrite(_backlightval);	// reset expanderand turn backlight off (Bit 8 =1)
+	delay(1000);
+
+  	//put the LCD into 4 bit mode
+	// this is according to the hitachi HD44780 datasheet
+	// figure 24, pg 46
+	
+	  // we start in 8bit mode, try to set 4 bit mode
+   write4bits(0x03 << 4);
+   delayMicroseconds(4500); // wait min 4.1ms
+   
+   // second try
+   write4bits(0x03 << 4);
+   delayMicroseconds(4500); // wait min 4.1ms
+   
+   // third go!
+   write4bits(0x03 << 4); 
+   delayMicroseconds(150);
+   
+   // finally, set to 4-bit interface
+   write4bits(0x02 << 4); 
+
+
+	// set # lines, font size, etc.
+	command(LCD_FUNCTIONSET | _displayfunction);  
+	
+	// turn the display on with no cursor or blinking default
+	_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
+	display();
+	
+	// clear it off
+	clear();
+	
+	// Initialize to default text direction (for roman languages)
+	_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
+	
+	// set the entry mode
+	command(LCD_ENTRYMODESET | _displaymode);
+	
+	home();
+  
+}
+
+/********** high level commands, for the user! */
+void LiquidCrystal_I2C::clear(){
+	command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero
+	delayMicroseconds(2000);  // this command takes a long time!
+}
+
+void LiquidCrystal_I2C::home(){
+	command(LCD_RETURNHOME);  // set cursor position to zero
+	delayMicroseconds(2000);  // this command takes a long time!
+}
+
+void LiquidCrystal_I2C::setCursor(uint8_t col, uint8_t row){
+	int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
+	if ( row > _numlines ) {
+		row = _numlines-1;    // we count rows starting w/0
+	}
+	command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
+}
+
+// Turn the display on/off (quickly)
+void LiquidCrystal_I2C::noDisplay() {
+	_displaycontrol &= ~LCD_DISPLAYON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+void LiquidCrystal_I2C::display() {
+	_displaycontrol |= LCD_DISPLAYON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+
+// Turns the underline cursor on/off
+void LiquidCrystal_I2C::noCursor() {
+	_displaycontrol &= ~LCD_CURSORON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+void LiquidCrystal_I2C::cursor() {
+	_displaycontrol |= LCD_CURSORON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+
+// Turn on and off the blinking cursor
+void LiquidCrystal_I2C::noBlink() {
+	_displaycontrol &= ~LCD_BLINKON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+void LiquidCrystal_I2C::blink() {
+	_displaycontrol |= LCD_BLINKON;
+	command(LCD_DISPLAYCONTROL | _displaycontrol);
+}
+
+// These commands scroll the display without changing the RAM
+void LiquidCrystal_I2C::scrollDisplayLeft(void) {
+	command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
+}
+void LiquidCrystal_I2C::scrollDisplayRight(void) {
+	command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
+}
+
+// This is for text that flows Left to Right
+void LiquidCrystal_I2C::leftToRight(void) {
+	_displaymode |= LCD_ENTRYLEFT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// This is for text that flows Right to Left
+void LiquidCrystal_I2C::rightToLeft(void) {
+	_displaymode &= ~LCD_ENTRYLEFT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// This will 'right justify' text from the cursor
+void LiquidCrystal_I2C::autoscroll(void) {
+	_displaymode |= LCD_ENTRYSHIFTINCREMENT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// This will 'left justify' text from the cursor
+void LiquidCrystal_I2C::noAutoscroll(void) {
+	_displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
+	command(LCD_ENTRYMODESET | _displaymode);
+}
+
+// Allows us to fill the first 8 CGRAM locations
+// with custom characters
+void LiquidCrystal_I2C::createChar(uint8_t location, uint8_t charmap[]) {
+	location &= 0x7; // we only have 8 locations 0-7
+	command(LCD_SETCGRAMADDR | (location << 3));
+	for (int i=0; i<8; i++) {
+		write(charmap[i]);
+	}
+}
+
+// Turn the (optional) backlight off/on
+void LiquidCrystal_I2C::noBacklight(void) {
+	_backlightval=LCD_NOBACKLIGHT;
+	expanderWrite(0);
+}
+
+void LiquidCrystal_I2C::backlight(void) {
+	_backlightval=LCD_BACKLIGHT;
+	expanderWrite(0);
+}
+
+
+
+/*********** mid level commands, for sending data/cmds */
+
+inline void LiquidCrystal_I2C::command(uint8_t value) {
+	send(value, 0);
+}
+
+
+/************ low level data pushing commands **********/
+
+// write either command or data
+void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
+	uint8_t highnib=value&0xf0;
+	uint8_t lownib=(value<<4)&0xf0;
+       write4bits((highnib)|mode);
+	write4bits((lownib)|mode); 
+}
+
+void LiquidCrystal_I2C::write4bits(uint8_t value) {
+	expanderWrite(value);
+	pulseEnable(value);
+}
+
+void LiquidCrystal_I2C::expanderWrite(uint8_t _data){                                        
+	Wire.beginTransmission(_Addr);
+	printIIC((int)(_data) | _backlightval);
+	Wire.endTransmission();   
+}
+
+void LiquidCrystal_I2C::pulseEnable(uint8_t _data){
+	expanderWrite(_data | En);	// En high
+	delayMicroseconds(1);		// enable pulse must be >450ns
+	
+	expanderWrite(_data & ~En);	// En low
+	delayMicroseconds(50);		// commands need > 37us to settle
+} 
+
+
+// Alias functions
+
+void LiquidCrystal_I2C::cursor_on(){
+	cursor();
+}
+
+void LiquidCrystal_I2C::cursor_off(){
+	noCursor();
+}
+
+void LiquidCrystal_I2C::blink_on(){
+	blink();
+}
+
+void LiquidCrystal_I2C::blink_off(){
+	noBlink();
+}
+
+void LiquidCrystal_I2C::load_custom_character(uint8_t char_num, uint8_t *rows){
+		createChar(char_num, rows);
+}
+
+void LiquidCrystal_I2C::setBacklight(uint8_t new_val){
+	if(new_val){
+		backlight();		// turn backlight on
+	}else{
+		noBacklight();		// turn backlight off
+	}
+}
+
+void LiquidCrystal_I2C::printstr(const char c[]){
+	//This function is not identical to the function used for "real" I2C displays
+	//it's here so the user sketch doesn't have to be changed 
+	print(c);
+}
+
+
+// unsupported API functions
+void LiquidCrystal_I2C::off(){}
+void LiquidCrystal_I2C::on(){}
+void LiquidCrystal_I2C::setDelay (int cmdDelay,int charDelay) {}
+uint8_t LiquidCrystal_I2C::status(){return 0;}
+uint8_t LiquidCrystal_I2C::keypad (){return 0;}
+uint8_t LiquidCrystal_I2C::init_bargraph(uint8_t graphtype){return 0;}
+void LiquidCrystal_I2C::draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end){}
+void LiquidCrystal_I2C::draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_row_end){}
+void LiquidCrystal_I2C::setContrast(uint8_t new_val){}
+
+	

+ 126 - 0
Software/src/WiFiPCController/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h

@@ -0,0 +1,126 @@
+//DFRobot.com
+#ifndef LiquidCrystal_I2C_h
+#define LiquidCrystal_I2C_h
+
+#include <inttypes.h>
+#include "Print.h" 
+#include <Wire.h>
+
+// commands
+#define LCD_CLEARDISPLAY 0x01
+#define LCD_RETURNHOME 0x02
+#define LCD_ENTRYMODESET 0x04
+#define LCD_DISPLAYCONTROL 0x08
+#define LCD_CURSORSHIFT 0x10
+#define LCD_FUNCTIONSET 0x20
+#define LCD_SETCGRAMADDR 0x40
+#define LCD_SETDDRAMADDR 0x80
+
+// flags for display entry mode
+#define LCD_ENTRYRIGHT 0x00
+#define LCD_ENTRYLEFT 0x02
+#define LCD_ENTRYSHIFTINCREMENT 0x01
+#define LCD_ENTRYSHIFTDECREMENT 0x00
+
+// flags for display on/off control
+#define LCD_DISPLAYON 0x04
+#define LCD_DISPLAYOFF 0x00
+#define LCD_CURSORON 0x02
+#define LCD_CURSOROFF 0x00
+#define LCD_BLINKON 0x01
+#define LCD_BLINKOFF 0x00
+
+// flags for display/cursor shift
+#define LCD_DISPLAYMOVE 0x08
+#define LCD_CURSORMOVE 0x00
+#define LCD_MOVERIGHT 0x04
+#define LCD_MOVELEFT 0x00
+
+// flags for function set
+#define LCD_8BITMODE 0x10
+#define LCD_4BITMODE 0x00
+#define LCD_2LINE 0x08
+#define LCD_1LINE 0x00
+#define LCD_5x10DOTS 0x04
+#define LCD_5x8DOTS 0x00
+
+// flags for backlight control
+#define LCD_BACKLIGHT 0x08
+#define LCD_NOBACKLIGHT 0x00
+
+#define En B00000100  // Enable bit
+#define Rw B00000010  // Read/Write bit
+#define Rs B00000001  // Register select bit
+
+class LiquidCrystal_I2C : public Print {
+public:
+  LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
+  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
+  void clear();
+  void home();
+  void noDisplay();
+  void display();
+  void noBlink();
+  void blink();
+  void noCursor();
+  void cursor();
+  void scrollDisplayLeft();
+  void scrollDisplayRight();
+  void printLeft();
+  void printRight();
+  void leftToRight();
+  void rightToLeft();
+  void shiftIncrement();
+  void shiftDecrement();
+  void noBacklight();
+  void backlight();
+  void autoscroll();
+  void noAutoscroll(); 
+  void createChar(uint8_t, uint8_t[]);
+  void setCursor(uint8_t, uint8_t); 
+#if defined(ARDUINO) && ARDUINO >= 100
+  virtual size_t write(uint8_t);
+#else
+  virtual void write(uint8_t);
+#endif
+  void command(uint8_t);
+  void init();
+
+////compatibility API function aliases
+void blink_on();						// alias for blink()
+void blink_off();       					// alias for noBlink()
+void cursor_on();      	 					// alias for cursor()
+void cursor_off();      					// alias for noCursor()
+void setBacklight(uint8_t new_val);				// alias for backlight() and nobacklight()
+void load_custom_character(uint8_t char_num, uint8_t *rows);	// alias for createChar()
+void printstr(const char[]);
+
+////Unsupported API functions (not implemented in this library)
+uint8_t status();
+void setContrast(uint8_t new_val);
+uint8_t keypad();
+void setDelay(int,int);
+void on();
+void off();
+uint8_t init_bargraph(uint8_t graphtype);
+void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end);
+void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len,  uint8_t pixel_col_end);
+	 
+
+private:
+  void init_priv();
+  void send(uint8_t, uint8_t);
+  void write4bits(uint8_t);
+  void expanderWrite(uint8_t);
+  void pulseEnable(uint8_t);
+  uint8_t _Addr;
+  uint8_t _displayfunction;
+  uint8_t _displaycontrol;
+  uint8_t _displaymode;
+  uint8_t _numlines;
+  uint8_t _cols;
+  uint8_t _rows;
+  uint8_t _backlightval;
+};
+
+#endif

+ 69 - 0
Software/src/WiFiPCController/lib/LiquidCrystal_I2C/diff.txt

@@ -0,0 +1,69 @@
+1,6c1
+< //www.DFRobot.com
+< //last updated on 26/11/2010
+< //Tim Starling Fix the reset bug (Thanks Tim)
+< //wiki doc http://www.dfrobot.com/wiki/index.php?title=I2C/TWI_LCD1602_Module_(SKU:_DFR0063)
+< //Support Forum: http://www.dfrobot.com/forum/
+< 
+---
+> // LiquidCrystal_I2C V2.0
+10d4
+< #include "WProgram.h" 
+12c6
+< 
+---
+> #include "Arduino.h"
+67c61
+< 	delay(50); 
+---
+> 	delayMicroseconds(50000); 
+77,90c71,84
+< 	  // we start in 8bit mode, try to set 4 bit mode
+<    write4bits(0x03 << 4);
+<    delayMicroseconds(4500); // wait min 4.1ms
+<    
+<    // second try
+<    write4bits(0x03 << 4);
+<    delayMicroseconds(4500); // wait min 4.1ms
+<    
+<    // third go!
+<    write4bits(0x03 << 4); 
+<    delayMicroseconds(150);
+<    
+<    // finally, set to 4-bit interface
+<    write4bits(0x02 << 4); 
+---
+> 	// we start in 8bit mode, try to set 4 bit mode
+> 	write4bits(0x03);
+> 	delayMicroseconds(4500); // wait min 4.1ms
+> 	
+> 	// second try
+> 	write4bits(0x03);
+> 	delayMicroseconds(4500); // wait min 4.1ms
+> 	
+> 	// third go!
+> 	write4bits(0x03); 
+> 	delayMicroseconds(150);
+> 	
+> 	// finally, set to 4-bit interface
+> 	write4bits(0x02); 
+225c219
+< inline void LiquidCrystal_I2C::write(uint8_t value) {
+---
+> inline size_t LiquidCrystal_I2C::write(uint8_t value) {
+226a221
+> 	return 0;
+235,238c230,233
+< 	uint8_t highnib=value&0xf0;
+< 	uint8_t lownib=(value<<4)&0xf0;
+<        write4bits((highnib)|mode);
+< 	write4bits((lownib)|mode); 
+---
+> 	uint8_t highnib=value>>4;
+> 	uint8_t lownib=value & 0x0F;
+> 	write4bits((highnib)|mode);
+> 	write4bits((lownib)|mode);
+248c243
+< 	Wire.send((int)(_data) | _backlightval);
+---
+> 	Wire.write((int)(_data) | _backlightval);

+ 70 - 0
Software/src/WiFiPCController/lib/LiquidCrystal_I2C/examples/CustomChars/CustomChars.pde

@@ -0,0 +1,70 @@
+//DFRobot.com
+//Compatible with the Arduino IDE 1.0
+//Library version:1.1
+#include <Wire.h>
+#include <LiquidCrystal_I2C.h>
+
+#if defined(ARDUINO) && ARDUINO >= 100
+#define printByte(args)  write(args);
+#else
+#define printByte(args)  print(args,BYTE);
+#endif
+
+uint8_t bell[8]  = {0x4,0xe,0xe,0xe,0x1f,0x0,0x4};
+uint8_t note[8]  = {0x2,0x3,0x2,0xe,0x1e,0xc,0x0};
+uint8_t clock[8] = {0x0,0xe,0x15,0x17,0x11,0xe,0x0};
+uint8_t heart[8] = {0x0,0xa,0x1f,0x1f,0xe,0x4,0x0};
+uint8_t duck[8]  = {0x0,0xc,0x1d,0xf,0xf,0x6,0x0};
+uint8_t check[8] = {0x0,0x1,0x3,0x16,0x1c,0x8,0x0};
+uint8_t cross[8] = {0x0,0x1b,0xe,0x4,0xe,0x1b,0x0};
+uint8_t retarrow[8] = {	0x1,0x1,0x5,0x9,0x1f,0x8,0x4};
+  
+LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
+
+void setup()
+{
+  lcd.init();                      // initialize the lcd 
+  lcd.backlight();
+  
+  lcd.createChar(0, bell);
+  lcd.createChar(1, note);
+  lcd.createChar(2, clock);
+  lcd.createChar(3, heart);
+  lcd.createChar(4, duck);
+  lcd.createChar(5, check);
+  lcd.createChar(6, cross);
+  lcd.createChar(7, retarrow);
+  lcd.home();
+  
+  lcd.print("Hello world...");
+  lcd.setCursor(0, 1);
+  lcd.print(" i ");
+  lcd.printByte(3);
+  lcd.print(" arduinos!");
+  delay(5000);
+  displayKeyCodes();
+  
+}
+
+// display all keycodes
+void displayKeyCodes(void) {
+  uint8_t i = 0;
+  while (1) {
+    lcd.clear();
+    lcd.print("Codes 0x"); lcd.print(i, HEX);
+    lcd.print("-0x"); lcd.print(i+16, HEX);
+    lcd.setCursor(0, 1);
+    for (int j=0; j<16; j++) {
+      lcd.printByte(i+j);
+    }
+    i+=16;
+    
+    delay(4000);
+  }
+}
+
+void loop()
+{
+
+}
+

+ 20 - 0
Software/src/WiFiPCController/lib/LiquidCrystal_I2C/examples/HelloWorld/HelloWorld.pde

@@ -0,0 +1,20 @@
+//DFRobot.com
+//Compatible with the Arduino IDE 1.0
+//Library version:1.1
+#include <Wire.h> 
+#include <LiquidCrystal_I2C.h>
+
+LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
+
+void setup()
+{
+  lcd.init();                      // initialize the lcd 
+ 
+  // Print a message to the LCD.
+  lcd.backlight();
+  lcd.print("Hello, world!");
+}
+
+void loop()
+{
+}

+ 34 - 0
Software/src/WiFiPCController/lib/LiquidCrystal_I2C/examples/SerialDisplay/SerialDisplay.pde

@@ -0,0 +1,34 @@
+/*
+ * Displays text sent over the serial port (e.g. from the Serial Monitor) on
+ * an attached LCD.
+ * DFRobot.com
+ *Compatible with the Arduino IDE 1.0
+ *Library version:1.1
+ */
+#include <Wire.h> 
+#include <LiquidCrystal_I2C.h>
+
+LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
+
+void setup()
+{
+  lcd.init();                      // initialize the lcd 
+  lcd.backlight();
+  Serial.begin(9600);
+}
+
+void loop()
+{
+  // when characters arrive over the serial port...
+  if (Serial.available()) {
+    // wait a bit for the entire message to arrive
+    delay(100);
+    // clear the screen
+    lcd.clear();
+    // read all the available characters
+    while (Serial.available() > 0) {
+      // display each character to the LCD
+      lcd.write(Serial.read());
+    }
+  }
+}

+ 46 - 0
Software/src/WiFiPCController/lib/LiquidCrystal_I2C/keywords.txt

@@ -0,0 +1,46 @@
+###########################################
+# Syntax Coloring Map For LiquidCrystal_I2C
+###########################################
+
+###########################################
+# Datatypes (KEYWORD1)
+###########################################
+
+LiquidCrystal_I2C	KEYWORD1
+
+###########################################
+# Methods and Functions (KEYWORD2)
+###########################################
+init	KEYWORD2
+begin	KEYWORD2
+clear	KEYWORD2
+home	KEYWORD2
+noDisplay	KEYWORD2
+display	KEYWORD2
+noBlink	KEYWORD2
+blink	KEYWORD2
+noCursor	KEYWORD2
+cursor	KEYWORD2
+scrollDisplayLeft	KEYWORD2
+scrollDisplayRight	KEYWORD2
+leftToRight	KEYWORD2
+rightToLeft	KEYWORD2
+shiftIncrement	KEYWORD2
+shiftDecrement	KEYWORD2
+noBacklight	KEYWORD2
+backlight	KEYWORD2
+autoscroll	KEYWORD2
+noAutoscroll	KEYWORD2
+createChar	KEYWORD2
+setCursor	KEYWORD2
+print	KEYWORD2
+blink_on	KEYWORD2
+blink_off	KEYWORD2
+cursor_on	KEYWORD2
+cursor_off	KEYWORD2
+setBacklight	KEYWORD2
+load_custom_character	KEYWORD2
+printstr	KEYWORD2
+###########################################
+# Constants (LITERAL1)
+###########################################

BIN
Software/src/WiFiPCController/lib/esp8266-dht22-master.zip


+ 21 - 0
Software/src/WiFiPCController/lib/esp8266-dht22-master/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 iotpipe
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 74 - 0
Software/src/WiFiPCController/lib/esp8266-dht22-master/README.md

@@ -0,0 +1,74 @@
+# esp8266-dht22
+A DHT library for the ESP8266
+
+This library builds on top of the ESP8266 SDK to support the DHTXX family of humidity and temperature sensors.  
+Supported models are:
+-DHT11
+-DHT21 (AM2301) 
+-DHT22 (AM2302)
+
+It is a port of the DHT Sensor library for the arduino created by @adafruit , which you can find [here](https://github.com/adafruit/DHT-sensor-library).
+
+Additional information about the DTHXX can be found in this [spec sheet](https://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf).
+
+## Usage
+The library is comprised of a single header and source code file.  Simply include dht.h at the top of your program to get started.
+
+There are only 4 functions you'll need to read temperature and humidity data.
+```C
+/*Initializes the DHT.  User must specify which GPIO on the ESP8266 is connected to the DHT and which member of the DHTXX family is being used.  Options are DHT11, DHT21, AM2301, DHT22, AM2302.  This must be called before reading the DHT*/
+void DHT_init(uint8_t pin, uint8_t type);
+
+/*Prepare ESP8266 for reading the DHT.  This must be called before reading the DHT.*/
+void DHT_begin(void);
+
+/*Returns a floating point value of the temperature, or -1 if failed.  User can specify if they want value in Celsius or Fahrenheight*/
+float readTemperature(bool isFahrenheit);
+
+/*Returns a floating point value of the humidity, or -1 if failed.*/
+float readHumidity();
+```
+
+A a quick example, here is a sample user_main.c file which prints out the humidity and temperature every 3 seconds.
+```C
+#include "dht22.h"
+#define baud_rate 115200
+
+volatile os_timer_t read_humidity_timer;
+void read_humidity_timerfunc(void);
+
+void user_init(void)
+{
+        uart_div_modify(0, UART_CLK_FREQ / baud_rate); 
+        os_delay_us(10);
+
+        //Setup a timer to read DHT every 3 seconds, on repeat.
+        os_timer_setfn(&read_humidity_timer, (os_timer_func_t *)read_humidity_timerfunc, NULL);
+        os_timer_arm(&read_humidity_timer, 3000, 1);
+
+        //Declare we are using a DHT22 on Pin 14
+        DHT_init(14,DHT22);
+        DHT_begin();
+}
+
+
+void read_humidity_timerfunc(void)
+{
+        float t = readTemperature(false);
+        float h = readHumidity();
+        
+        os_printf("Temperature (C): %d\r\n", (int)t);
+        os_printf("Humidity (C): %d\r\n", (int)h);
+}
+```
+
+##What's been tested
+-DHT22 with an Adafruit Huzzah
+
+##Known issues
+The 1 wire communication bus relays on very exact timing.  During the signal reading process any interrupts will corrupt the result.  In Adafruit's Arduino implementation they disable all interrupts during the reading process.  The ESP8266 cannot disable all interrupts so if an interrupt does occur expect a bad result.  Luckily, bad results are returned as -1, so you have a way to verify.
+
+On the Adafruit Huzzah using certain pins will result in failure to read the signal if you don't power cycle your ESP8266 after flashing and after hitting the reset button.  So far, this has happened on Pins #2 and #4.  Pin #14, however, does not have this issue.  When in doubt, power cycle!
+
+##Contact the author
+Reach out on twitter [@iotpipe](https://twitter.com/iot_pipe)

+ 289 - 0
Software/src/WiFiPCController/lib/esp8266-dht22-master/dht22.c

@@ -0,0 +1,289 @@
+
+
+ 
+//DHT library for the ESP8266, a port from the Adafruit library for the Arduino
+//MIT license
+//written by IoT Pipe
+
+
+#include "dht22.h"
+
+#define MIN_INTERVAL 2000
+#define NUM_VALID_INPUT_PINS 8
+const uint32_t pin_mux[NUM_VALID_INPUT_PINS] = {PERIPHS_IO_MUX_GPIO0_U,PERIPHS_IO_MUX_GPIO2_U,PERIPHS_IO_MUX_GPIO4_U,PERIPHS_IO_MUX_GPIO5_U,PERIPHS_IO_MUX_MTDI_U,PERIPHS_IO_MUX_MTCK_U,PERIPHS_IO_MUX_MTMS_U,PERIPHS_IO_MUX_MTDO_U}; 
+const uint8_t pin_num[NUM_VALID_INPUT_PINS] = {0,2,4,5,12,13,14,15};
+const uint8_t pin_func[NUM_VALID_INPUT_PINS] = {FUNC_GPIO0, FUNC_GPIO2,FUNC_GPIO4,FUNC_GPIO5,FUNC_GPIO12,FUNC_GPIO13,FUNC_GPIO14,FUNC_GPIO15};
+
+
+float ICACHE_FLASH_ATTR convertCtoF(float c) {
+	return c * 1.8 + 32;
+}
+
+float ICACHE_FLASH_ATTR convertFtoC(float f) {
+	return (f - 32) * 0.55555;
+}
+
+static int ICACHE_FLASH_ATTR get_index(int pin)
+{
+	int i = 0;
+	for(i = 0; i < NUM_VALID_INPUT_PINS; i++)
+	{
+		if(pin==pin_num[i])
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+void ICACHE_FLASH_ATTR DHT_begin(void) 
+{
+	LOG_DEBUG("DHT BEGIN");
+
+	// set up the pins!
+	int index = get_index(_pin);
+
+	gpio_init();
+	PIN_FUNC_SELECT(pin_mux[index],pin_func[index]); 
+	//GPIO_OUTPUT_SET(_pin,1);
+	GPIO_DIS_OUTPUT(_pin);
+	PIN_PULLUP_EN(pin_mux[index]);
+
+	// Using this value makes sure that millis() - lastreadtime will be >= MIN_INTERVAL right away. Note that this assignment wraps around, but so will the subtraction.
+	_lastreadtime = -MIN_INTERVAL;
+}
+
+static bool read() {
+
+	int index = get_index(_pin);
+
+	// Check if sensor was read less than two seconds ago and return early to use last reading. 
+	//This is todo.  The force argument overrides this logic.  We can implement in ESP8266 with a timer.
+	uint32 current =  system_get_time();
+	if ( current - _lastreadtime < 2000000 )
+	{	
+		LOG_DEBUG_ARGS("2 seconds is required between polls,  Its only been %d (ms)",(current-_lastreadtime) / 1000);
+		return _lastresult;
+	}
+	_lastreadtime=current;
+
+
+	// Reset 40 bits of received data to zero.
+	data[0] = data[1] = data[2] = data[3] = data[4] = 0;
+
+	// Send start signal.  See DHT datasheet for full signal diagram:
+	//   http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf
+
+	// Go into high impedence state to let pull-up raise data line level and
+	// start the reading process.
+	GPIO_OUTPUT_SET(_pin,1);
+	os_delay_us(250*1000);
+
+
+	// First set data line low for 10 milliseconds.
+	GPIO_OUTPUT_SET(_pin,0);
+	os_delay_us(10*1000);
+
+	uint32_t cycles[80];
+	{
+		// Turn off interrupts temporarily because the next sections are timing critical
+		// and we don't want any interruptions.
+		//TODO: We've disabled GPIO interrupts, but we haven't done anything about timers
+		ETS_GPIO_INTR_DISABLE();
+
+		// End the start signal by setting data line high for 40 microseconds.
+		GPIO_OUTPUT_SET(_pin,1);
+		os_delay_us(40);
+
+		// Now start reading the data line to get the value from the DHT sensor.
+		GPIO_DIS_OUTPUT(_pin);
+		PIN_PULLUP_EN(pin_mux[index]);
+
+		//os_delay_us(100);  // Delay a bit to let sensor pull data line low.
+
+		// First expect a low signal for ~80 microseconds followed by a high signal
+		// for ~80 microseconds again.
+		if (expectPulse(0) == 0) 
+		{
+			LOG_DEBUG("Timeout waiting for start signal low pulse.");
+			_lastresult = false;
+			return _lastresult;
+		}
+		if (expectPulse(1) == 0) 
+		{
+			LOG_DEBUG("Timeout waiting for start signal high pulse.");
+			_lastresult = false;
+			return _lastresult;
+		}
+
+		// Now read the 40 bits sent by the sensor.  Each bit is sent as a 50
+		// microsecond low pulse followed by a variable length high pulse.  If the
+		// high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
+		// then it's a 1.  We measure the cycle count of the initial 50us low pulse
+		// and use that to compare to the cycle count of the high pulse to determine
+		// if the bit is a 0 (high state cycle count < low state cycle count), or a
+		// 1 (high state cycle count > low state cycle count). Note that for speed all
+		// the pulses are read into a array and then examined in a later step.
+		int i = 0;
+		for (i=0; i<80; i+=2) 
+		{
+			cycles[i]   = expectPulse(0);
+			cycles[i+1] = expectPulse(1);
+		}
+	} // Timing critical code is now complete.
+	ETS_GPIO_INTR_ENABLE();
+
+	// Inspect pulses and determine which ones are 0 (high state cycle count < low
+	// state cycle count), or 1 (high state cycle count > low state cycle count).
+	int i = 0;
+	for (i=0; i<40; ++i) 
+	{
+		uint32_t lowCycles  = cycles[2*i];
+		uint32_t highCycles = cycles[2*i+1];
+		if ((lowCycles == 0) || (highCycles == 0)) 
+		{
+			LOG_DEBUG("Timeout waiting for pulse.");
+			_lastresult = false;
+			return _lastresult;
+		}
+		data[i/8] <<= 1;
+		// Now compare the low and high cycle times to see if the bit is a 0 or 1.
+		if (highCycles > lowCycles) 
+		{
+			// High cycles are greater than 50us low cycle count, must be a 1.
+			data[i/8] |= 1;
+		}
+		// Else high cycles are less than (or equal to, a weird case) the 50us low
+		// cycle count so this must be a zero.  Nothing needs to be changed in the
+		// stored data.
+	}
+
+	LOG_DEBUG_ARGS("Received: %d,%d,%d,%d,%d ?= %d",data[0],data[1],data[2],data[3],data[4],(data[0] + data[1] + data[2] + data[3]) & 0xFF );
+
+
+
+	// Check we read 40 bits and that the checksum matches.
+	if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) 
+	{
+		_lastresult = true;
+	
+		float f = data[0];
+		f *= 256;
+		f += data[1];
+		f *= 0.1;
+
+		f = data[2] & 0x7F;
+	      	f *= 256;
+	      	f += data[3];
+	      	f *= 0.1;
+	      	if (data[2] & 0x80) {
+        		f *= -1;
+      		}
+		return _lastresult;
+	}
+	else 
+	{
+		LOG_DEBUG("Checksum failure!");
+		_lastresult = false;
+		return _lastresult;
+	}
+}
+
+// Expect the signal line to be at the specified level for a period of time and
+// return a count of loop cycles spent at that level (this cycle count can be
+// used to compare the relative time of two pulses).  If more than a millisecond
+// ellapses without the level changing then the call fails with a 0 response.
+// This is adapted from Arduino's pulseInLong function (which is only available
+// in the very latest IDE versions):
+//   https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_pulse.c
+uint32_t expectPulse(bool level) 
+{
+	uint32_t count = 0;
+	while (GPIO_INPUT_GET(_pin) == level) 
+	{
+		if (count++ >= _maxcycles) 
+		{
+			LOG_DEBUG_ARGS("Max cycles reached: %d",count);
+			return 0; // Exceeded timeout, fail.
+      		}
+    	}
+	return count;
+}
+
+void DHT_init(uint8_t pin, uint8_t type, uint8_t count) 
+{
+	LOG_DEBUG("DHT_INIT");
+	_pin = pin;
+	_type = type;
+
+	//_maxcycles gives thenumber of cycles in a millisecond. this is used as a timeout in other functions.
+
+	//bit11~bit0 are decimal part
+	uint32_t cal = system_rtc_clock_cali_proc();
+	uint32_t int_part = ((cal*1000) >> 12) / 1000;// + ( (float)(((cal*1000)>>12)%1000 ) / 1000.0f);
+	uint32_t dec_part = ((cal * 1000) >> 12) % 1000; 
+
+	float result = int_part + (float)dec_part/1000.0f;
+
+	_maxcycles = 1000.0f / result;
+	LOG_DEBUG_ARGS("max cycles: %d",(int)_maxcycles);
+}
+
+
+float readHumidity()
+{
+	float f = -1;
+	if (read()) 
+	{
+		switch (_type) 
+		{
+			case DHT11:
+				f = data[0];
+				break;
+			case DHT22:
+			case DHT21:
+				f = data[0];
+				f *= 256;
+				f += data[1];
+				f *= 0.1;
+				break;
+		}
+	}
+	return f;
+}
+
+//boolean S == Scale.  True == Fahrenheit; False == Celcius
+float readTemperature(bool S) {
+	float f = -1;
+
+	if (read()) 
+	{
+		switch (_type) 
+		{
+			case DHT11:
+				f = data[2];
+				if(S) 
+				{
+					f = convertCtoF(f);
+				}
+			break;
+			case DHT22:
+			case DHT21:
+				f = data[2] & 0x7F;
+				f *= 256;
+				f += data[3];
+				f *= 0.1;
+				if (data[2] & 0x80) 
+				{
+					f *= -1;
+				}
+				if(S) 
+				{
+					f = convertCtoF(f);
+				}
+				break;
+		}
+	}
+	return f;
+}
+

+ 54 - 0
Software/src/WiFiPCController/lib/esp8266-dht22-master/dht22.h

@@ -0,0 +1,54 @@
+/* 
+DHT library for the ESP8266, a port from the Adafruit library for the Arduino
+MIT license
+written by IoT Pipe
+*/
+
+#ifndef DHT22_H
+#define DHT22_H
+
+#include "ets_sys.h"
+#include "osapi.h"
+#include "os_type.h"
+#include "user_config.h"
+#include "mem.h"
+#include "user_interface.h"
+
+#include "gpio.h"
+// Uncomment to enable printing out nice debug messages.
+//#define DHT_DEBUG
+
+// Setup debug printing macros.
+#ifdef DHT_DEBUG
+	#define LOG_DEBUG(message)		do {os_printf("[DHT-DEBUG] %s", message); os_printf("\r\n");} while (0)
+	#define LOG_DEBUG_ARGS(message, args...)		do {os_printf("[DHT-DEBUG] "); os_printf(message, args); os_printf("\r\n");} while (0)
+#else
+	#define LOG_DEBUG(message) do {} while(0)
+	#define LOG_DEBUG_ARGS(message, args...) do {} while(0)
+#endif
+
+// Define types of sensors.
+#define DHT11 11
+#define DHT22 22
+#define DHT21 21
+#define AM2301 21
+
+
+static uint8_t data[5];
+static uint8_t _pin, _type, _bit, _type;
+static uint32_t _lastreadtime, _maxcycles;
+static bool _lastresult;
+static uint32_t expectPulse(bool level);
+static int ICACHE_FLASH_ATTR get_index(int pin);
+static bool read();
+
+
+
+void DHT_init(uint8_t pin, uint8_t type, uint8_t count);
+void DHT_begin(void);
+float readTemperature(bool S);
+float readHumidity();
+float ICACHE_FLASH_ATTR convertCtoF(float);
+float ICACHE_FLASH_ATTR convertFtoC(float);
+
+#endif

+ 1 - 0
Software/src/WiFiPCController/lib/pubsubclient/.gitignore

@@ -0,0 +1 @@
+tests/bin

+ 7 - 0
Software/src/WiFiPCController/lib/pubsubclient/.travis.yml

@@ -0,0 +1,7 @@
+sudo: false
+language: cpp
+compiler:
+  - g++
+script: cd tests && make && make test
+os:
+  - linux

+ 68 - 0
Software/src/WiFiPCController/lib/pubsubclient/CHANGES.txt

@@ -0,0 +1,68 @@
+2.4
+   * Add MQTT_SOCKET_TIMEOUT to prevent it blocking indefinitely
+     whilst waiting for inbound data
+   * Fixed return code when publishing >256 bytes
+   
+2.3
+   * Add publish(topic,payload,retained) function
+
+2.2
+   * Change code layout to match Arduino Library reqs
+
+2.1
+   * Add MAX_TRANSFER_SIZE def to chunk messages if needed
+   * Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
+
+2.0
+   * Add (and default to) MQTT 3.1.1 support
+   * Fix PROGMEM handling for Intel Galileo/ESP8266
+   * Add overloaded constructors for convenience
+   * Add chainable setters for server/callback/client/stream
+   * Add state function to return connack return code
+
+1.9
+   * Do not split MQTT packets over multiple calls to _client->write()
+   * API change: All constructors now require an instance of Client
+      to be passed in.
+   * Fixed example to match 1.8 api changes - dpslwk
+   * Added username/password support - WilHall
+   * Added publish_P - publishes messages from PROGMEM - jobytaffey
+
+1.8
+    * KeepAlive interval is configurable in PubSubClient.h
+    * Maximum packet size is configurable in PubSubClient.h
+    * API change: Return boolean rather than int from various functions
+    * API change: Length parameter in message callback changed
+       from int to unsigned int
+    * Various internal tidy-ups around types
+1.7
+    * Improved keepalive handling
+    * Updated to the Arduino-1.0 API
+1.6
+    * Added the ability to publish a retained message
+
+1.5
+    * Added default constructor
+    * Fixed compile error when used with arduino-0021 or later
+
+1.4
+    * Fixed connection lost handling
+
+1.3
+    * Fixed packet reading bug in PubSubClient.readPacket
+
+1.2
+    * Fixed compile error when used with arduino-0016 or later
+
+
+1.1
+    * Reduced size of library
+    * Added support for Will messages
+    * Clarified licensing - see LICENSE.txt
+
+
+1.0
+    * Only Quality of Service (QOS) 0 messaging is supported
+    * The maximum message size, including header, is 128 bytes
+    * The keepalive interval is set to 30 seconds
+    * No support for Will messages

+ 20 - 0
Software/src/WiFiPCController/lib/pubsubclient/LICENSE.txt

@@ -0,0 +1,20 @@
+Copyright (c) 2008-2015 Nicholas O'Leary
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 47 - 0
Software/src/WiFiPCController/lib/pubsubclient/README.md

@@ -0,0 +1,47 @@
+# Arduino Client for MQTT
+
+This library provides a client for doing simple publish/subscribe messaging with
+a server that supports MQTT.
+
+## Examples
+
+The library comes with a number of example sketches. See File > Examples > PubSubClient
+within the Arduino application.
+
+Full API documentation is available here: http://pubsubclient.knolleary.net
+
+## Limitations
+
+ - It can only publish QoS 0 messages. It can subscribe at QoS 0 or QoS 1.
+ - The maximum message size, including header, is **128 bytes** by default. This
+   is configurable via `MQTT_MAX_PACKET_SIZE` in `PubSubClient.h`.
+ - The keepalive interval is set to 15 seconds by default. This is configurable
+   via `MQTT_KEEPALIVE` in `PubSubClient.h`.
+ - The client uses MQTT 3.1.1 by default. It can be changed to use MQTT 3.1 by
+   changing value of `MQTT_VERSION` in `PubSubClient.h`.
+
+
+## Compatible Hardware
+
+The library uses the Arduino Ethernet Client api for interacting with the
+underlying network hardware. This means it Just Works with a growing number of
+boards and shields, including:
+
+ - Arduino Ethernet
+ - Arduino Ethernet Shield
+ - Arduino YUN – use the included `YunClient` in place of `EthernetClient`, and
+   be sure to do a `Bridge.begin()` first
+ - Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
+   enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
+ - Sparkfun WiFly Shield – [library](https://github.com/dpslwk/WiFly)
+ - TI CC3000 WiFi - [library](https://github.com/sparkfun/SFE_CC3000_Library)
+ - Intel Galileo/Edison
+ - ESP8266
+
+The library cannot currently be used with hardware based on the ENC28J60 chip –
+such as the Nanode or the Nuelectronics Ethernet Shield. For those, there is an
+[alternative library](https://github.com/njh/NanodeMQTT) available.
+
+## License
+
+This code is released under the MIT License.

+ 43 - 0
Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_auth/mqtt_auth.ino

@@ -0,0 +1,43 @@
+/*
+ Basic MQTT example with Authentication
+
+  - connects to an MQTT server, providing username
+    and password
+  - publishes "hello world" to the topic "outTopic"
+  - subscribes to the topic "inTopic"
+*/
+
+#include <SPI.h>
+#include <Ethernet.h>
+#include <PubSubClient.h>
+
+// Update these with values suitable for your network.
+byte mac[]    = {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
+IPAddress ip(172, 16, 0, 100);
+IPAddress server(172, 16, 0, 2);
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  // handle message arrived
+}
+
+EthernetClient ethClient;
+PubSubClient client(server, 1883, callback, ethClient);
+
+void setup()
+{
+  Ethernet.begin(mac, ip);
+  // Note - the default maximum packet size is 128 bytes. If the
+  // combined length of clientId, username and password exceed this,
+  // you will need to increase the value of MQTT_MAX_PACKET_SIZE in
+  // PubSubClient.h
+  
+  if (client.connect("arduinoClient", "testuser", "testpass")) {
+    client.publish("outTopic","hello world");
+    client.subscribe("inTopic");
+  }
+}
+
+void loop()
+{
+  client.loop();
+}

+ 77 - 0
Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_basic/mqtt_basic.ino

@@ -0,0 +1,77 @@
+/*
+ Basic MQTT example
+
+ This sketch demonstrates the basic capabilities of the library.
+ It connects to an MQTT server then:
+  - publishes "hello world" to the topic "outTopic"
+  - subscribes to the topic "inTopic", printing out any messages
+    it receives. NB - it assumes the received payloads are strings not binary
+
+ It will reconnect to the server if the connection is lost using a blocking
+ reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
+ achieve the same result without blocking the main loop.
+ 
+*/
+
+#include <SPI.h>
+#include <Ethernet.h>
+#include <PubSubClient.h>
+
+// Update these with values suitable for your network.
+byte mac[]    = {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
+IPAddress ip(172, 16, 0, 100);
+IPAddress server(172, 16, 0, 2);
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  Serial.print("Message arrived [");
+  Serial.print(topic);
+  Serial.print("] ");
+  for (int i=0;i<length;i++) {
+    Serial.print((char)payload[i]);
+  }
+  Serial.println();
+}
+
+EthernetClient ethClient;
+PubSubClient client(ethClient);
+
+void reconnect() {
+  // Loop until we're reconnected
+  while (!client.connected()) {
+    Serial.print("Attempting MQTT connection...");
+    // Attempt to connect
+    if (client.connect("arduinoClient")) {
+      Serial.println("connected");
+      // Once connected, publish an announcement...
+      client.publish("outTopic","hello world");
+      // ... and resubscribe
+      client.subscribe("inTopic");
+    } else {
+      Serial.print("failed, rc=");
+      Serial.print(client.state());
+      Serial.println(" try again in 5 seconds");
+      // Wait 5 seconds before retrying
+      delay(5000);
+    }
+  }
+}
+
+void setup()
+{
+  Serial.begin(57600);
+
+  client.setServer(server, 1883);
+  client.setCallback(callback);
+
+  Ethernet.begin(mac, ip);
+  // Allow the hardware to sort itself out
+  delay(1500);
+}
+
+void loop()
+{
+  if (!client.connected()) {
+    reconnect();
+  }
+  client.loop();
+}

+ 126 - 0
Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_esp8266/mqtt_esp8266.ino

@@ -0,0 +1,126 @@
+/*
+ Basic ESP8266 MQTT example
+
+ This sketch demonstrates the capabilities of the pubsub library in combination
+ with the ESP8266 board/library.
+
+ It connects to an MQTT server then:
+  - publishes "hello world" to the topic "outTopic" every two seconds
+  - subscribes to the topic "inTopic", printing out any messages
+    it receives. NB - it assumes the received payloads are strings not binary
+  - If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
+    else switch it off
+
+ It will reconnect to the server if the connection is lost using a blocking
+ reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
+ achieve the same result without blocking the main loop.
+
+ To install the ESP8266 board, (using Arduino 1.6.4+):
+  - Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
+       http://arduino.esp8266.com/stable/package_esp8266com_index.json
+  - Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
+  - Select your ESP8266 in "Tools -> Board"
+
+*/
+
+#include <ESP8266WiFi.h>
+#include <PubSubClient.h>
+
+// Update these with values suitable for your network.
+
+const char* ssid = "........";
+const char* password = "........";
+const char* mqtt_server = "broker.mqtt-dashboard.com";
+
+WiFiClient espClient;
+PubSubClient client(espClient);
+long lastMsg = 0;
+char msg[50];
+int value = 0;
+
+void setup() {
+  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
+  Serial.begin(115200);
+  setup_wifi();
+  client.setServer(mqtt_server, 1883);
+  client.setCallback(callback);
+}
+
+void setup_wifi() {
+
+  delay(10);
+  // We start by connecting to a WiFi network
+  Serial.println();
+  Serial.print("Connecting to ");
+  Serial.println(ssid);
+
+  WiFi.begin(ssid, password);
+
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    Serial.print(".");
+  }
+
+  Serial.println("");
+  Serial.println("WiFi connected");
+  Serial.println("IP address: ");
+  Serial.println(WiFi.localIP());
+}
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  Serial.print("Message arrived [");
+  Serial.print(topic);
+  Serial.print("] ");
+  for (int i = 0; i < length; i++) {
+    Serial.print((char)payload[i]);
+  }
+  Serial.println();
+
+  // Switch on the LED if an 1 was received as first character
+  if ((char)payload[0] == '1') {
+    digitalWrite(BUILTIN_LED, LOW);   // Turn the LED on (Note that LOW is the voltage level
+    // but actually the LED is on; this is because
+    // it is acive low on the ESP-01)
+  } else {
+    digitalWrite(BUILTIN_LED, HIGH);  // Turn the LED off by making the voltage HIGH
+  }
+
+}
+
+void reconnect() {
+  // Loop until we're reconnected
+  while (!client.connected()) {
+    Serial.print("Attempting MQTT connection...");
+    // Attempt to connect
+    if (client.connect("ESP8266Client")) {
+      Serial.println("connected");
+      // Once connected, publish an announcement...
+      client.publish("outTopic", "hello world");
+      // ... and resubscribe
+      client.subscribe("inTopic");
+    } else {
+      Serial.print("failed, rc=");
+      Serial.print(client.state());
+      Serial.println(" try again in 5 seconds");
+      // Wait 5 seconds before retrying
+      delay(5000);
+    }
+  }
+}
+void loop() {
+
+  if (!client.connected()) {
+    reconnect();
+  }
+  client.loop();
+
+  long now = millis();
+  if (now - lastMsg > 2000) {
+    lastMsg = now;
+    ++value;
+    snprintf (msg, 75, "hello world #%ld", value);
+    Serial.print("Publish message: ");
+    Serial.println(msg);
+    client.publish("outTopic", msg);
+  }
+}

+ 60 - 0
Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_publish_in_callback/mqtt_publish_in_callback.ino

@@ -0,0 +1,60 @@
+/*
+ Publishing in the callback
+
+  - connects to an MQTT server
+  - subscribes to the topic "inTopic"
+  - when a message is received, republishes it to "outTopic"
+
+  This example shows how to publish messages within the
+  callback function. The callback function header needs to
+  be declared before the PubSubClient constructor and the
+  actual callback defined afterwards.
+  This ensures the client reference in the callback function
+  is valid.
+
+*/
+
+#include <SPI.h>
+#include <Ethernet.h>
+#include <PubSubClient.h>
+
+// Update these with values suitable for your network.
+byte mac[]    = {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
+IPAddress ip(172, 16, 0, 100);
+IPAddress server(172, 16, 0, 2);
+
+// Callback function header
+void callback(char* topic, byte* payload, unsigned int length);
+
+EthernetClient ethClient;
+PubSubClient client(server, 1883, callback, ethClient);
+
+// Callback function
+void callback(char* topic, byte* payload, unsigned int length) {
+  // In order to republish this payload, a copy must be made
+  // as the orignal payload buffer will be overwritten whilst
+  // constructing the PUBLISH packet.
+
+  // Allocate the correct amount of memory for the payload copy
+  byte* p = (byte*)malloc(length);
+  // Copy the payload to the new buffer
+  memcpy(p,payload,length);
+  client.publish("outTopic", p, length);
+  // Free the memory
+  free(p);
+}
+
+void setup()
+{
+
+  Ethernet.begin(mac, ip);
+  if (client.connect("arduinoClient")) {
+    client.publish("outTopic","hello world");
+    client.subscribe("inTopic");
+  }
+}
+
+void loop()
+{
+  client.loop();
+}

+ 67 - 0
Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_reconnect_nonblocking/mqtt_reconnect_nonblocking.ino

@@ -0,0 +1,67 @@
+/*
+ Reconnecting MQTT example - non-blocking
+
+ This sketch demonstrates how to keep the client connected
+ using a non-blocking reconnect function. If the client loses
+ its connection, it attempts to reconnect every 5 seconds
+ without blocking the main loop.
+
+*/
+
+#include <SPI.h>
+#include <Ethernet.h>
+#include <PubSubClient.h>
+
+// Update these with values suitable for your hardware/network.
+byte mac[]    = {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
+IPAddress ip(172, 16, 0, 100);
+IPAddress server(172, 16, 0, 2);
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  // handle message arrived
+}
+
+EthernetClient ethClient;
+PubSubClient client(ethClient);
+
+long lastReconnectAttempt = 0;
+
+boolean reconnect() {
+  if (client.connect("arduinoClient")) {
+    // Once connected, publish an announcement...
+    client.publish("outTopic","hello world");
+    // ... and resubscribe
+    client.subscribe("inTopic");
+  }
+  return client.connected();
+}
+
+void setup()
+{
+  client.setServer(server, 1883);
+  client.setCallback(callback);
+
+  Ethernet.begin(mac, ip);
+  delay(1500);
+  lastReconnectAttempt = 0;
+}
+
+
+void loop()
+{
+  if (!client.connected()) {
+    long now = millis();
+    if (now - lastReconnectAttempt > 5000) {
+      lastReconnectAttempt = now;
+      // Attempt to reconnect
+      if (reconnect()) {
+        lastReconnectAttempt = 0;
+      }
+    }
+  } else {
+    // Client connected
+
+    client.loop();
+  }
+
+}

+ 57 - 0
Software/src/WiFiPCController/lib/pubsubclient/examples/mqtt_stream/mqtt_stream.ino

@@ -0,0 +1,57 @@
+/*
+ Example of using a Stream object to store the message payload
+
+ Uses SRAM library: https://github.com/ennui2342/arduino-sram
+ but could use any Stream based class such as SD
+
+  - connects to an MQTT server
+  - publishes "hello world" to the topic "outTopic"
+  - subscribes to the topic "inTopic"
+*/
+
+#include <SPI.h>
+#include <Ethernet.h>
+#include <PubSubClient.h>
+#include <SRAM.h>
+
+// Update these with values suitable for your network.
+byte mac[]    = {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
+IPAddress ip(172, 16, 0, 100);
+IPAddress server(172, 16, 0, 2);
+
+SRAM sram(4, SRAM_1024);
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  sram.seek(1);
+
+  // do something with the message
+  for(uint8_t i=0; i<length; i++) {
+    Serial.write(sram.read());
+  }
+  Serial.println();
+
+  // Reset position for the next message to be stored
+  sram.seek(1);
+}
+
+EthernetClient ethClient;
+PubSubClient client(server, 1883, callback, ethClient, sram);
+
+void setup()
+{
+  Ethernet.begin(mac, ip);
+  if (client.connect("arduinoClient")) {
+    client.publish("outTopic","hello world");
+    client.subscribe("inTopic");
+  }
+
+  sram.begin();
+  sram.seek(1);
+
+  Serial.begin(9600);
+}
+
+void loop()
+{
+  client.loop();
+}

+ 30 - 0
Software/src/WiFiPCController/lib/pubsubclient/keywords.txt

@@ -0,0 +1,30 @@
+#######################################
+# Syntax Coloring Map For PubSubClient
+#######################################
+
+#######################################
+# Datatypes (KEYWORD1)
+#######################################
+
+PubSubClient	KEYWORD1
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+
+connect 	KEYWORD2
+disconnect 	KEYWORD2
+publish 	KEYWORD2
+publish_P 	KEYWORD2
+subscribe 	KEYWORD2
+unsubscribe 	KEYWORD2
+loop 	KEYWORD2
+connected 	KEYWORD2
+setServer	KEYWORD2
+setCallback	KEYWORD2
+setClient	KEYWORD2
+setStream	KEYWORD2
+
+#######################################
+# Constants (LITERAL1)
+#######################################

+ 17 - 0
Software/src/WiFiPCController/lib/pubsubclient/library.json

@@ -0,0 +1,17 @@
+{
+    "name": "PubSubClient",
+    "keywords": "ethernet, mqtt, m2m, iot",
+    "description": "A client library for MQTT messaging. MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.",
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/knolleary/pubsubclient.git"
+    },
+    "version": "2.6",
+    "exclude": "tests",
+    "examples": "examples/*/*.ino",
+    "frameworks": "arduino",
+    "platforms": [
+        "atmelavr",
+        "espressif"
+    ]
+}

+ 9 - 0
Software/src/WiFiPCController/lib/pubsubclient/library.properties

@@ -0,0 +1,9 @@
+name=PubSubClient
+version=2.6
+author=Nick O'Leary <nick.oleary@gmail.com>
+maintainer=Nick O'Leary <nick.oleary@gmail.com>
+sentence=A client library for MQTT messaging.
+paragraph=MQTT is a lightweight messaging protocol ideal for small devices. This library allows you to send and receive MQTT messages. It supports the latest MQTT 3.1.1 protocol and can be configured to use the older MQTT 3.1 if needed. It supports all Arduino Ethernet Client compatible hardware, including the Intel Galileo/Edison, ESP8266 and TI CC3000.
+category=Communication
+url=http://pubsubclient.knolleary.net
+architectures=*

+ 590 - 0
Software/src/WiFiPCController/lib/pubsubclient/src/PubSubClient.cpp

@@ -0,0 +1,590 @@
+/*
+  PubSubClient.cpp - A simple client for MQTT.
+  Nick O'Leary
+  http://knolleary.net
+*/
+
+#include "PubSubClient.h"
+#include "Arduino.h"
+
+PubSubClient::PubSubClient() {
+    this->_state = MQTT_DISCONNECTED;
+    this->_client = NULL;
+    this->stream = NULL;
+    setCallback(NULL);
+}
+
+PubSubClient::PubSubClient(Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setClient(client);
+    this->stream = NULL;
+}
+
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr, port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr, port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(addr,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip, port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip, port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(ip,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setClient(client);
+    setStream(stream);
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setCallback(callback);
+    setClient(client);
+    this->stream = NULL;
+}
+PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) {
+    this->_state = MQTT_DISCONNECTED;
+    setServer(domain,port);
+    setCallback(callback);
+    setClient(client);
+    setStream(stream);
+}
+
+boolean PubSubClient::connect(const char *id) {
+    return connect(id,NULL,NULL,0,0,0,0);
+}
+
+boolean PubSubClient::connect(const char *id, const char *user, const char *pass) {
+    return connect(id,user,pass,0,0,0,0);
+}
+
+boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
+    return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage);
+}
+
+boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) {
+    if (!connected()) {
+        int result = 0;
+
+        if (domain != NULL) {
+            result = _client->connect(this->domain, this->port);
+        } else {
+            result = _client->connect(this->ip, this->port);
+        }
+        if (result == 1) {
+            nextMsgId = 1;
+            // Leave room in the buffer for header and variable length field
+            uint16_t length = 5;
+            unsigned int j;
+
+#if MQTT_VERSION == MQTT_VERSION_3_1
+            uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION};
+#define MQTT_HEADER_VERSION_LENGTH 9
+#elif MQTT_VERSION == MQTT_VERSION_3_1_1
+            uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION};
+#define MQTT_HEADER_VERSION_LENGTH 7
+#endif
+            for (j = 0;j<MQTT_HEADER_VERSION_LENGTH;j++) {
+                buffer[length++] = d[j];
+            }
+
+            uint8_t v;
+            if (willTopic) {
+                v = 0x06|(willQos<<3)|(willRetain<<5);
+            } else {
+                v = 0x02;
+            }
+
+            if(user != NULL) {
+                v = v|0x80;
+
+                if(pass != NULL) {
+                    v = v|(0x80>>1);
+                }
+            }
+
+            buffer[length++] = v;
+
+            buffer[length++] = ((MQTT_KEEPALIVE) >> 8);
+            buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF);
+            length = writeString(id,buffer,length);
+            if (willTopic) {
+                length = writeString(willTopic,buffer,length);
+                length = writeString(willMessage,buffer,length);
+            }
+
+            if(user != NULL) {
+                length = writeString(user,buffer,length);
+                if(pass != NULL) {
+                    length = writeString(pass,buffer,length);
+                }
+            }
+
+            write(MQTTCONNECT,buffer,length-5);
+
+            lastInActivity = lastOutActivity = millis();
+
+            while (!_client->available()) {
+                unsigned long t = millis();
+                if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) {
+                    _state = MQTT_CONNECTION_TIMEOUT;
+                    _client->stop();
+                    return false;
+                }
+            }
+            uint8_t llen;
+            uint16_t len = readPacket(&llen);
+
+            if (len == 4) {
+                if (buffer[3] == 0) {
+                    lastInActivity = millis();
+                    pingOutstanding = false;
+                    _state = MQTT_CONNECTED;
+                    return true;
+                } else {
+                    _state = buffer[3];
+                }
+            }
+            _client->stop();
+        } else {
+            _state = MQTT_CONNECT_FAILED;
+        }
+        return false;
+    }
+    return true;
+}
+
+// reads a byte into result
+boolean PubSubClient::readByte(uint8_t * result) {
+   uint32_t previousMillis = millis();
+   while(!_client->available()) {
+     uint32_t currentMillis = millis();
+     if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){
+       return false;
+     }
+   }
+   *result = _client->read();
+   return true;
+}
+
+// reads a byte into result[*index] and increments index
+boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){
+  uint16_t current_index = *index;
+  uint8_t * write_address = &(result[current_index]);
+  if(readByte(write_address)){
+    *index = current_index + 1;
+    return true;
+  }
+  return false;
+}
+
+uint16_t PubSubClient::readPacket(uint8_t* lengthLength) {
+    uint16_t len = 0;
+    if(!readByte(buffer, &len)) return 0;
+    bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH;
+    uint32_t multiplier = 1;
+    uint16_t length = 0;
+    uint8_t digit = 0;
+    uint16_t skip = 0;
+    uint8_t start = 0;
+
+    do {
+        if(!readByte(&digit)) return 0;
+        buffer[len++] = digit;
+        length += (digit & 127) * multiplier;
+        multiplier *= 128;
+    } while ((digit & 128) != 0);
+    *lengthLength = len-1;
+
+    if (isPublish) {
+        // Read in topic length to calculate bytes to skip over for Stream writing
+        if(!readByte(buffer, &len)) return 0;
+        if(!readByte(buffer, &len)) return 0;
+        skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2];
+        start = 2;
+        if (buffer[0]&MQTTQOS1) {
+            // skip message id
+            skip += 2;
+        }
+    }
+
+    for (uint16_t i = start;i<length;i++) {
+        if(!readByte(&digit)) return 0;
+        if (this->stream) {
+            if (isPublish && len-*lengthLength-2>skip) {
+                this->stream->write(digit);
+            }
+        }
+        if (len < MQTT_MAX_PACKET_SIZE) {
+            buffer[len] = digit;
+        }
+        len++;
+    }
+
+    if (!this->stream && len > MQTT_MAX_PACKET_SIZE) {
+        len = 0; // This will cause the packet to be ignored.
+    }
+
+    return len;
+}
+
+boolean PubSubClient::loop() {
+    if (connected()) {
+        unsigned long t = millis();
+        if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) {
+            if (pingOutstanding) {
+                this->_state = MQTT_CONNECTION_TIMEOUT;
+                _client->stop();
+                return false;
+            } else {
+                buffer[0] = MQTTPINGREQ;
+                buffer[1] = 0;
+                _client->write(buffer,2);
+                lastOutActivity = t;
+                lastInActivity = t;
+                pingOutstanding = true;
+            }
+        }
+        if (_client->available()) {
+            uint8_t llen;
+            uint16_t len = readPacket(&llen);
+            uint16_t msgId = 0;
+            uint8_t *payload;
+            if (len > 0) {
+                lastInActivity = t;
+                uint8_t type = buffer[0]&0xF0;
+                if (type == MQTTPUBLISH) {
+                    if (callback) {
+                        uint16_t tl = (buffer[llen+1]<<8)+buffer[llen+2];
+                        char topic[tl+1];
+                        for (uint16_t i=0;i<tl;i++) {
+                            topic[i] = buffer[llen+3+i];
+                        }
+                        topic[tl] = 0;
+                        // msgId only present for QOS>0
+                        if ((buffer[0]&0x06) == MQTTQOS1) {
+                            msgId = (buffer[llen+3+tl]<<8)+buffer[llen+3+tl+1];
+                            payload = buffer+llen+3+tl+2;
+                            callback(topic,payload,len-llen-3-tl-2);
+
+                            buffer[0] = MQTTPUBACK;
+                            buffer[1] = 2;
+                            buffer[2] = (msgId >> 8);
+                            buffer[3] = (msgId & 0xFF);
+                            _client->write(buffer,4);
+                            lastOutActivity = t;
+
+                        } else {
+                            payload = buffer+llen+3+tl;
+                            callback(topic,payload,len-llen-3-tl);
+                        }
+                    }
+                } else if (type == MQTTPINGREQ) {
+                    buffer[0] = MQTTPINGRESP;
+                    buffer[1] = 0;
+                    _client->write(buffer,2);
+                } else if (type == MQTTPINGRESP) {
+                    pingOutstanding = false;
+                }
+            }
+        }
+        return true;
+    }
+    return false;
+}
+
+boolean PubSubClient::publish(const char* topic, const char* payload) {
+    return publish(topic,(const uint8_t*)payload,strlen(payload),false);
+}
+
+boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
+    return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
+}
+
+boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) {
+    return publish(topic, payload, plength, false);
+}
+
+boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
+    if (connected()) {
+        if (MQTT_MAX_PACKET_SIZE < 5 + 2+strlen(topic) + plength) {
+            // Too long
+            return false;
+        }
+        // Leave room in the buffer for header and variable length field
+        uint16_t length = 5;
+        length = writeString(topic,buffer,length);
+        uint16_t i;
+        for (i=0;i<plength;i++) {
+            buffer[length++] = payload[i];
+        }
+        uint8_t header = MQTTPUBLISH;
+        if (retained) {
+            header |= 1;
+        }
+        return write(header,buffer,length-5);
+    }
+    return false;
+}
+
+boolean PubSubClient::publish_P(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
+    uint8_t llen = 0;
+    uint8_t digit;
+    unsigned int rc = 0;
+    uint16_t tlen;
+    unsigned int pos = 0;
+    unsigned int i;
+    uint8_t header;
+    unsigned int len;
+
+    if (!connected()) {
+        return false;
+    }
+
+    tlen = strlen(topic);
+
+    header = MQTTPUBLISH;
+    if (retained) {
+        header |= 1;
+    }
+    buffer[pos++] = header;
+    len = plength + 2 + tlen;
+    do {
+        digit = len % 128;
+        len = len / 128;
+        if (len > 0) {
+            digit |= 0x80;
+        }
+        buffer[pos++] = digit;
+        llen++;
+    } while(len>0);
+
+    pos = writeString(topic,buffer,pos);
+
+    rc += _client->write(buffer,pos);
+
+    for (i=0;i<plength;i++) {
+        rc += _client->write((char)pgm_read_byte_near(payload + i));
+    }
+
+    lastOutActivity = millis();
+
+    return rc == tlen + 4 + plength;
+}
+
+boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
+    uint8_t lenBuf[4];
+    uint8_t llen = 0;
+    uint8_t digit;
+    uint8_t pos = 0;
+    uint16_t rc;
+    uint16_t len = length;
+    do {
+        digit = len % 128;
+        len = len / 128;
+        if (len > 0) {
+            digit |= 0x80;
+        }
+        lenBuf[pos++] = digit;
+        llen++;
+    } while(len>0);
+
+    buf[4-llen] = header;
+    for (int i=0;i<llen;i++) {
+        buf[5-llen+i] = lenBuf[i];
+    }
+
+#ifdef MQTT_MAX_TRANSFER_SIZE
+    uint8_t* writeBuf = buf+(4-llen);
+    uint16_t bytesRemaining = length+1+llen;  //Match the length type
+    uint8_t bytesToWrite;
+    boolean result = true;
+    while((bytesRemaining > 0) && result) {
+        bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
+        rc = _client->write(writeBuf,bytesToWrite);
+        result = (rc == bytesToWrite);
+        bytesRemaining -= rc;
+        writeBuf += rc;
+    }
+    return result;
+#else
+    rc = _client->write(buf+(4-llen),length+1+llen);
+    lastOutActivity = millis();
+    return (rc == 1+llen+length);
+#endif
+}
+
+boolean PubSubClient::subscribe(const char* topic) {
+    return subscribe(topic, 0);
+}
+
+boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
+    if (qos < 0 || qos > 1) {
+        return false;
+    }
+    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
+        // Too long
+        return false;
+    }
+    if (connected()) {
+        // Leave room in the buffer for header and variable length field
+        uint16_t length = 5;
+        nextMsgId++;
+        if (nextMsgId == 0) {
+            nextMsgId = 1;
+        }
+        buffer[length++] = (nextMsgId >> 8);
+        buffer[length++] = (nextMsgId & 0xFF);
+        length = writeString((char*)topic, buffer,length);
+        buffer[length++] = qos;
+        return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-5);
+    }
+    return false;
+}
+
+boolean PubSubClient::unsubscribe(const char* topic) {
+    if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
+        // Too long
+        return false;
+    }
+    if (connected()) {
+        uint16_t length = 5;
+        nextMsgId++;
+        if (nextMsgId == 0) {
+            nextMsgId = 1;
+        }
+        buffer[length++] = (nextMsgId >> 8);
+        buffer[length++] = (nextMsgId & 0xFF);
+        length = writeString(topic, buffer,length);
+        return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-5);
+    }
+    return false;
+}
+
+void PubSubClient::disconnect() {
+    buffer[0] = MQTTDISCONNECT;
+    buffer[1] = 0;
+    _client->write(buffer,2);
+    _state = MQTT_DISCONNECTED;
+    _client->stop();
+    lastInActivity = lastOutActivity = millis();
+}
+
+uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) {
+    const char* idp = string;
+    uint16_t i = 0;
+    pos += 2;
+    while (*idp) {
+        buf[pos++] = *idp++;
+        i++;
+    }
+    buf[pos-i-2] = (i >> 8);
+    buf[pos-i-1] = (i & 0xFF);
+    return pos;
+}
+
+
+boolean PubSubClient::connected() {
+    boolean rc;
+    if (_client == NULL ) {
+        rc = false;
+    } else {
+        rc = (int)_client->connected();
+        if (!rc) {
+            if (this->_state == MQTT_CONNECTED) {
+                this->_state = MQTT_CONNECTION_LOST;
+                _client->flush();
+                _client->stop();
+            }
+        }
+    }
+    return rc;
+}
+
+PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) {
+    IPAddress addr(ip[0],ip[1],ip[2],ip[3]);
+    return setServer(addr,port);
+}
+
+PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) {
+    this->ip = ip;
+    this->port = port;
+    this->domain = NULL;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) {
+    this->domain = domain;
+    this->port = port;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) {
+    this->callback = callback;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setClient(Client& client){
+    this->_client = &client;
+    return *this;
+}
+
+PubSubClient& PubSubClient::setStream(Stream& stream){
+    this->stream = &stream;
+    return *this;
+}
+
+int PubSubClient::state() {
+    return this->_state;
+}

+ 144 - 0
Software/src/WiFiPCController/lib/pubsubclient/src/PubSubClient.h

@@ -0,0 +1,144 @@
+/*
+ PubSubClient.h - A simple client for MQTT.
+  Nick O'Leary
+  http://knolleary.net
+*/
+
+#ifndef PubSubClient_h
+#define PubSubClient_h
+
+#include <Arduino.h>
+#include "IPAddress.h"
+#include "Client.h"
+#include "Stream.h"
+
+#define MQTT_VERSION_3_1      3
+#define MQTT_VERSION_3_1_1    4
+
+// MQTT_VERSION : Pick the version
+//#define MQTT_VERSION MQTT_VERSION_3_1
+#ifndef MQTT_VERSION
+#define MQTT_VERSION MQTT_VERSION_3_1_1
+#endif
+
+// MQTT_MAX_PACKET_SIZE : Maximum packet size
+#ifndef MQTT_MAX_PACKET_SIZE
+#define MQTT_MAX_PACKET_SIZE 512 // need to fix this here, because this define cannot be overruled within the Arduino sketch...
+#endif
+
+// MQTT_KEEPALIVE : keepAlive interval in Seconds
+#ifndef MQTT_KEEPALIVE
+#define MQTT_KEEPALIVE 15
+#endif
+
+// MQTT_SOCKET_TIMEOUT: socket timeout interval in Seconds
+#ifndef MQTT_SOCKET_TIMEOUT
+#define MQTT_SOCKET_TIMEOUT 15
+#endif
+
+// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
+//  in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
+//  pass the entire MQTT packet in each write call.
+//#define MQTT_MAX_TRANSFER_SIZE 80
+
+// Possible values for client.state()
+#define MQTT_CONNECTION_TIMEOUT     -4
+#define MQTT_CONNECTION_LOST        -3
+#define MQTT_CONNECT_FAILED         -2
+#define MQTT_DISCONNECTED           -1
+#define MQTT_CONNECTED               0
+#define MQTT_CONNECT_BAD_PROTOCOL    1
+#define MQTT_CONNECT_BAD_CLIENT_ID   2
+#define MQTT_CONNECT_UNAVAILABLE     3
+#define MQTT_CONNECT_BAD_CREDENTIALS 4
+#define MQTT_CONNECT_UNAUTHORIZED    5
+
+#define MQTTCONNECT     1 << 4  // Client request to connect to Server
+#define MQTTCONNACK     2 << 4  // Connect Acknowledgment
+#define MQTTPUBLISH     3 << 4  // Publish message
+#define MQTTPUBACK      4 << 4  // Publish Acknowledgment
+#define MQTTPUBREC      5 << 4  // Publish Received (assured delivery part 1)
+#define MQTTPUBREL      6 << 4  // Publish Release (assured delivery part 2)
+#define MQTTPUBCOMP     7 << 4  // Publish Complete (assured delivery part 3)
+#define MQTTSUBSCRIBE   8 << 4  // Client Subscribe request
+#define MQTTSUBACK      9 << 4  // Subscribe Acknowledgment
+#define MQTTUNSUBSCRIBE 10 << 4 // Client Unsubscribe request
+#define MQTTUNSUBACK    11 << 4 // Unsubscribe Acknowledgment
+#define MQTTPINGREQ     12 << 4 // PING Request
+#define MQTTPINGRESP    13 << 4 // PING Response
+#define MQTTDISCONNECT  14 << 4 // Client is Disconnecting
+#define MQTTReserved    15 << 4 // Reserved
+
+#define MQTTQOS0        (0 << 1)
+#define MQTTQOS1        (1 << 1)
+#define MQTTQOS2        (2 << 1)
+
+#ifdef ESP8266
+#include <functional>
+#define MQTT_CALLBACK_SIGNATURE std::function<void(char*, uint8_t*, unsigned int)> callback
+#else
+#define MQTT_CALLBACK_SIGNATURE void (*callback)(char*, uint8_t*, unsigned int)
+#endif
+
+class PubSubClient {
+private:
+   Client* _client;
+   uint8_t buffer[MQTT_MAX_PACKET_SIZE];
+   uint16_t nextMsgId;
+   unsigned long lastOutActivity;
+   unsigned long lastInActivity;
+   bool pingOutstanding;
+   MQTT_CALLBACK_SIGNATURE;
+   uint16_t readPacket(uint8_t*);
+   boolean readByte(uint8_t * result);
+   boolean readByte(uint8_t * result, uint16_t * index);
+   boolean write(uint8_t header, uint8_t* buf, uint16_t length);
+   uint16_t writeString(const char* string, uint8_t* buf, uint16_t pos);
+   IPAddress ip;
+   const char* domain;
+   uint16_t port;
+   Stream* stream;
+   int _state;
+public:
+   PubSubClient();
+   PubSubClient(Client& client);
+   PubSubClient(IPAddress, uint16_t, Client& client);
+   PubSubClient(IPAddress, uint16_t, Client& client, Stream&);
+   PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
+   PubSubClient(IPAddress, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
+   PubSubClient(uint8_t *, uint16_t, Client& client);
+   PubSubClient(uint8_t *, uint16_t, Client& client, Stream&);
+   PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
+   PubSubClient(uint8_t *, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
+   PubSubClient(const char*, uint16_t, Client& client);
+   PubSubClient(const char*, uint16_t, Client& client, Stream&);
+   PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client);
+   PubSubClient(const char*, uint16_t, MQTT_CALLBACK_SIGNATURE,Client& client, Stream&);
+
+   PubSubClient& setServer(IPAddress ip, uint16_t port);
+   PubSubClient& setServer(uint8_t * ip, uint16_t port);
+   PubSubClient& setServer(const char * domain, uint16_t port);
+   PubSubClient& setCallback(MQTT_CALLBACK_SIGNATURE);
+   PubSubClient& setClient(Client& client);
+   PubSubClient& setStream(Stream& stream);
+
+   boolean connect(const char* id);
+   boolean connect(const char* id, const char* user, const char* pass);
+   boolean connect(const char* id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
+   boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);
+   void disconnect();
+   boolean publish(const char* topic, const char* payload);
+   boolean publish(const char* topic, const char* payload, boolean retained);
+   boolean publish(const char* topic, const uint8_t * payload, unsigned int plength);
+   boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
+   boolean publish_P(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
+   boolean subscribe(const char* topic);
+   boolean subscribe(const char* topic, uint8_t qos);
+   boolean unsubscribe(const char* topic);
+   boolean loop();
+   boolean connected();
+   int state();
+};
+
+
+#endif

+ 4 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/.gitignore

@@ -0,0 +1,4 @@
+.build
+tmpbin
+logs
+*.pyc

+ 25 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/Makefile

@@ -0,0 +1,25 @@
+SRC_PATH=./src
+OUT_PATH=./bin
+TEST_SRC=$(wildcard ${SRC_PATH}/*_spec.cpp)
+TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%)
+VPATH=${SRC_PATH}
+SHIM_FILES=${SRC_PATH}/lib/*.cpp
+PSC_FILE=../src/PubSubClient.cpp
+CC=g++
+CFLAGS=-I${SRC_PATH}/lib -I../src
+
+all: $(TEST_BIN)
+
+${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES}
+	mkdir -p ${OUT_PATH}
+	${CC} ${CFLAGS} $^ -o $@
+
+clean:
+	@rm -rf ${OUT_PATH}
+
+test:
+	@bin/connect_spec
+	@bin/publish_spec
+	@bin/receive_spec
+	@bin/subscribe_spec
+	@bin/keepalive_spec

+ 93 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/README.md

@@ -0,0 +1,93 @@
+# Arduino Client for MQTT Test Suite
+
+This is a regression test suite for the `PubSubClient` library.
+
+There are two parts:
+
+ - Tests that can be compiled and run on any machine
+ - Tests that build the example sketches using the Arduino IDE
+
+
+It is a work-in-progress and is subject to complete refactoring as the whim takes
+me.
+
+
+## Local tests
+
+These are a set of executables that can be run to test specific areas of functionality.
+They do not require a real Arduino to be attached, nor the use of the Arduino IDE.
+
+The tests include a set of mock files to stub out the parts of the Arduino environment the library
+depends on.
+
+### Dependencies
+
+ - g++
+
+### Running
+
+Build the tests using the provided `Makefile`:
+
+    $ make
+
+This will create a set of executables in `./bin/`. Run each of these executables to test the corresponding functionality. 
+
+*Note:* the `connect_spec` and `keepalive_spec` tests involve testing keepalive timers so naturally take a few minutes to run through.
+
+## Arduino tests
+
+*Note:* INO Tool doesn't currently play nicely with Arduino 1.5. This has broken this test suite. 
+
+Without a suitable arduino plugged in, the test suite will only check the
+example sketches compile cleanly against the library.
+
+With an arduino plugged in, each sketch that has a corresponding python
+test case is built, uploaded and then the tests run.
+
+### Dependencies
+
+ - Python 2.7+
+ - [INO Tool](http://inotool.org/) - this provides command-line build/upload of Arduino sketches
+
+### Running
+
+The test suite _does not_ run an MQTT server - it is assumed to be running already.
+ 
+    $ python testsuite.py
+
+A summary of activity is printed to the console. More comprehensive logs are written
+to the `logs` directory.
+
+### What it does
+
+For each sketch in the library's `examples` directory, e.g. `mqtt_basic.ino`, the suite looks for a matching test case
+`testcases/mqtt_basic.py`.
+
+The test case must follow these conventions:
+ - sub-class `unittest.TestCase`
+ - provide the class methods `setUpClass` and `tearDownClass` (TODO: make this optional)
+ - all test method names begin with `test_`
+ 
+The suite will call the `setUpClass` method _before_ uploading the sketch. This
+allows any test setup to be performed before the sketch runs - such as connecting
+a client and subscribing to topics.
+
+
+### Settings
+
+The file `testcases/settings.py` is used to config the test environment.
+
+ - `server_ip` - the IP address of the broker the client should connect to (the broker port is assumed to be 1883).
+ - `arduino_ip` - the IP address the arduino should use (when not testing DHCP).
+
+Before each sketch is compiled, these values are automatically substituted in. To
+do this, the suite looks for lines that _start_ with the following:
+
+     byte server[] = {
+     byte ip[] = {
+
+and replaces them with the appropriate values.
+
+
+
+

+ 256 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/connect_spec.cpp

@@ -0,0 +1,256 @@
+#include "PubSubClient.h"
+#include "ShimClient.h"
+#include "Buffer.h"
+#include "BDDTest.h"
+#include "trace.h"
+
+
+byte server[] = { 172, 16, 0, 2 };
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  // handle message arrived
+}
+
+
+int test_connect_fails_no_network() {
+    IT("fails to connect if underlying client doesn't connect");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(false);
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_FALSE(rc);
+    int state = client.state();
+    IS_TRUE(state == MQTT_CONNECT_FAILED);
+    END_IT
+}
+
+int test_connect_fails_on_no_response() {
+    IT("fails to connect if no response received after 15 seconds");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_FALSE(rc);
+    int state = client.state();
+    IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
+    END_IT
+}
+
+int test_connect_properly_formatted() {
+    IT("sends a properly formatted connect packet and succeeds");
+    ShimClient shimClient;
+
+    shimClient.setAllowConnect(true);
+    byte expectServer[] = { 172, 16, 0, 2 };
+    shimClient.expectConnect(expectServer,1883);
+    byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+
+    shimClient.expect(connect,26);
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int state = client.state();
+    IS_TRUE(state == MQTT_DISCONNECTED);
+
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    state = client.state();
+    IS_TRUE(state == MQTT_CONNECTED);
+
+    END_IT
+}
+
+int test_connect_properly_formatted_hostname() {
+    IT("accepts a hostname");
+    ShimClient shimClient;
+
+    shimClient.setAllowConnect(true);
+    shimClient.expectConnect((char* const)"localhost",1883);
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client((char* const)"localhost", 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+
+int test_connect_fails_on_bad_rc() {
+    IT("fails to connect if a bad return code is received");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+    byte connack[] = { 0x20, 0x02, 0x00, 0x01 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_FALSE(rc);
+
+    int state = client.state();
+    IS_TRUE(state == 0x01);
+
+    END_IT
+}
+
+int test_connect_accepts_username_password() {
+    IT("accepts a username and password");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connect[] = { 0x10,0x24,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x4,0x70,0x61,0x73,0x73};
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.expect(connect,0x26);
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"pass");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_connect_accepts_username_no_password() {
+    IT("accepts a username but no password");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connect[] = { 0x10,0x1e,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72};
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.expect(connect,0x20);
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1",(char*)"user",0);
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_connect_ignores_password_no_username() {
+    IT("ignores a password but no username");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.expect(connect,26);
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1",0,(char*)"pass");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_connect_with_will() {
+    IT("accepts a will");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connect[] = {0x10,0x30,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xe,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65};
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.expect(connect,0x32);
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1",(char*)"willTopic",1,0,(char*)"willMessage");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_connect_with_will_username_password() {
+    IT("accepts a will, username and password");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connect[] = {0x10,0x40,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xce,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x8,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64};
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.expect(connect,0x42);
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1",(char*)"user",(char*)"password",(char*)"willTopic",1,0,(char*)"willMessage");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_connect_disconnect_connect() {
+    IT("connects, disconnects and connects again");
+    ShimClient shimClient;
+
+    shimClient.setAllowConnect(true);
+    byte expectServer[] = { 172, 16, 0, 2 };
+    shimClient.expectConnect(expectServer,1883);
+    byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+
+    shimClient.expect(connect,26);
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+
+    int state = client.state();
+    IS_TRUE(state == MQTT_DISCONNECTED);
+
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+
+    state = client.state();
+    IS_TRUE(state == MQTT_CONNECTED);
+
+    byte disconnect[] = {0xE0,0x00};
+    shimClient.expect(disconnect,2);
+
+    client.disconnect();
+
+    IS_FALSE(client.connected());
+    IS_FALSE(shimClient.connected());
+    IS_FALSE(shimClient.error());
+
+    state = client.state();
+    IS_TRUE(state == MQTT_DISCONNECTED);
+
+    shimClient.expect(connect,28);
+    shimClient.respond(connack,4);
+    rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+    IS_FALSE(shimClient.error());
+    state = client.state();
+    IS_TRUE(state == MQTT_CONNECTED);
+
+    END_IT
+}
+
+int main()
+{
+    SUITE("Connect");
+    test_connect_fails_no_network();
+    test_connect_fails_on_no_response();
+
+    test_connect_properly_formatted();
+    test_connect_accepts_username_password();
+    test_connect_fails_on_bad_rc();
+    test_connect_properly_formatted_hostname();
+
+    test_connect_accepts_username_no_password();
+    test_connect_ignores_password_no_username();
+    test_connect_with_will();
+    test_connect_with_will_username_password();
+    test_connect_disconnect_connect();
+    FINISH
+}

+ 185 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/keepalive_spec.cpp

@@ -0,0 +1,185 @@
+#include "PubSubClient.h"
+#include "ShimClient.h"
+#include "Buffer.h"
+#include "BDDTest.h"
+#include "trace.h"
+#include <unistd.h>
+
+byte server[] = { 172, 16, 0, 2 };
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  // handle message arrived
+}
+
+
+int test_keepalive_pings_idle() {
+    IT("keeps an idle connection alive (takes 1 minute)");
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte pingreq[] = { 0xC0,0x0 };
+    shimClient.expect(pingreq,2);
+    byte pingresp[] = { 0xD0,0x0 };
+    shimClient.respond(pingresp,2);
+
+    for (int i = 0; i < 50; i++) {
+        sleep(1);
+        if ( i == 15 || i == 31 || i == 47) {
+            shimClient.expect(pingreq,2);
+            shimClient.respond(pingresp,2);
+        }
+        rc = client.loop();
+        IS_TRUE(rc);
+    }
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_keepalive_pings_with_outbound_qos0() {
+    IT("keeps a connection alive that only sends qos0 (takes 1 minute)");
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+
+    for (int i = 0; i < 50; i++) {
+        TRACE(i<<":");
+        shimClient.expect(publish,16);
+        rc = client.publish((char*)"topic",(char*)"payload");
+        IS_TRUE(rc);
+        IS_FALSE(shimClient.error());
+        sleep(1);
+        if ( i == 15 || i == 31 || i == 47) {
+            byte pingreq[] = { 0xC0,0x0 };
+            shimClient.expect(pingreq,2);
+            byte pingresp[] = { 0xD0,0x0 };
+            shimClient.respond(pingresp,2);
+        }
+        rc = client.loop();
+        IS_TRUE(rc);
+        IS_FALSE(shimClient.error());
+    }
+
+    END_IT
+}
+
+int test_keepalive_pings_with_inbound_qos0() {
+    IT("keeps a connection alive that only receives qos0 (takes 1 minute)");
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+
+    for (int i = 0; i < 50; i++) {
+        TRACE(i<<":");
+        sleep(1);
+        if ( i == 15 || i == 31 || i == 47) {
+            byte pingreq[] = { 0xC0,0x0 };
+            shimClient.expect(pingreq,2);
+            byte pingresp[] = { 0xD0,0x0 };
+            shimClient.respond(pingresp,2);
+        }
+        shimClient.respond(publish,16);
+        rc = client.loop();
+        IS_TRUE(rc);
+        IS_FALSE(shimClient.error());
+    }
+
+    END_IT
+}
+
+int test_keepalive_no_pings_inbound_qos1() {
+    IT("does not send pings for connections with inbound qos1 (takes 1 minute)");
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+    byte puback[] = {0x40,0x2,0x12,0x34};
+
+    for (int i = 0; i < 50; i++) {
+        shimClient.respond(publish,18);
+        shimClient.expect(puback,4);
+        sleep(1);
+        rc = client.loop();
+        IS_TRUE(rc);
+        IS_FALSE(shimClient.error());
+    }
+
+    END_IT
+}
+
+int test_keepalive_disconnects_hung() {
+    IT("disconnects a hung connection (takes 30 seconds)");
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte pingreq[] = { 0xC0,0x0 };
+    shimClient.expect(pingreq,2);
+
+    for (int i = 0; i < 32; i++) {
+        sleep(1);
+        rc = client.loop();
+    }
+    IS_FALSE(rc);
+
+    int state = client.state();
+    IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int main()
+{
+    SUITE("Keep-alive");
+    test_keepalive_pings_idle();
+    test_keepalive_pings_with_outbound_qos0();
+    test_keepalive_pings_with_inbound_qos0();
+    test_keepalive_no_pings_inbound_qos1();
+    test_keepalive_disconnects_hung();
+
+    FINISH
+}

+ 23 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Arduino.h

@@ -0,0 +1,23 @@
+#ifndef Arduino_h
+#define Arduino_h
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+
+extern "C"{
+    typedef uint8_t byte ;
+    typedef uint8_t boolean ;
+
+    /* sketch */
+    extern void setup( void ) ;
+    extern void loop( void ) ;
+    uint32_t millis( void );
+}
+
+#define PROGMEM
+#define pgm_read_byte_near(x) *(x)
+
+#endif // Arduino_h

+ 50 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/BDDTest.cpp

@@ -0,0 +1,50 @@
+#include "BDDTest.h"
+#include "trace.h"
+#include <sstream>
+#include <iostream>
+#include <string>
+#include <list>
+
+int testCount = 0;
+int testPasses = 0;
+const char* testDescription;
+
+std::list<std::string> failureList;
+
+void bddtest_suite(const char* name) {
+    LOG(name << "\n");
+}
+
+int bddtest_test(const char* file, int line, const char* assertion, int result) {
+    if (!result) {
+        LOG("✗\n");
+        std::ostringstream os;
+        os << "   ! "<<testDescription<<"\n      " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]";
+        failureList.push_back(os.str());
+    }
+    return result;
+}
+
+void bddtest_start(const char* description) {
+    LOG(" - "<<description<<" ");
+    testDescription = description;
+    testCount ++;
+}
+void bddtest_end() {
+    LOG("✓\n");
+    testPasses ++;
+}
+
+int bddtest_summary() {
+    for (std::list<std::string>::iterator it = failureList.begin(); it != failureList.end(); it++) {
+        LOG("\n");
+        LOG(*it);
+        LOG("\n");
+    }
+
+    LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n");
+    if (testPasses == testCount) {
+        return 0;
+    }
+    return 1;
+}

+ 23 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/BDDTest.h

@@ -0,0 +1,23 @@
+#ifndef bddtest_h
+#define bddtest_h
+
+void bddtest_suite(const char* name);
+int bddtest_test(const char*, int, const char*, int);
+void bddtest_start(const char*);
+void bddtest_end();
+int bddtest_summary();
+
+#define SUITE(x) { bddtest_suite(x); }
+#define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false;  }
+
+#define IT(x) { bddtest_start(x); }
+#define END_IT { bddtest_end();return true;}
+
+#define FINISH { return bddtest_summary(); }
+
+#define IS_TRUE(x) TEST(x)
+#define IS_FALSE(x) TEST(!(x))
+#define IS_EQUAL(x,y) TEST(x==y)
+#define IS_NOT_EQUAL(x,y) TEST(x!=y)
+
+#endif

+ 30 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Buffer.cpp

@@ -0,0 +1,30 @@
+#include "Buffer.h"
+#include "Arduino.h"
+
+Buffer::Buffer() {
+}
+
+Buffer::Buffer(uint8_t* buf, size_t size) {
+    this->add(buf,size);
+}
+bool Buffer::available() {
+    return this->pos < this->length;
+}
+
+uint8_t Buffer::next() {
+    if (this->available()) {
+        return this->buffer[this->pos++];
+    }
+    return 0;
+}
+
+void Buffer::reset() {
+    this->pos = 0;
+}
+
+void Buffer::add(uint8_t* buf, size_t size) {
+    uint16_t i = 0;
+    for (;i<size;i++) {
+        this->buffer[this->length++] = buf[i];
+    }
+}

+ 23 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Buffer.h

@@ -0,0 +1,23 @@
+#ifndef buffer_h
+#define buffer_h
+
+#include "Arduino.h"
+
+class Buffer {
+private:
+    uint8_t buffer[1024];
+    uint16_t pos;
+    uint16_t length;
+    
+public:
+    Buffer();
+    Buffer(uint8_t* buf, size_t size);
+    
+    virtual bool available();
+    virtual uint8_t next();
+    virtual void reset();
+    
+    virtual void add(uint8_t* buf, size_t size);
+};
+
+#endif

+ 21 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Client.h

@@ -0,0 +1,21 @@
+#ifndef client_h
+#define client_h
+#include "IPAddress.h"
+
+class Client {
+public:
+  virtual int connect(IPAddress ip, uint16_t port) =0;
+  virtual int connect(const char *host, uint16_t port) =0;
+  virtual size_t write(uint8_t) =0;
+  virtual size_t write(const uint8_t *buf, size_t size) =0;
+  virtual int available() = 0;
+  virtual int read() = 0;
+  virtual int read(uint8_t *buf, size_t size) = 0;
+  virtual int peek() = 0;
+  virtual void flush() = 0;
+  virtual void stop() = 0;
+  virtual uint8_t connected() = 0;
+  virtual operator bool() = 0;
+};
+
+#endif

+ 44 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/IPAddress.cpp

@@ -0,0 +1,44 @@
+
+#include <Arduino.h>
+#include <IPAddress.h>
+
+IPAddress::IPAddress()
+{
+    memset(_address, 0, sizeof(_address));
+}
+
+IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet)
+{
+    _address[0] = first_octet;
+    _address[1] = second_octet;
+    _address[2] = third_octet;
+    _address[3] = fourth_octet;
+}
+
+IPAddress::IPAddress(uint32_t address)
+{
+    memcpy(_address, &address, sizeof(_address));
+}
+
+IPAddress::IPAddress(const uint8_t *address)
+{
+    memcpy(_address, address, sizeof(_address));
+}
+
+IPAddress& IPAddress::operator=(const uint8_t *address)
+{
+    memcpy(_address, address, sizeof(_address));
+    return *this;
+}
+
+IPAddress& IPAddress::operator=(uint32_t address)
+{
+    memcpy(_address, (const uint8_t *)&address, sizeof(_address));
+    return *this;
+}
+
+bool IPAddress::operator==(const uint8_t* addr)
+{
+    return memcmp(addr, _address, sizeof(_address)) == 0;
+}
+

+ 72 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/IPAddress.h

@@ -0,0 +1,72 @@
+/*
+ *
+ * MIT License:
+ * Copyright (c) 2011 Adrian McEwen
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * adrianm@mcqn.com 1/1/2011
+ */
+
+#ifndef IPAddress_h
+#define IPAddress_h
+
+
+// A class to make it easier to handle and pass around IP addresses
+
+class IPAddress {
+private:
+    uint8_t _address[4];  // IPv4 address
+    // Access the raw byte array containing the address.  Because this returns a pointer
+    // to the internal structure rather than a copy of the address this function should only
+    // be used when you know that the usage of the returned uint8_t* will be transient and not
+    // stored.
+    uint8_t* raw_address() { return _address; };
+
+public:
+    // Constructors
+    IPAddress();
+    IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet);
+    IPAddress(uint32_t address);
+    IPAddress(const uint8_t *address);
+
+    // Overloaded cast operator to allow IPAddress objects to be used where a pointer
+    // to a four-byte uint8_t array is expected
+    operator uint32_t() { return *((uint32_t*)_address); };
+    bool operator==(const IPAddress& addr) { return (*((uint32_t*)_address)) == (*((uint32_t*)addr._address)); };
+    bool operator==(const uint8_t* addr);
+
+    // Overloaded index operator to allow getting and setting individual octets of the address
+    uint8_t operator[](int index) const { return _address[index]; };
+    uint8_t& operator[](int index) { return _address[index]; };
+
+    // Overloaded copy operators to allow initialisation of IPAddress objects from other types
+    IPAddress& operator=(const uint8_t *address);
+    IPAddress& operator=(uint32_t address);
+
+
+    friend class EthernetClass;
+    friend class UDP;
+    friend class Client;
+    friend class Server;
+    friend class DhcpClass;
+    friend class DNSClient;
+};
+
+
+#endif

+ 153 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/ShimClient.cpp

@@ -0,0 +1,153 @@
+#include "ShimClient.h"
+#include "trace.h"
+#include <iostream>
+#include <Arduino.h>
+#include <ctime>
+
+extern "C" {
+    uint32_t millis(void) {
+       return time(0)*1000;
+    }
+}
+
+ShimClient::ShimClient() {
+    this->responseBuffer = new Buffer();
+    this->expectBuffer = new Buffer();
+    this->_allowConnect = true;
+    this->_connected = false;
+    this->_error = false;
+    this->expectAnything = true;
+    this->_received = 0;
+    this->_expectedPort = 0;
+}
+
+int ShimClient::connect(IPAddress ip, uint16_t port) {
+    if (this->_allowConnect) {
+        this->_connected = true;
+    }
+    if (this->_expectedPort !=0) {
+        // if (memcmp(ip,this->_expectedIP,4) != 0) {
+        //     TRACE( "ip mismatch\n");
+        //     this->_error = true;
+        // }
+        if (port != this->_expectedPort) {
+            TRACE( "port mismatch\n");
+            this->_error = true;
+        }
+    }
+    return this->_connected;
+}
+int ShimClient::connect(const char *host, uint16_t port)  {
+    if (this->_allowConnect) {
+        this->_connected = true;
+    }
+    if (this->_expectedPort !=0) {
+        if (strcmp(host,this->_expectedHost) != 0) {
+            TRACE( "host mismatch\n");
+            this->_error = true;
+        }
+        if (port != this->_expectedPort) {
+            TRACE( "port mismatch\n");
+            this->_error = true;
+        }
+
+    }
+    return this->_connected;
+}
+size_t ShimClient::write(uint8_t b)  {
+    this->_received += 1;
+    TRACE(std::hex << (unsigned int)b);
+    if (!this->expectAnything) {
+        if (this->expectBuffer->available()) {
+            uint8_t expected = this->expectBuffer->next();
+            if (expected != b) {
+                this->_error = true;
+                TRACE("!=" << (unsigned int)expected);
+            }
+        } else {
+            this->_error = true;
+        }
+    }
+    TRACE("\n"<< std::dec);
+    return 1;
+}
+size_t ShimClient::write(const uint8_t *buf, size_t size)  {
+    this->_received += size;
+    TRACE( "[" << std::dec << (unsigned int)(size) << "] ");
+    uint16_t i=0;
+    for (;i<size;i++) {
+        if (i>0) {
+            TRACE(":");
+        }
+        TRACE(std::hex << (unsigned int)(buf[i]));
+
+        if (!this->expectAnything) {
+            if (this->expectBuffer->available()) {
+                uint8_t expected = this->expectBuffer->next();
+                if (expected != buf[i]) {
+                    this->_error = true;
+                    TRACE("!=" << (unsigned int)expected);
+                }
+            } else {
+                this->_error = true;
+            }
+        }
+    }
+    TRACE("\n"<<std::dec);
+    return size;
+}
+int ShimClient::available()  {
+    return this->responseBuffer->available();
+}
+int ShimClient::read()  { return this->responseBuffer->next(); }
+int ShimClient::read(uint8_t *buf, size_t size) {
+    uint16_t i = 0;
+    for (;i<size;i++) {
+        buf[i] = this->read();
+    }
+    return size;
+}
+int ShimClient::peek()  { return 0; }
+void ShimClient::flush() {}
+void ShimClient::stop() {
+    this->setConnected(false);
+}
+uint8_t ShimClient::connected() { return this->_connected; }
+ShimClient::operator bool() { return true; }
+
+
+ShimClient* ShimClient::respond(uint8_t *buf, size_t size) {
+    this->responseBuffer->add(buf,size);
+    return this;
+}
+
+ShimClient* ShimClient::expect(uint8_t *buf, size_t size) {
+    this->expectAnything = false;
+    this->expectBuffer->add(buf,size);
+    return this;
+}
+
+void ShimClient::setConnected(bool b) {
+    this->_connected = b;
+}
+void ShimClient::setAllowConnect(bool b) {
+    this->_allowConnect = b;
+}
+
+bool ShimClient::error() {
+    return this->_error;
+}
+
+uint16_t ShimClient::received() {
+    return this->_received;
+}
+
+void ShimClient::expectConnect(IPAddress ip, uint16_t port) {
+    this->_expectedIP = ip;
+    this->_expectedPort = port;
+}
+
+void ShimClient::expectConnect(const char *host, uint16_t port) {
+    this->_expectedHost = host;
+    this->_expectedPort = port;
+}

+ 51 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/ShimClient.h

@@ -0,0 +1,51 @@
+#ifndef shimclient_h
+#define shimclient_h
+
+#include "Arduino.h"
+#include "Client.h"
+#include "IPAddress.h"
+#include "Buffer.h"
+
+
+class ShimClient : public Client {
+private:
+    Buffer* responseBuffer;
+    Buffer* expectBuffer;
+    bool _allowConnect;
+    bool _connected;
+    bool expectAnything;
+    bool _error;
+    uint16_t _received;
+    IPAddress _expectedIP;
+    uint16_t _expectedPort;
+    const char* _expectedHost;
+    
+public:
+  ShimClient();
+  virtual int connect(IPAddress ip, uint16_t port);
+  virtual int connect(const char *host, uint16_t port);
+  virtual size_t write(uint8_t);
+  virtual size_t write(const uint8_t *buf, size_t size);
+  virtual int available();
+  virtual int read();
+  virtual int read(uint8_t *buf, size_t size);
+  virtual int peek();
+  virtual void flush();
+  virtual void stop();
+  virtual uint8_t connected();
+  virtual operator bool();
+  
+  virtual ShimClient* respond(uint8_t *buf, size_t size);
+  virtual ShimClient* expect(uint8_t *buf, size_t size);
+  
+  virtual void expectConnect(IPAddress ip, uint16_t port);
+  virtual void expectConnect(const char *host, uint16_t port);
+  
+  virtual uint16_t received();
+  virtual bool error();
+  
+  virtual void setAllowConnect(bool b);
+  virtual void setConnected(bool b);
+};
+
+#endif

+ 39 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Stream.cpp

@@ -0,0 +1,39 @@
+#include "Stream.h"
+#include "trace.h"
+#include <iostream>
+#include <Arduino.h>
+
+Stream::Stream() {
+    this->expectBuffer = new Buffer();
+    this->_error = false;
+    this->_written = 0;
+}
+
+size_t Stream::write(uint8_t b)  {
+    this->_written++;
+    TRACE(std::hex << (unsigned int)b);
+    if (this->expectBuffer->available()) {
+        uint8_t expected = this->expectBuffer->next();
+        if (expected != b) {
+            this->_error = true;
+            TRACE("!=" << (unsigned int)expected);
+        }
+    } else {
+        this->_error = true;
+    }
+    TRACE("\n"<< std::dec);
+    return 1;
+}
+
+
+bool Stream::error() {
+    return this->_error;
+}
+
+void Stream::expect(uint8_t *buf, size_t size) {
+    this->expectBuffer->add(buf,size);
+}
+
+uint16_t Stream::length() {
+    return this->_written;
+}

+ 22 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/Stream.h

@@ -0,0 +1,22 @@
+#ifndef Stream_h
+#define Stream_h
+
+#include "Arduino.h"
+#include "Buffer.h"
+
+class Stream {
+private:
+    Buffer* expectBuffer;
+    bool _error;
+    uint16_t _written;
+
+public:
+    Stream();
+    virtual size_t write(uint8_t);
+    
+    virtual bool error();
+    virtual void expect(uint8_t *buf, size_t size);
+    virtual uint16_t length();
+};
+
+#endif

+ 10 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/lib/trace.h

@@ -0,0 +1,10 @@
+#ifndef trace_h
+#define trace_h
+#include <iostream>
+
+#include <stdlib.h>
+
+#define LOG(x) {std::cout << x << std::flush; }
+#define TRACE(x) {if (getenv("TRACE")) { std::cout << x << std::flush; }}
+
+#endif

+ 190 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/publish_spec.cpp

@@ -0,0 +1,190 @@
+#include "PubSubClient.h"
+#include "ShimClient.h"
+#include "Buffer.h"
+#include "BDDTest.h"
+#include "trace.h"
+
+
+byte server[] = { 172, 16, 0, 2 };
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  // handle message arrived
+}
+
+int test_publish() {
+    IT("publishes a null-terminated string");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+    shimClient.expect(publish,16);
+
+    rc = client.publish((char*)"topic",(char*)"payload");
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+
+int test_publish_bytes() {
+    IT("publishes a byte array");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
+    int length = 5;
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x30,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
+    shimClient.expect(publish,14);
+
+    rc = client.publish((char*)"topic",payload,length);
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+
+int test_publish_retained() {
+    IT("publishes retained - 1");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
+    int length = 5;
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
+    shimClient.expect(publish,14);
+
+    rc = client.publish((char*)"topic",payload,length,true);
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_publish_retained_2() {
+    IT("publishes retained - 2");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,'A','B','C','D','E'};
+    shimClient.expect(publish,14);
+
+    rc = client.publish((char*)"topic",(char*)"ABCDE",true);
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_publish_not_connected() {
+    IT("publish fails when not connected");
+    ShimClient shimClient;
+
+    PubSubClient client(server, 1883, callback, shimClient);
+
+    int rc = client.publish((char*)"topic",(char*)"payload");
+    IS_FALSE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_publish_too_long() {
+    IT("publish fails when topic/payload are too long");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    //                                         0        1         2         3         4         5         6         7         8         9         0         1         2
+    rc = client.publish((char*)"topic",(char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
+    IS_FALSE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_publish_P() {
+    IT("publishes using PROGMEM");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte payload[] = { 0x01,0x02,0x03,0x0,0x05 };
+    int length = 5;
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x31,0xc,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1,0x2,0x3,0x0,0x5};
+    shimClient.expect(publish,14);
+
+    rc = client.publish_P((char*)"topic",payload,length,true);
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+
+
+
+int main()
+{
+    SUITE("Publish");
+    test_publish();
+    test_publish_bytes();
+    test_publish_retained();
+    test_publish_retained_2();
+    test_publish_not_connected();
+    test_publish_too_long();
+    test_publish_P();
+
+    FINISH
+}

+ 249 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/receive_spec.cpp

@@ -0,0 +1,249 @@
+#include "PubSubClient.h"
+#include "ShimClient.h"
+#include "Buffer.h"
+#include "BDDTest.h"
+#include "trace.h"
+
+
+byte server[] = { 172, 16, 0, 2 };
+
+bool callback_called = false;
+char lastTopic[1024];
+char lastPayload[1024];
+unsigned int lastLength;
+
+void reset_callback() {
+    callback_called = false;
+    lastTopic[0] = '\0';
+    lastPayload[0] = '\0';
+    lastLength = 0;
+}
+
+void callback(char* topic, byte* payload, unsigned int length) {
+    callback_called = true;
+    strcpy(lastTopic,topic);
+    memcpy(lastPayload,payload,length);
+    lastLength = length;
+}
+
+int test_receive_callback() {
+    IT("receives a callback message");
+    reset_callback();
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+    shimClient.respond(publish,16);
+
+    rc = client.loop();
+
+    IS_TRUE(rc);
+
+    IS_TRUE(callback_called);
+    IS_TRUE(strcmp(lastTopic,"topic")==0);
+    IS_TRUE(memcmp(lastPayload,"payload",7)==0);
+    IS_TRUE(lastLength == 7);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_receive_stream() {
+    IT("receives a streamed callback message");
+    reset_callback();
+
+    Stream stream;
+    stream.expect((uint8_t*)"payload",7);
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient, stream);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x30,0xe,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+    shimClient.respond(publish,16);
+
+    rc = client.loop();
+
+    IS_TRUE(rc);
+
+    IS_TRUE(callback_called);
+    IS_TRUE(strcmp(lastTopic,"topic")==0);
+    IS_TRUE(lastLength == 7);
+
+    IS_FALSE(stream.error());
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_receive_max_sized_message() {
+    IT("receives an max-sized message");
+    reset_callback();
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    int length = MQTT_MAX_PACKET_SIZE;
+    byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+    byte bigPublish[length];
+    memset(bigPublish,'A',length);
+    bigPublish[length] = 'B';
+    memcpy(bigPublish,publish,16);
+    shimClient.respond(bigPublish,length);
+
+    rc = client.loop();
+
+    IS_TRUE(rc);
+
+    IS_TRUE(callback_called);
+    IS_TRUE(strcmp(lastTopic,"topic")==0);
+    IS_TRUE(lastLength == length-9);
+    IS_TRUE(memcmp(lastPayload,bigPublish+9,lastLength)==0);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_receive_oversized_message() {
+    IT("drops an oversized message");
+    reset_callback();
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    int length = MQTT_MAX_PACKET_SIZE+1;
+    byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+    byte bigPublish[length];
+    memset(bigPublish,'A',length);
+    bigPublish[length] = 'B';
+    memcpy(bigPublish,publish,16);
+    shimClient.respond(bigPublish,length);
+
+    rc = client.loop();
+
+    IS_TRUE(rc);
+
+    IS_FALSE(callback_called);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_receive_oversized_stream_message() {
+    IT("drops an oversized message");
+    reset_callback();
+
+    Stream stream;
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient, stream);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    int length = MQTT_MAX_PACKET_SIZE+1;
+    byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+
+    byte bigPublish[length];
+    memset(bigPublish,'A',length);
+    bigPublish[length] = 'B';
+    memcpy(bigPublish,publish,16);
+
+    shimClient.respond(bigPublish,length);
+    stream.expect(bigPublish+9,length-9);
+
+    rc = client.loop();
+
+    IS_TRUE(rc);
+
+    IS_TRUE(callback_called);
+    IS_TRUE(strcmp(lastTopic,"topic")==0);
+    IS_TRUE(lastLength == length-9);
+
+    IS_FALSE(stream.error());
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_receive_qos1() {
+    IT("receives a qos1 message");
+    reset_callback();
+
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte publish[] = {0x32,0x10,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x12,0x34,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
+    shimClient.respond(publish,18);
+
+    byte puback[] = {0x40,0x2,0x12,0x34};
+    shimClient.expect(puback,4);
+
+    rc = client.loop();
+
+    IS_TRUE(rc);
+
+    IS_TRUE(callback_called);
+    IS_TRUE(strcmp(lastTopic,"topic")==0);
+    IS_TRUE(memcmp(lastPayload,"payload",7)==0);
+    IS_TRUE(lastLength == 7);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int main()
+{
+    SUITE("Receive");
+    test_receive_callback();
+    test_receive_stream();
+    test_receive_max_sized_message();
+    test_receive_oversized_message();
+    test_receive_oversized_stream_message();
+    test_receive_qos1();
+
+    FINISH
+}

+ 177 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/src/subscribe_spec.cpp

@@ -0,0 +1,177 @@
+#include "PubSubClient.h"
+#include "ShimClient.h"
+#include "Buffer.h"
+#include "BDDTest.h"
+#include "trace.h"
+
+
+byte server[] = { 172, 16, 0, 2 };
+
+void callback(char* topic, byte* payload, unsigned int length) {
+  // handle message arrived
+}
+
+int test_subscribe_no_qos() {
+    IT("subscribe without qos defaults to 0");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x0 };
+    shimClient.expect(subscribe,12);
+    byte suback[] = { 0x90,0x3,0x0,0x2,0x0 };
+    shimClient.respond(suback,5);
+
+    rc = client.subscribe((char*)"topic");
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_subscribe_qos_1() {
+    IT("subscribes qos 1");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte subscribe[] = { 0x82,0xa,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x1 };
+    shimClient.expect(subscribe,12);
+    byte suback[] = { 0x90,0x3,0x0,0x2,0x1 };
+    shimClient.respond(suback,5);
+
+    rc = client.subscribe((char*)"topic",1);
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_subscribe_not_connected() {
+    IT("subscribe fails when not connected");
+    ShimClient shimClient;
+
+    PubSubClient client(server, 1883, callback, shimClient);
+
+    int rc = client.subscribe((char*)"topic");
+    IS_FALSE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_subscribe_invalid_qos() {
+    IT("subscribe fails with invalid qos values");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    rc = client.subscribe((char*)"topic",2);
+    IS_FALSE(rc);
+    rc = client.subscribe((char*)"topic",254);
+    IS_FALSE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_subscribe_too_long() {
+    IT("subscribe fails with too long topic");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    // max length should be allowed
+    //                            0        1         2         3         4         5         6         7         8         9         0         1         2
+    rc = client.subscribe((char*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
+    IS_TRUE(rc);
+
+    //                            0        1         2         3         4         5         6         7         8         9         0         1         2
+    rc = client.subscribe((char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
+    IS_FALSE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+
+int test_unsubscribe() {
+    IT("unsubscribes");
+    ShimClient shimClient;
+    shimClient.setAllowConnect(true);
+
+    byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
+    shimClient.respond(connack,4);
+
+    PubSubClient client(server, 1883, callback, shimClient);
+    int rc = client.connect((char*)"client_test1");
+    IS_TRUE(rc);
+
+    byte unsubscribe[] = { 0xA2,0x9,0x0,0x2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63 };
+    shimClient.expect(unsubscribe,12);
+    byte unsuback[] = { 0xB0,0x2,0x0,0x2 };
+    shimClient.respond(unsuback,4);
+
+    rc = client.unsubscribe((char*)"topic");
+    IS_TRUE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int test_unsubscribe_not_connected() {
+    IT("unsubscribe fails when not connected");
+    ShimClient shimClient;
+
+    PubSubClient client(server, 1883, callback, shimClient);
+
+    int rc = client.unsubscribe((char*)"topic");
+    IS_FALSE(rc);
+
+    IS_FALSE(shimClient.error());
+
+    END_IT
+}
+
+int main()
+{
+    SUITE("Subscribe");
+    test_subscribe_no_qos();
+    test_subscribe_qos_1();
+    test_subscribe_not_connected();
+    test_subscribe_invalid_qos();
+    test_subscribe_too_long();
+    test_unsubscribe();
+    test_unsubscribe_not_connected();
+    FINISH
+}

+ 0 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/__init__.py


+ 43 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/mqtt_basic.py

@@ -0,0 +1,43 @@
+import unittest
+import settings
+
+import time
+import mosquitto
+
+import serial
+
+def on_message(mosq, obj, msg):
+  obj.message_queue.append(msg)
+
+class mqtt_basic(unittest.TestCase):
+  
+  message_queue = []
+  
+  @classmethod
+  def setUpClass(self):
+    self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True,obj=self)
+    self.client.connect(settings.server_ip)
+    self.client.on_message = on_message
+    self.client.subscribe("outTopic",0)
+
+  @classmethod
+  def tearDownClass(self):
+    self.client.disconnect()
+  
+  def test_one(self):
+    i=30
+    while len(self.message_queue) == 0 and i > 0:
+      self.client.loop()
+      time.sleep(0.5)
+      i -= 1
+    self.assertTrue(i>0, "message receive timed-out")
+    self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
+    msg = self.message_queue[0]
+    self.assertEqual(msg.mid,0,"message id not 0")
+    self.assertEqual(msg.topic,"outTopic","message topic incorrect")
+    self.assertEqual(msg.payload,"hello world")
+    self.assertEqual(msg.qos,0,"message qos not 0")
+    self.assertEqual(msg.retain,False,"message retain flag incorrect")
+    
+    
+

+ 64 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/mqtt_publish_in_callback.py

@@ -0,0 +1,64 @@
+import unittest
+import settings
+
+import time
+import mosquitto
+
+import serial
+
+def on_message(mosq, obj, msg):
+  obj.message_queue.append(msg)
+
+class mqtt_publish_in_callback(unittest.TestCase):
+  
+  message_queue = []
+  
+  @classmethod
+  def setUpClass(self):
+    self.client = mosquitto.Mosquitto("pubsubclient_ut", clean_session=True,obj=self)
+    self.client.connect(settings.server_ip)
+    self.client.on_message = on_message
+    self.client.subscribe("outTopic",0)
+
+  @classmethod
+  def tearDownClass(self):
+    self.client.disconnect()
+  
+  def test_connect(self):
+    i=30
+    while len(self.message_queue) == 0 and i > 0:
+      self.client.loop()
+      time.sleep(0.5)
+      i -= 1
+    self.assertTrue(i>0, "message receive timed-out")
+    self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
+    msg = self.message_queue.pop(0)
+    self.assertEqual(msg.mid,0,"message id not 0")
+    self.assertEqual(msg.topic,"outTopic","message topic incorrect")
+    self.assertEqual(msg.payload,"hello world")
+    self.assertEqual(msg.qos,0,"message qos not 0")
+    self.assertEqual(msg.retain,False,"message retain flag incorrect")
+    
+
+  def test_publish(self):
+    self.assertEqual(len(self.message_queue), 0, "message queue not empty")
+    payload = "abcdefghij"
+    self.client.publish("inTopic",payload)
+    
+    i=30
+    while len(self.message_queue) == 0 and i > 0:
+      self.client.loop()
+      time.sleep(0.5)
+      i -= 1
+
+    self.assertTrue(i>0, "message receive timed-out")
+    self.assertEqual(len(self.message_queue), 1, "unexpected number of messages received")
+    msg = self.message_queue.pop(0)
+    self.assertEqual(msg.mid,0,"message id not 0")
+    self.assertEqual(msg.topic,"outTopic","message topic incorrect")
+    self.assertEqual(msg.payload,payload)
+    self.assertEqual(msg.qos,0,"message qos not 0")
+    self.assertEqual(msg.retain,False,"message retain flag incorrect")
+    
+
+

+ 2 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/testcases/settings.py

@@ -0,0 +1,2 @@
+server_ip = "172.16.0.2"
+arduino_ip = "172.16.0.100"

+ 179 - 0
Software/src/WiFiPCController/lib/pubsubclient/tests/testsuite.py

@@ -0,0 +1,179 @@
+#!/usr/bin/env python
+import os
+import os.path
+import sys
+import shutil
+from subprocess import call
+import importlib
+import unittest
+import re
+
+from testcases import settings
+
+class Workspace(object):
+  
+  def __init__(self):
+    self.root_dir = os.getcwd()
+    self.build_dir = os.path.join(self.root_dir,"tmpbin");
+    self.log_dir = os.path.join(self.root_dir,"logs");
+    self.tests_dir = os.path.join(self.root_dir,"testcases");
+    self.examples_dir = os.path.join(self.root_dir,"../PubSubClient/examples")
+    self.examples = []
+    self.tests = []
+    if not os.path.isdir("../PubSubClient"):
+      raise Exception("Cannot find PubSubClient library")
+    try:
+      import ino
+    except:
+      raise Exception("ino tool not installed")
+
+  def init(self):
+    if os.path.isdir(self.build_dir):
+      shutil.rmtree(self.build_dir)
+    os.mkdir(self.build_dir)
+    if os.path.isdir(self.log_dir):
+      shutil.rmtree(self.log_dir)
+    os.mkdir(self.log_dir)
+    
+    os.chdir(self.build_dir)
+    call(["ino","init"])
+    
+    shutil.copytree("../../PubSubClient","lib/PubSubClient")
+    
+    filenames = []
+    for root, dirs, files in os.walk(self.examples_dir):
+      filenames += [os.path.join(root,f) for f in files if f.endswith(".ino")]
+    filenames.sort()
+    for e in filenames:
+      self.examples.append(Sketch(self,e))
+    
+    filenames = []
+    for root, dirs, files in os.walk(self.tests_dir):
+      filenames += [os.path.join(root,f) for f in files if f.endswith(".ino")]
+    filenames.sort()
+    for e in filenames:
+      self.tests.append(Sketch(self,e))
+      
+  def clean(self):
+    shutil.rmtree(self.build_dir)
+  
+class Sketch(object):
+  def __init__(self,wksp,fn):
+    self.w = wksp
+    self.filename = fn
+    self.basename = os.path.basename(self.filename)
+    self.build_log = os.path.join(self.w.log_dir,"%s.log"%(os.path.basename(self.filename),))
+    self.build_err_log = os.path.join(self.w.log_dir,"%s.err.log"%(os.path.basename(self.filename),))
+    self.build_upload_log = os.path.join(self.w.log_dir,"%s.upload.log"%(os.path.basename(self.filename),))
+
+  def build(self):
+    sys.stdout.write(" Build:   ")
+    sys.stdout.flush()
+    
+    # Copy sketch over, replacing IP addresses as necessary
+    fin = open(self.filename,"r")
+    lines = fin.readlines()
+    fin.close()
+    fout = open(os.path.join(self.w.build_dir,"src","sketch.ino"),"w")
+    for l in lines:
+      if re.match(r"^byte server\[\] = {",l):
+        fout.write("byte server[] = { %s };\n"%(settings.server_ip.replace(".",", "),))
+      elif re.match(r"^byte ip\[\] = {",l):
+        fout.write("byte ip[] = { %s };\n"%(settings.arduino_ip.replace(".",", "),))
+      else:
+        fout.write(l)
+    fout.flush()
+    fout.close()
+    
+    # Run build
+    fout = open(self.build_log, "w")
+    ferr = open(self.build_err_log, "w")
+    rc = call(["ino","build"],stdout=fout,stderr=ferr)
+    fout.close()
+    ferr.close()
+    if rc == 0:
+      sys.stdout.write("pass")
+      sys.stdout.write("\n")
+      return True
+    else:
+      sys.stdout.write("fail")
+      sys.stdout.write("\n")
+      with open(self.build_err_log) as f:
+        for line in f:
+          print " ",line,
+      return False
+  
+  def upload(self):
+    sys.stdout.write(" Upload:  ")
+    sys.stdout.flush()
+    fout = open(self.build_upload_log, "w")
+    rc = call(["ino","upload"],stdout=fout,stderr=fout)
+    fout.close()
+    if rc == 0:
+      sys.stdout.write("pass")
+      sys.stdout.write("\n")
+      return True
+    else:
+      sys.stdout.write("fail")
+      sys.stdout.write("\n")
+      with open(self.build_upload_log) as f:
+        for line in f:
+          print " ",line,
+      return False
+
+
+  def test(self):
+    # import the matching test case, if it exists
+    try:
+      basename = os.path.basename(self.filename)[:-4]
+      i = importlib.import_module("testcases."+basename)
+    except:
+      sys.stdout.write(" Test:    no tests found")
+      sys.stdout.write("\n")
+      return
+    c = getattr(i,basename)
+    
+    testmethods = [m for m in dir(c) if m.startswith("test_")]
+    testmethods.sort()
+    tests = []
+    for m in testmethods:
+      tests.append(c(m))
+      
+    result = unittest.TestResult()
+    c.setUpClass()
+    if self.upload():
+      sys.stdout.write(" Test:    ")
+      sys.stdout.flush()
+      for t in tests:
+        t.run(result)
+        print "%d/%d"%(result.testsRun-len(result.failures)-len(result.errors),result.testsRun)
+        if not result.wasSuccessful():
+          if len(result.failures) > 0:
+            for f in result.failures:
+              print "-- %s"%(str(f[0]),)
+              print f[1]
+          if len(result.errors) > 0:
+            print " Errors:"
+            for f in result.errors:
+              print "-- %s"%(str(f[0]),)
+              print f[1]
+    c.tearDownClass()
+
+if __name__ == '__main__':
+  run_tests = True
+
+  w = Workspace()
+  w.init()
+  
+  for e in w.examples:
+    print "--------------------------------------"
+    print "[%s]"%(e.basename,)
+    if e.build() and run_tests:
+      e.test()
+  for e in w.tests:
+    print "--------------------------------------"
+    print "[%s]"%(e.basename,)
+    if e.build() and run_tests:
+      e.test()
+  
+  w.clean()

+ 15 - 0
Software/src/WiFiPCController/miscFunctions.ino

@@ -0,0 +1,15 @@
+void updateUptime() {
+  sysUptime_mins++;
+  if(sysUptime_mins == 60) {
+    sysUptime_mins=0;
+    sysUptime_hours++;
+  }
+  if(sysUptime_hours == 24) {
+    sysUptime_hours=0;
+    sysUptime_days++;
+  }
+}
+void buildUptimeString() {
+  if(sysUptime_days > 0) sprintf(uptimeStr, "%dd %02d:%02d", sysUptime_days, sysUptime_hours, sysUptime_mins);
+  else sprintf(uptimeStr, "%02d:%02d", sysUptime_hours, sysUptime_mins);
+}

Some files were not shown because too many files changed in this diff