forked from radinsky/broadlink-http-rest
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRestHome.tex
1334 lines (1044 loc) · 47.3 KB
/
RestHome.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%% LyX 2.3.6.1 created this file. For more info, see http://www.lyx.org/.
%% Do not edit unless you really know what you are doing.
\documentclass[english]{scrartcl}
\usepackage[T1]{fontenc}
\usepackage[latin9]{inputenc}
\makeatletter
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Textclass specific LaTeX commands.
\newenvironment{lyxcode}
{\par\begin{list}{}{
\setlength{\rightmargin}{\leftmargin}
\setlength{\listparindent}{0pt}% needed for AMS classes
\raggedright
\setlength{\itemsep}{0pt}
\setlength{\parsep}{0pt}
\normalfont\ttfamily}%
\item[]}
{\end{list}}
\makeatother
\usepackage{babel}
\begin{document}
\title{REST Home}
\maketitle
\section{Introduction}
RestHome is a python based home automation server. Instead of a specific
web gui, RestHome is entirely HTTP driven, using a REST based API.
There is a web gui under development. Interfaces can also be physical
things such as buttons and LCDs that can be interacted with. RestHome
provides logical control flow and state variables to home automation
rather than just static scenes or routines.
RestHome is smart enough to know how to handle being run behind ssl
tunneling software to read the actual connecting address. You should
only allow remote access via SSL and only on POST operations that
are password protected. While more secure methods of connection might
be better, IFTTT webhooks won't handle much beyond a password shoved
into a JSON payload. I'll add OAuth style webhooks eventually.
RestHome spawns multiple threads to handle its built-in HTTP server
and passes events to those threads to handle concurrent actions and
timing. There is a plugin device that can handle specific scheduling
as well.
\section{Initial Setup}
This section is in need of work.
You'll need Python 3.6 or later, the pysocks module, and likely a
few others. I'll update this at a later date.
Execute ./server.py and if it does not find a settings.ini file, it
will attempt to create a bare one and auto-detect what it can. During
discovery, it may attempt to load additional device modules and install
them. You will be prompted before installing anything.
Once it's up and running, use Control-C (\textasciicircum C) to stop
the program. Now you'll edit the settings.ini to configure the scenes
and devices and logic you want, then restart the server.
\section{Settings.ini}
The settings file is a standard ``INI'' file using square brackets
to label sections.
\begin{itemize}
\item The ``{[}General{]}'' section defines specific values related to
the server itself including binding to specific ports and security
considerations.
\item The ``{[}Status{]}'' section is a global variable space. This may
eventually go away in favor of using a catch-all device for the entire
household to allow device/variable syntax. This forms a sort of namespace
for variables by device.
\item Device sections are just the device name. They must contain details
about the device. At the very least, this section must contain a ``Type''
entry names the module to use for this device. You will usually have
a ``Comment'' field that describes the device in detail, a ``Room''
that names the room in the house where the device is located, and
a ``Template'' that defines the types of commands and default Icon
file (for the upcoming GUI) for the device. You may also specify a
``Timeout'', a ``Delay'' between commands, and maybe an ``IPAddress'',
``MACAddress'', or ``Hostname''.
\item Each device section normally has it's own Status section. For example,
I have 3 multi-split air conditioners. So for the ``{[}LivingRoom-HVAC{]}''
device section there is a ``{[}LivingRoom-HVAC Status{]}'' section
with the status variables of the device.
\item Devices that identify as a thermostat create a temperature zone list
using the names of the rooms they are in.
\item Each device section may have a ``Commands'' section just like the
``Status'' section. A device's ``Commands'' section lists command
names followed by data. The data defined for a command may be interpretted
two ways. If the data begins with a period, it is a macro, and each
entry on that line is a command to be sent to the device separated
by that device's device-delay. Otherwise, the data is interpretted
by the device.
\item ``Outside'' is the recommended device name for whatever you have
that gets the outside temperature. I normally scrape a website to
get the information. We'll see how to do this later.
\end{itemize}
\subsection{General}
\begin{description}
\item [{ServerAddress}] IP to listen on, defaults to 0.0.0.0
\item [{ServerPort}] Port to listen on, defaults to 8080
\item [{Timeout}] Defaults timeout for network operations, in seconds.
Defaults to 8
\item [{LearnFrom}] IP addresses that can use ``learnCommand''. Default
is anyone if not set.
\item [{BroadcastAddress}] Use a specific IP when broadcasting some device
discovery commands
\item [{Autodetect}] Set to a list of module names (space separated) to
attempt device discovery with that module. The list will be cleared
after device discovery completes.
\item [{AllowOverwrite}] If this is set, allow learned commands to overwrite
existing commands. If not set, existing commands are protected from
overwrite.
\item [{RestrictAccess}] Restrict all operations to this list of IPs. All
other connections will be denied.
\item [{Password}] Allow password protected POST operations from any IP.
GET operations (no password) will only be allowed from the list of
addresses in RestrictAccess. \emph{Warning:} This is a plaintext password,
so make sure this file is not readable by anyone else!
\item [{Hostname}] A hostname that will be used when generating URLs, mostly
for Web UI
\item [{House}] The device name that represents the entire house, usually
a virtual ``url'' device
\item [{Debug}] Can increase the debug level up to 8 to get an exhaustive
trace of what's going on.
\item [{Email}] May be used by the web UI
\item [{Degrees}] Attempts to report all temperatures in 'F' or 'C'.
\item [{DefaultUI}] Default name of the GUI you want to use. The only one
now is ``testing''.
\item [{CustomDash}] A custom 1-liner to throw at the top of the testing
UI. This will likely change.
\end{description}
\section{REST URLs}
\begin{itemize}
\item IR learn/send commands
\end{itemize}
\begin{lyxcode}
\#~learn~a~command~named~``mute''~from~the~``deviceName''~device
http://localhost:8080/deviceName/learnCommand/mute~
\#~send~``lampon''~command~to~``deviceName'',~set~lamp~variable~to~1/set
http://localhost:8080/deviceName/sendCommand/lampon~
\end{lyxcode}
\begin{itemize}
\item Get/Set Status variables
\end{itemize}
\begin{lyxcode}
\#~return~lamp~status~as~0~or~1~
http://localhost:8080/deviceName/getStatus/lamp~
\#~set~the~status~variable~to~1~
http://localhost:8080/deviceName/setStatus/lamp/1~
\end{lyxcode}
\begin{itemize}
\item List All Pending Events
\end{itemize}
\begin{lyxcode}
http://localhost:8080/listEvents~
\begin{lyxcode}
returns
\end{lyxcode}
\{~\\
~~~~\textquotedbl ok\textquotedbl :~\textquotedbl eventList\textquotedbl ,~\\
~~~~\textquotedbl 3\textquotedbl :~\textquotedbl ZoneTimer~=~.inc(Zone)~Menu/Init~ZoneTimer\textquotedbl ,~\\
~~~~\textquotedbl 25\textquotedbl :~\textquotedbl POLL\_LivingRoom-HVAC\_update~=~LivingRoom-HVAC\textquotedbl ,~\\
~~~~\textquotedbl 34\textquotedbl :~\textquotedbl POLL\_BedRoom-HVAC\_update~=~BedRoom-HVAC\textquotedbl ,~\\
~~~~\textquotedbl 42\textquotedbl :~\textquotedbl POLL\_UtilityRoom-HVAC\_update~=~UtilityRoom-HVAC\textquotedbl ,~\\
~~~~\textquotedbl 233\textquotedbl :~\textquotedbl ThermostatLoop~=~.ThermostatLogic~ThermostatLoop\textquotedbl ,~\\
~~~~\textquotedbl 835\textquotedbl :~\textquotedbl fetchWeatherLoop~=~.Weather/updateWeather~fetchWeatherLoop\textquotedbl ~~\\
\}
\end{lyxcode}
\begin{itemize}
\item List All Devices
\end{itemize}
\begin{lyxcode}
http://localhost:8080/listDevices~
\begin{lyxcode}
returns
\end{lyxcode}
\{
~~~~\textquotedbl ok\textquotedbl :~\textquotedbl deviceList\textquotedbl ,~\\
~~~~\textquotedbl Fireplace\textquotedbl :~\textquotedbl Duraflame~Heater\textquotedbl ,~\\
~~~~\textquotedbl Alice\textquotedbl :~\textquotedbl Vizio~SmartCast~TV\textquotedbl ,~\\
~~~~\textquotedbl LivingRoom-BlackBean\textquotedbl :~\textquotedbl LivingRoom-BlackBean\textquotedbl ,~\\
~~~~\textquotedbl AV\textquotedbl :~\textquotedbl Sony~DH770~AV~Reciever\textquotedbl ,~\\
~~~~\textquotedbl AC\textquotedbl :~\textquotedbl GREE~Air~Conditioner\textquotedbl ,~\\
~~~~\textquotedbl IFTTT\textquotedbl :~\textquotedbl IFTTT\textquotedbl ,~\\
~~~~\textquotedbl LG\textquotedbl :~\textquotedbl Ultra~Blueray~Player\textquotedbl ,~\\
~~~~\textquotedbl BeagleBone\textquotedbl :~\textquotedbl On-board~GPIO~Pins\textquotedbl ,~\\
~~~~\textquotedbl StrayScampsDen\textquotedbl :~\textquotedbl Entire~House\textquotedbl ~~\\
\}
\end{lyxcode}
\begin{itemize}
\item List Status variables for a device, and current values
\end{itemize}
\begin{lyxcode}
http://thermostat:8080/LivingRoom-HVAC/listStatus
\begin{lyxcode}
returns
\end{lyxcode}
\{
~~~~\textquotedbl ok\textquotedbl :~\textquotedbl LivingRoom-HVAC~Status\textquotedbl ,
~~~~\textquotedbl Mode\textquotedbl :~\textquotedbl heat\textquotedbl ,~\\
~~~~\textquotedbl Standby\textquotedbl :~\textquotedbl False\textquotedbl ,
~~~~\textquotedbl HeatSetpoint\textquotedbl :~\textquotedbl 72\textquotedbl ,~\\
~~~~\textquotedbl CoolSetpoint\textquotedbl :~\textquotedbl 74\textquotedbl ,~\\
~~~~\textquotedbl lastTemp\textquotedbl :~\textquotedbl 75\textquotedbl ,~\\
~~~~\textquotedbl CurrentTemp\textquotedbl :~\textquotedbl 75\textquotedbl ,~\\
~~~~\textquotedbl FanSpeed\textquotedbl :~\textquotedbl auto\textquotedbl ,~\\
~~~~\textquotedbl CurrentHumidity\textquotedbl :~\textquotedbl None\textquotedbl ,~\\
~~~~\textquotedbl CurrentSensorTemperature\textquotedbl :~\textquotedbl 75\textquotedbl ,~\\
~~~~\textquotedbl RunState\textquotedbl :~\textquotedbl normal\textquotedbl ,~\\
~~~~\textquotedbl Defrost\textquotedbl :~\textquotedbl False\textquotedbl ~\\
~\}
\end{lyxcode}
\begin{itemize}
\item Lists all rooms and the devices in that room
\end{itemize}
\begin{lyxcode}
http://thermostat:8080/listRooms
\begin{lyxcode}
returns
\end{lyxcode}
\{
~~~~\textquotedbl ok\textquotedbl :~\textquotedbl StrayScampsDen\textquotedbl ,~\\
~~~~\textquotedbl BedRoom\textquotedbl :~\textquotedbl Bedroom-Blackbean~BedRoom-HVAC\textquotedbl ,~\\
~~~~\textquotedbl LivingRoom\textquotedbl :~\textquotedbl Livingroom-Blackbean~LivingRoom-HVAC~Thermostat\textquotedbl ,~\\
~~~~\textquotedbl UtilityRoom\textquotedbl :~\textquotedbl UtilityRoom-HVAC\textquotedbl ~\\
~\}
\end{lyxcode}
\begin{itemize}
\item Similar to above, but lists thermal zones only (and current temp),
including Outside
\end{itemize}
\begin{lyxcode}
http://thermostat:8080/listThermalZones
\begin{lyxcode}
returns
\end{lyxcode}
\{~\\
~~~~\textquotedbl ok\textquotedbl :~\textquotedbl thermalZones\textquotedbl ,~\\
~~~~\textquotedbl LivingRoom\textquotedbl :~\textquotedbl 75\textquotedbl ,~\\
~~~~\textquotedbl BedRoom\textquotedbl :~\textquotedbl 75\textquotedbl ,~\\
~~~~\textquotedbl UtilityRoom\textquotedbl :~\textquotedbl 77\textquotedbl ,~\\
~~~~\textquotedbl Outside\textquotedbl :~\textquotedbl 65\textquotedbl ~\\
~\}
\end{lyxcode}
\begin{itemize}
\item Get the type of a variable (currently always returns ``string'')
\end{itemize}
\begin{lyxcode}
http://thermostat:8080/LivingRoom-HVAC/getType/Defrost
\begin{lyxcode}
returns
\end{lyxcode}
\{~\textquotedbl Defrost\textquotedbl :~\textquotedbl string\textquotedbl ~\}
\end{lyxcode}
\begin{itemize}
\item Toggle a status variable (does not send on/off commands unless you
have a trigger set on the variable. It just flips the internal status
variable, changing 0 to 1 and anything not 0 into 0.
\end{itemize}
\begin{lyxcode}
http://thermostat:8080/Thermostat/toggleStatus/Night
\begin{lyxcode}
returns
\end{lyxcode}
\{~\textquotedbl Night\textquotedbl :~\textquotedbl 1\textquotedbl ~\}
\end{lyxcode}
\section{Special Sections}
\subsection{TRIGGER device/status}
You can use this two ways, either list the ``command'' to run when
the variable changes or list ``on'' and ``off'' to first test
the new value and run the given command. Either way, if you want to
run a command when a status variable changes, this is it.
\subsection{ARRAY arrayname}
You list ``Elements'' as a space separated list. When you access
the arrayname as a status variable it will return the first item on
this list. If you increment the arrayname, an internal counter is
moved to the next element. Likewise, you can decrement the arrayname
to get to the previous element. Access is circular. The contents of
``elements'' can be another status variable. The index of the ARRAY
is stored in a status section named after the array.
\subsection{EVENT Eventname}
You specify a command as well as when it should run, relative to now.
For example, if you wanted it to run in 2 minutes and 30 seconds,
you set:
\begin{lyxcode}
minutes~=~2~\\
seconds~=~30
\end{lyxcode}
You start the event by calling it as a command from a macro or startup.
If you want the event to repeat in a loop, then have it call itself
as part of its own command parameter.
\subsection{LOGIC node}
Like an Event, you call the node like any other command. You specify
a ``test'', optionally a ``compare'', and one or more branches.
Here is an example that allows one person to set a ``target'' via
the REST setStatus/target command and then another person puts in
a guess using setStatus/guess. The higher/lower hints are printed
on the server console output.
\begin{lyxcode}
{[}LOGIC~GuessNumber{]}~~\\
test~=~guess~~\\
compare~=~target~~\\
less~=~.PRINT~higher~~\\
more~=~.PRINT~lower~~\\
equal~=~.PRINT~You~guessed~it~\\
~\\
{[}TRIGGER~Home/guess{]}~~\\
command~=~.GuessNumber~\\
~\\
{[}TRIGGER~Home/target{]}~~\\
command~=~.PRINT~Value~set!~
\end{lyxcode}
%
You can use the following conditional branches: neg, less, pos, more,
zero, equal, else, and error.
\paragraph{Advanced}
The LOGIC node can execute other logic nodes in the branches allowing
for complex conditionals and loops. Also remember that you can execute
other commands just by changing a triggered variable, or launch events
to start things in the future.
\subsection{WOL name}
Wake On LAN! You must specify ``mac'', ``ip'', and ``port''.
The last is optional and defaults to 7. It justs sends the packet
when you use ``name'' as a command
\subsection{PING hostname}
This command checks to see if a host is available. You specify ``host''
as the IP or hostname to ping and then the commands to run if the
machine is ``on'' or ``off''. Again, ``hostname'' is the name
of the command.
\subsection{SHELL command}
This creates a command that executes an external command via the system
command processor. You specify the ``command'' (be VERY careful
with command substitution because parameters can be set via POST data).
You specify the ``parameters'' to the command separately. The ``shell''
parameter defaults to False for better security, but if set to anything
else, the above command will be passed to a subshell rather than being
executed directly. This will allow full shell expansion. You can also
specify ``store'' as a variable name that will get the results of
the command execution.
\subsection{SCENE button}
This command defines a simple scene button. You pass the name of the
scene to the ``button'' either by passing this variable in JSON
POST, or via a macro by placing the scene name in parenthesis and
calling the button. For example, if you had ``{[}SCENE Livingroom{]}''
you could call it with ``Livingroom(reading)'' which would trigger
the ``reading'' scene. You define each scene in the section. You
can check the main ``House'' Status for a variable named after the
button to detect the current scene.
If a ``sceneoff'' is defined (scene with the word ``off'' glued
on the end) then the old scene ``off'' is executed before the new
scene is activated. Commands within a scene are protected so that
multiple scene changes can't happen at once.
You may specify a default ``device'' or set a custom ``deviceDelay''.
If you specify ``pre'', then it will be executed before any scene
change. Likewise, ``post'' is sent after. You can narrow the list
to a set of ``commands'' which will be passed through to the device
on a match as well as specify an ``else'' for when the given button
doesn't match.
Note: The index may soon move to a status var named ``Index'' in
a section named after the button rather than storing in the House
section.
\subsection{RADIO button}
The RADIO button is just like the SCENE button except that it can
``pop out'' a button before pushing another button. If a ``sequence''
is defined, it will push each button in sequence to get to your selection.
\subsection{GPIO label}
GPIO support is currently limited, but I hope someone expands the
module to more diverse needs. You first need to set up the GPIO pins
as a generic GPIO device. The name you define will hold any Status
variables detected by GPIO pins.
\begin{lyxcode}
{[}Thermostat{]}~~\\
Type~=~gpio~~\\
Comment~=~On-board~GPIO~Pins~~\\
Template~=~Device~~\\
Room~=~LivingRoom~~\\
StartupCommand~=~.timer00(ThermostatLoop)~~\\
ShutdownCommand~=~.GasHeateroff~Nightoff~~\\
~~~~~~~~~~~~set(StrayScampsDen/HeatRoom,LivingRoom)~\\
~~~~~~~~~~~~Menu/Boff~Menu/Backoff~Menu/Enteron
\end{lyxcode}
This creates a device called ``Thermostat'', which is the hostname
of the RPi this is running on, but it doesn't need to be the hostname.
\emph{Type} loads the gpio module. The \emph{Comment} is just descriptive
text. \emph{Template} determines basic commands available (WIP) and
what icon to use. The \emph{Room} line associates this device with
a room. \emph{StartupCommand} is a command to run during system startup
and \emph{ShutdownCommand} is executed at system shutdown. These are
available with any device description block and they are not executable
through the REST API, only during startup and shutdown, so they do
not go in the ``Commands'' section.
We'll get into macros soon, but for now, a command that begins with
a period is a macro. In this case, the \emph{ShutdownCommand} turns
off the GasHeater (a relay on GPIO), turns off Night mode (another
relay along with an internal switch we'll see soon), we set ``HeatRoom''
back to LivingRoom, turn off the ``B'' light and ``Back'' light
on the menu, and turn on the Enter light.
Now, let's set up the devices that are on the GPIO lines.
\begin{lyxcode}
{[}GPIO~Night{]}~
Template~=~switch~
type~=~relay~
gpio~=~16~\\
~\\
{[}GPIO~GasHeater{]}~
Template~=~switch~
type~=~relay
gpio~=~21~
\end{lyxcode}
This is basically saying to flip the given GPIO pin on or off. If
you are using a BeagleBone, the GPIO pins are labelled differently,
as shown below. As a ``switch'', the commands ``GasHeateron''
and ``GasHeateroff'' are defined for you.
\begin{lyxcode}
{[}GPIO~temp{]}~
type~=~tempF~
gpio~=~P9\_40~
poll~=~5~
trigger~=~thermostat
\end{lyxcode}
This example also shows how to change the \emph{type} to a temperature
sensor. This assumes an ADC input for a thermistor on P9\_40, and
we'll poll it every 5 minutes. It will poll twice with a one second
delay between and then averages the results, converts it to a temperature
and returns it in either F or C depending on the \emph{type}. This
sets a status variable called ``temp'' in the parent GPIO device
namespace. The \emph{trigger} is the command to run when the value
changes.
\section{Macros}
Now that we have seen how to create commands like EVENTs, and control
flow commands like LOGIC nodes, let's see what else we can do with
macros.
A macro begins with a period. Everything from the period to the end
of the line is considered a macro. You just list one command after
another. The \emph{device} that owns the command will have a deviceDelay
that can be set per device. The system will put a pause of at least
this amount between commands.
For each command, you can specify additional values such as ``device=''
or whatever you want and that variable will be attached to the list
of variables passed from the JSON payload. These variables can be
referenced anywhere on your macro line by prefixing the variable name
with a \$
Of course, we have some additional virtual commands for you:
\subsection{Macro functions}
\begin{description}
\item [{set(var)}] Set the given variable to one
\item [{set(var,value)}] set the given variable to the given value
\item [{clear(var)}] set the given variable to 0
\item [{toggle(var)}] changes values of 0 to 1, all others to 0
\item [{sleep(2.2)}] pause for the given number of seconds
\item [{inc(var)}] increment a variable or array index
\item [{dec(var)}] decrement a variable or array index
\item [{cancel(var)}] cancel an event where ``var'' is the event name
\item [{print(var)}] print the value of a variable after expansion
\item [{logic(...)}] executes a nameless logic node. You specify the test
and conditions in the parenthesis. For example: ``logic(test=Thermostat/CurrentTemp,
compare=Thermostat/Threshold, more=Thermostat/Heatoff, less=Thermostat/Heaton)''
\item [{event(...)}] Create and execute an event, listing the parameters
in the parenthesis
\item [{expr(varname=var1+var2)}] You can perform math on two variables
and write it to another variable. Only +, -, {*} and /
\item [{timerXX(cmd)}] Make a command run in XX number of minutes (may
be floating point). Execution of the macro is not paused because it
uses an event.
\item [{xxxxxx(button)}] Allows you to pass a value (as button=) to logic
nodes, events, arrays (you pass in the index to set it), scenes (to
set a specific scene), radio buttons (to denote the button name),
etc
\item [{\$S(var)}] Expands a Status variable name to its value
\item [{\$R(var)}] Returns the current value of an array (the index is
internal to the array). If not an array, it will shorten the value
to 8 characters (this may change soon to an explicit string manipulation
feature).
\item [{\$T(var)}] Like \$S, but attempts to display the value as a temperature
\item [{\$L(var)}] Displays a special Glyph if the LCD module is loaded.
For example, \$L(Up) will be replaced by an up arrow symbol on LCD
displays. The available Glyph names are Degree, Dot, Space, Up, Down,
Set (arrows up and down), Menu (3 lines), Night (a moon), Outside
(looks like a map point), Fan, and Droplet.
\end{description}
\subsection{Macro Example: Geolocator}
\begin{lyxcode}
{[}GEOIP{]}~~\\
URL~=~https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?
~~~~~~~~~~~~address=\$S(PostalAddress)\&benchmark=4~\\
Type~=~url~~\\
Method~=~GET~~\\
Hostname~=~geocoding.geo.census.gov~\\
Template~=~url~~\\
Find~=~Longitude~\textbackslash (X\textbackslash )~Coordinates:~</b><span>(?P<Longitude>-?
~~~~~~~~~~~~{[}0-9{]}+\textbackslash .{[}0-9{]}+)</span><br>.{*}?Latitude~\textbackslash (Y\textbackslash )~
~~~~~~~~~~~~Coordinates:~</b><span>
~~~~~~~~~~~~(?P<Latitude>-?{[}0-9{]}+\textbackslash .{[}0-9{]}+)</span>~~\\
StartupCommand~=~.fetchURL~~\\
Comment~=~Converts~Postal~Address~into~latitude~and~longitude
\end{lyxcode}
The ``fetchURL'' command is run on Startup as an event after all
devices have loaded. Actually, currently any unrecognized command
will fetch the URL, but use ``fetchURL'' for future compatibility.
You'll note that part of the URL is \$S(PostAddress). It's easier
to read and modify as a variable. You set that PostAddress in the
global status, like this:
\begin{lyxcode}
{[}Status{]}
PostalAddress~=~1234~Firecrest~Lane,~Anywhere,~TX~75666~
WeatherHost~=~darksky.net
\end{lyxcode}
You can put the address into a browser (the URL device will URL encode
for you), substituting the address for the \$S expression, and whatever
comes back, use the browser's ``view-source'' feature to see the
code. You then match the data you want to scrape off the page and
write a Unix ``regular expression'' to match the data. This expression
becomes the Find command in the device definition with the variables
in angle brackets. When matched, these variables will become device
status variables, so you end up with a section called {[}GEOIP Status{]}
with the latitude and longitude in it.
\subsection{Macro Example \#2: Weather lookup}
\begin{lyxcode}
{[}Outside{]}~
URL~=~https://\$S(WeatherHost)/forecast/\$S(GEOIP/Latitude),
~~~~~~~~~~~~\$S(GEOIP/Longitude)/us12/en~
Type~=~url~
Hostname~=~\$S(WeatherHost)~
Method~=~GET~
Template~=~url~
Find~=~<span~class=\textquotedbl currently\textquotedbl >.{*}?<img.{*}?src=\textquotedbl (?P<IconFile>.{*}?)\textquotedbl .{*}
~~~~~~~~~~~~<span~class=\textquotedbl summary~swap\textquotedbl >(?P<lastTemp>\textbackslash d+).\
~~~~~~~~~~~~(?P<Weather>.{*}?)\textbackslash .~
StartupCommand~=~.updateWeather~fetchWeatherLoop~
Comment~=~Gets~the~current~weather~by~longitude~and~latitude~
Degrees~=~F
{[}Outside~Status{]}~
IconFile~=~/images/weather-icons/partly-cloudy-night.png~
lastTemp~=~65~
Weather~=~Mostly~Cloudy~
IconURL~=~https://darksky.net/images/weather-icons/partly-cloudy-night.png
{[}Outside~Commands{]}~
updateWeather~=~.fetchURL~
~~~~~~~~~~~~set(Outside/IconURL,https://\$S(WeatherHost)\$S(Outside/IconFile))
{[}EVENT~fetchWeatherLoop{]}~
command~=~.Weather/updateWeather~fetchWeatherLoop~
minutes~=~15
\end{lyxcode}
%
This example builds upon the previous one. Now that we have the geo-location
of the address, we send that information off to another URL (the ``WeatherHost''
we set in {[}Status{]} earlier) in order to find weather information.
The Startup Command means we start with ``updateWeather'' and when
that finishes ``fetchWeatherLoop''.
\begin{description}
\item [{updateWeather}] A command that begins with a period interprets
the whole line as a macro, a list of commands. The first is to fetch
the given URL. The second is a ``set'' command that sets the ``IconURL''
in case a GUI interface wants to use the icon that the original weather
site used.
\item [{fetchWeatherLoop}] Is defined as an EVENT type. It waits 15 minutes
and then runs the command. The command is to perform our ``updateWeather''
command and then call the event again. This is how you make a repeating
loop. This updates the weather every 15 minutes.
\end{description}
\section{Device Modules}
Many of these device modules are just wrappers that load a module
from PIP to do the actual work. If the required module is not loaded,
it will ask you if you want to install it and will run pip3 for you.
\subsection*{null}
This is just a do-nothing device for testing or throwing commands
at. I use it for an overall House device to hold commands and variables
that go for the whole house in general
\subsection*{virtual}
A virtual device is one who's commands must be sent through another
device. For example, if you control a TV and/or other devices through
an IR blaster like the broadlink, then the broadlink is the actual
device and all the devices that talk through it are accessed via virtual
devices. Devices are protected from being accessed simultaneously
by different events and also from having commands sent too quickly.
This protection is always on the main device, locking all virtual
devices with it.
You defined a virtual device like this:
\begin{lyxcode}
{[}LCD{]}~
Type~=~textlcd~
Width~=~16~
Height~=~2~
Template~=~lcd~
StartupCommand~=~.sleep(5)~ZoneTimer~\\
~\\
{[}MainMenu{]}~
Type~=~virtual~
Actual~=~LCD~
Line1~=~\$L(Space)+~|~\$L(Night)~|~\$L(Up)~|~\$L(Down)~
Line2~=~\$R(\$R(Zone))~\$L(Dot)\$T(\$R(Zone)/lastTemp)~
~~~~~~~~~~~~\$L(Set)\$T(\$R(Zone)/HeatSetpoint)
\end{lyxcode}
%
The LCD (defined by the ``textlcd'' module). The ``MainMenu''
device is one of the menus on the LCD. It is a virtual device. Use
``Actual'' to specify the device commands are actually sent to.
The rest we'll get into in the big example.
\subsection*{url}
We've already seen this device used in the examples above.
\subsection*{scheduler}
Here is one way to set up a schedule:
\begin{lyxcode}
{[}WeekDay{]}~
Type~=~schedule
Comment~=~WeekDay~Schedule~
Device~=~StrayScampsDen~\\
~\\
{[}WeekDay~Status{]}~
08\_10~=~.earlyrise~
08\_55~=~.getupnow~
19\_10~=~.Kitchen/dim~
20\_15~=~.killalllights~
24\_00~=~.needsomesleep~
only~=~weekdays~\\
~\\
{[}Weekends{]}~
Type~=~schedule
Comment~=~Weekends~
Device~=~StrayScampsDen~\\
~\\
{[}Weekends~Status{]}~
10\_30~=~.earlyrise~
only~=~weekends~
\end{lyxcode}
%
A schedule can be enabled and disabled by using either setStatus to
set the ``enabled'' status variable or use sendCommand/enable and
sendCommand/disable. The initial setting is enabled if not specified.
The ``only'' command can restrict a schedule to a specific day of
the week or only weekends or weekdays. Weekdays can be 'Monday', 'Tuesday',
'Wednesday', 'Thursday', 'Friday', 'Saturday', or 'Sunday'.
You may specify a few other parameters in the device itself. You may
specify a ``trigger'' command that is run every ``poll'' time,
which defaults to every hour (it's in seconds, so 3600). Unlike an
Event, a schedule has all of it's information in status variables,
making it easier to enable and disable, or change parameters dynamically.
For example, implementing a ``boost mode'' outside of your usual
schedule might be easiest to implement by disabling the usual thermostat
loop. You could test a variable with a LOGIC section, but the scheduler
does this all for you.
Otherwise, it will look for the next clock time, as specified in the
Status variables and will run the given command at that time. The
execution of any command through the scheduler module will set the
following variable: \$month, \$day, \$HH, \$mm, \$ss, \$weekday, and
\$isWeekday (the last one is a boolean).
\subsection*{log}
Here is a cool little device. Every device of this type is a file,
and each command logs something to that file. The command data is
a string, like this example that you can send commands to, or even
make it the DebugLog into a menu that logs all button presses to a
file.
\begin{lyxcode}
{[}DebugLog{]}
Type~=~log
Output~=~/tmp/mylogfile.txt
Comment~=~Various~items~logged~at~runtime~\\
~\\
{[}DebugLog~Commands{]}
logtemp~=~\$month/\$day~\$HH:\$mm~Temp~=~\$S(Weather/temp)
lognotify~=~NOTIFICATION~FROM:~\$from~TITLE:~\$title
logpre~=~INITIAL~NOTICE~\$title~from~\$from
Init~=~Initialized~Menu
Enter~=~Enter~button~pressed
A~=~Got~'A'
B~=~Got~'B'
C~=~Got~'C'
D~=~Get~'D'
Exit~=~Exited~Menu
\end{lyxcode}
\subsection*{broadlink}
The broadlink device should support most of the broadlink IR gateway
families, but has only been tested on the Blackbean Mini. Here the
data for commands (if not a macro) is interpretted to be IR data that
is saved with the ``learnCommand'' feature from the REST API. This
module is best used by first setting Autodetect to ``broadlink''
and then editing the names of the autogenerated information.
The devices you talk to through the broadlink device are all set up
as virtual devices. It's really easy to generate an IR remote web
interface that simply does a ``sendCommand'' URL to ``push buttons''
on phantom remotes.
\subsection*{gpio}
At the moment, temp sensors on a BeagleBone and relays on RPi are
tested. You could be able to make relays work on BeagleBone by just
specifying the pins using the BeagleBone naming convention. A different
module is used for each system and it will detect the system its running
on and it will ask to install the correct module on first run. You
should just be able to press 'Y' and let it do it.
\subsection*{kumo}
The kumo cloud module interfaces with the Mitsubishi HVAC devices
that use the ``Kumo Cloud'' app. You have to set up a ``{[}Kumo
Status{]}'' section with your username and password, then set Autodetect
to ``kumo'' and start the server. It will autodetect all the units.
It's best to look at the code in the ``plugins'' directory for the
list of parameters this supports since its being actively changed
at the moment.
\subsection*{textlcd}
This module is for driving text-based LCDs. It has a number of custom
glyphs that you load with \$L() expansions.
This includes: Degree, Dot, Space, Up, Down, Set (arrow up and down),
Menu (3 dashes), Night (crescent moon), Outside (like a GPS drop pin),
Fan, and droplet (like water, for humidity).
There is lots of capability here, but I found the module I have is
very sensitive to voltage drops which tend to occur when the relay
switches (5V relay - may try adding a resistor some day but I don't
want to take it off the wall). For this reason, the code basically
reinitializes every time the screen is cleared and clears the screen
with every new menu. This is really slow, but it stops the screen
from displaying trash.
Examples of using textlcd to create menus is at the end of this document.
\subsection*{pimoroni-touchphat}
This cool little module maintains a stack of devices. As you touch
buttons on the device, it blinks the button's LED and then sends a
command (the same name as the button) to whatever device is at the
top of it's stack. You can easily add menus to the stack. You use
the 'Back' button to pop a menu off the stack and return to the previous
menu. Menus can have a Start and an Exit command defined and these
will be run when the menu is first loaded and before it is removed.
If you are at the main menu and press back, it loads the menu defined
for ``Back''. The back button will toggle between these two menus
if their are no other menus on the stack.
Of course, menus are normally virtual menus on top of the LCD device.
This way, the menu gets displayed on the LCD when it gets the Init
command, sent after ``Start'' finishes.
FIXME: The menu system should likely be extracted out and separated
such that you can use other input methods with it, such as buttons
connected to individual GPIO pins, keyboards, or other hat modules
with buttons.
\section{SSL \& Security}
The code will accept being fed data from SSL tunnel and recognizes
the real IPs rather than the IP of the ssl tunnel itself. Most exterior
integration was focused on IFTTT until Google changed how voice commands
work. In this case, any variables within the JSON can be expanded
using \$variable expansion or tested within a LOGIC expression. Password
security can be set up via IFTTT by setting a variable called ``password''
within the SSL-encrypted JSON payload.
Using key-based security might be possible to set up, but has not
been done at this time.
\section{Thermostat Example}
Ready to build the cool thermostat in the video? Want to make a dynamic
menu system to control your whole house? Here is how a RPi 0 2W, a
simple 2 line LCD, a touch-hat with capacitive buttons, and a pair
of relays on the GPIO pins can control not just an old gas wall heater,
but also control the entire house. Since it uses the temperature readings
and set-points of the Mitsubishi units, the presence sensing and Google
Assistant features of the Mitsubishi units work for the gas heater
(the Mitsubishi multi-splits don't do heat very well if it freezes
outside because I didn't get the heat boost addition since I figured
the gas wall unit would be cheaper to run). I run both when it cold
outside. And of course, a simple LOGIC node can turn off the heat
(by disabling the schedule) when its hot enough outside or when the
kumo units are set to Cool. This functionality is not shown in the
sample below:
\begin{lyxcode}
{[}LivingRoom-HVAC{]}
Name~=~LivingRoom
IPAddress~=~192.168.X.Y
Serial~=~<autodetected~serial~number>
Type~=~kumo
Template~=~thermostat
Comment~=~Mitsubishi~AC
Room~=~LivingRoom~\\
~\\
{[}BedRoom-HVAC{]}
Name~=~BedRoom
IPAddress~=~192.168.X.Y
Serial~=~<autodetected~serial~number>
Type~=~kumo
Template~=~thermostat
Comment~=~Mitsubishi~AC
Room~=~BedRoom~\\
~\\
{[}Kumo~Status{]}
Username~=~<username>
Password~=~<password>~\\
~\\
{[}Menu{]}
Type~=~pimoroni-touchphat
Device~=~MainMenu
Back~=~SecondMenu~\\
~\\
{[}LCD{]}
Type~=~textlcd
Width~=~16
Height~=~2
Template~=~lcd
StartupCommand~=~.sleep(5)~ZoneTimer~\\
~\\
{[}MainMenu{]}
Type~=~virtual
Actual~=~LCD