forked from hakimel/reveal.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
1498 lines (1324 loc) · 64.7 KB
/
index.html
File metadata and controls
1498 lines (1324 loc) · 64.7 KB
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
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>DIY IoT Lamp Workshop</title>
<!-- Primer (https://primer.style/) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Primer/10.9.0/build.css" integrity="sha256-BBqc9dgsx+a7JMEvvfApoq2WsTfULA1IsicmrBt4nN8=" crossorigin="anonymous" />
<link rel="stylesheet" href="css/reveal.css">
<link rel="stylesheet" href="css/theme/black.css">
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="lib/css/zenburn.css">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.2/css/all.css" integrity="sha384-/rXc/GQVaYpyDdyxK+ecHPVYJSN9bmVFBvjA/9eOB+pb3F2w2N6fc5qB9Ew5yIns" crossorigin="anonymous">
<!-- custom fonts -->
<link href="https://fonts.googleapis.com/css?family=Old+Standard+TT" rel="stylesheet">
<!-- custom styles -->
<style>
pre.serialmon {
background:white;
color:black;
}
.copybutton {
position:absolute;
top:0;
right:-1.25em;
}
[hidden] {
/* override Primer style which was hiding slides in the "overview" mode */
display: block !important;
}
li.equation {
/* meant to approximate the look of a LaTeX equation */
font-family: 'Old Standard TT', serif;
font-style: italic;
}
</style>
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
</head>
<body>
<div class="reveal">
<div class="slides">
<section id="title" data-background-image="images/lamp.jpg" data-background-opacity=".1">
<h1>DIY IoT Lamp Workshop</h1>
<p>Leonard Lee</p>
<p><a href="https://github.com/leoclee"><small><i class="fab fa-github"></i> leoclee</small></a></p>
</section>
<section id="iot-definition">
<h2>Internet of Things (IoT)</h2>
<blockquote cite="https://en.wikipedia.org/wiki/Internet_of_things">
“extend[s] Internet connectivity beyond standard devices, such as desktops, laptops, smartphones and tablets, to any range of traditionally dumb or non-internet-enabled physical devices and everyday objects”
</blockquote>
</section>
<section id="iot-examples" data-background-image="images/network-782707_1280-980x637.png" data-background-opacity=".1">
<!-- background image by jeferrb from https://pixabay.com/en/network-iot-internet-of-things-782707/ -->
<h2>Things Like These:</h2>
<ul>
<li>speakers</li>
<li>televisions</li>
<li>lighting</li>
<li>thermostats</li>
<li>kitchen appliances</li>
<li>vehicles/planes</li>
<li>fitness equipment</li>
<li>vending machines/kiosks</li>
<li>switches/outlets</li>
</ul>
</section>
<section id="iot-why">
<h2>Why?</h2>
<p>Connecting devices to the internet and to each other provide some form of value:</p>
<ul>
<li>remote access</li>
<li>home automation (smart homes)</li>
<li>cloud integration</li>
<li>real time monitoring/tracking/notification</li>
</ul>
</section>
<section id="objectives">
<h2>Objectives</h2>
<ul>
<li>build a functional IoT lamp</li>
<li>experience a hands-on electronics project</li>
<li>learn about various network protocols</li>
<li>try Arduino coding</li>
<li>inspire participants with the possibilities of IoT</li>
</ul>
</section>
<section id="the-lamp">
<p class="stretch"><img data-src="images/lamp.jpg"/></p>
</section>
<section id="supplies" data-background-image="images/surprise.jpg" data-background-opacity=".1">
<h2>Supplies</h2>
<ul>
<li><a href="https://www.ikea.com/us/en/catalog/products/60356144/">IKEA TVÄRS table lamp</a></li>
<li><a href="https://wiki.wemos.cc/products:d1:d1_mini">WEMOS/LOLIN D1 Mini</a> (or clone)</li>
<li>5V individually addressable RGB LED strip (<a href="https://www.adafruit.com/category/168">NeoPixels</a>/WS2811/WS2812(B)/WS2813/SK6812 (mini))</li>
<li>micro USB cable</li>
<li>5V USB power supply</li>
<li>paper towel/toilet paper cardboard tube</li>
<li>wires</li>
</ul>
</section>
<section id="esp8266" data-background-image="https://wiki.wemos.cc/_media/products:d1:d1_mini_v3.1.0_1_16x9.jpg" data-background-opacity=".25">
<h2>D1 Mini/ESP8266</h2>
<p>The D1 Mini is a development board that uses the ESP8266 microcontroller/<abbr title="system on chip">SoC</abbr> to connect to the internet. It is much more powerful than some of the standard Arduino boards, though it has slightly fewer GPIO pins.</p>
<p>Overall, it is a good balance between size, capability, and cost.</p>
<!-- TODO stop hotlinking background photo -->
</section>
<section id="pi" data-background-image="https://www.raspberrypi.org/app/uploads/2017/05/Raspberry-Pi-3-1-1619x1080.jpg" data-background-opacity=".25">
<h2>What About Raspberry Pi?</h2>
<!-- TODO stop hotlinking background photo -->
<ul>
<li>Raspberry Pi (and other Single Board Computers) are full fledged computers that run an <abbr title="operating system">OS</abbr> and can run multiple programs.</li>
<li>The D1 Mini (and other Arduino compatible boards) are microcontrollers that run 1 program at a time.</li>
<li>While the Pi is (waaay) more powerful and can do more, it also takes longer to boot up, is bigger, is more sensitive to power outages, and costs more--basically overkill for a project like this.</li>
</ul>
</section>
<section id="code-prep">
<h2>Preparing the IDE</h2>
<ol>
<li>install <a href="https://wiki.wemos.cc/downloads">CH340G driver</a></li>
<li>install the <a href="https://www.arduino.cc/en/Main/Software">Arduino <abbr title="integrated development environment">IDE</abbr></a></li>
<li>Preferences > add an Additional Boards Manager URL: <pre><code class="shell copy">http://arduino.esp8266.com/stable/package_esp8266com_index.json</code></pre></li>
<li>Tools > Board > Boards Manager... > esp8266 > Install</li>
<li>Tools > Board > ESP8266 Modules > LOLIN(WEMOS) D1 R2 & Mini</li>
</ol>
</section>
<section>
<section id="first-sketch">
<h2>Upload Your First Sketch</h2>
<ol>
<li>connect the D1 mini to your computer using the micro USB cable</li>
<li>select the correct COM port under Tools > Port</li>
<li>open the Blink example sketch under File > Examples > ESP8266 > Blink</li>
<li>click on the Upload icon at the top (or CTRL+U/⌘+U)</li>
<li>After the sketch is successfully uploaded, the blue LED on the D1 Mini should start blinking! This is the <a href="https://en.wikipedia.org/wiki/%22Hello,_World!%22_program">Hello World</a> of electronics.</li>
</ol>
</section>
<section id="win-com-port">
<figure><img data-src="images/device-manager-com-port.png" style="margin:0;"/><figcaption><small>On Windows, open the Device Manager to find the port named USB-SERIAL CH340</small></figcaption></figure>
</section>
</section>
<section id="blink">
<h2>Blink: What's Happening in <code>setup()</code> and <code>loop()</code></h2>
<pre><code class="cpp copy">void setup() {
pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output
}
void loop() {
digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level
delay(1000); // Wait for a second
digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH
delay(2000); // Wait for two seconds (to demonstrate the active low LED)
}</code></pre>
<p><small>File > Examples > ESP8266 > Blink</small></p>
</section>
<section id="delay">
<h2>The Problem with <code>delay()</code></h2>
<p><a href="https://www.arduino.cc/reference/en/language/functions/time/delay/"><code>delay()</code></a> is a blocking function that pauses execution until a certain amount of time has passed. During this time, it brings most other activity to a halt (the ESP8266 has a single core). This is problematic for any device which needs to react quickly to user input or other events.</p>
</section>
<section id="blink-without-delay">
<h2>Blink Without Delay</h2>
<p><small>Solves the problem by only executing code after a certain amount of time has passed.</small></p>
<pre><code class="cpp copy">int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000;
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (ledState == LOW) {
ledState = HIGH; // Note that this switches the LED *off*
} else {
ledState = LOW; // Note that this switches the LED *on*
}
digitalWrite(LED_BUILTIN, ledState);
}
}</code></pre>
<p><small>File > Examples > ESP8266 > BlinkWithoutDelay</small></p>
</section>
<section id="serial-monitor">
<h2>Serial Monitor</h2>
<p>The Serial Monitor can be used to display text from the microcontroller for debugging purposes.</p>
<p>Tools > Serial Monitor (or Ctrl+Shift+M/⌘+Shift+U)</p>
</section>
<section id="serial-monitor-screenshot">
<img data-src="images/serialmonitor.png"/>
</section>
<section id="hello-world">
<h2>Software Hello World</h2>
<ul>
<li>Set the baud rate on the Serial Monitor to match the above <code>Serial.begin()</code> call (115200)</li>
<li>Add the following to the beginning of the <code>setup()</code> function:</li>
<pre><code class="cpp copy"> Serial.begin(115200);
Serial.println("");
Serial.println("Hello, World!");</code></pre></li>
<li>Re-upload the program; you should be able to see "Hello, World!" printed in the Serial Monitor window</li>
</ul>
</section>
<section id="led-strip" data-background-video="https://cdn-shop.adafruit.com/product-videos/1200x900/2969-04.mp4" data-background-video-loop="loop" data-background-opacity=".5" data-background-video-muted="true">
<h2>WS2812B/NeoPixel LED Strips</h2>
<ul>
<li>Individually addressable: each LED can be a different color/brightness at the same time</li>
<li>Run on 5V DC, same as what is provided by USB</li>
<li>LED density from 30-144 LEDs/m</li>
<li>Some strips are weather/water proof (<a href="https://en.wikipedia.org/wiki/IP_Code">IP</a> 6X rated) at the cost of a few mm of thickness</li>
<li>Arrows indicate the direction of data flow</li>
<li>Can be cut along the copper pads</li>
<li>Adhesive backing (usually terrible)</li>
</ul>
</section>
<section id="wiring">
<h2>Wiring Time</h2>
<p>Connect the LED strip to the D1 mini by soldering three wires:</p>
<p><small>⚠️ Ask for help if you are unable to solder the connections yourself! ⚠️</small></p>
<table>
<thead>
<tr>
<th>D1 mini</th>
<th>LED strip</th>
</tr>
</thead>
<tbody>
<tr>
<td>D2</td>
<td>DI/DIN</td>
</tr>
<tr>
<td>G/GND</td>
<td>GND/-</td>
</tr>
<tr>
<td>5V</td>
<td>5V/+5V/VCC/+</td>
</tr>
</tbody>
</table>
</section>
<section id="wiring-diagram">
<h2>Wiring Diagram</h2>
<p><small>⚠️ Your LED strip's connection leads may not be in the same order! Check the labels! ⚠️</small></p>
<img data-src="images/fritzing.png"/>
</section>
<section id="wiring-photo">
<p class="stretch"><img data-src="images/wired.jpg"/></p>
<p><small>using an LED strip connector... note the arrow direction</small></p>
</section>
<section id="level-shifting">
<h2>A Note About <a href="https://en.wikipedia.org/wiki/Level_shifter">Level Shifting</a></h2>
<p>D2 pin connects directly to the strip's data input ("DI"/"DIN"). D2's output voltage is 3.3V and DI expects 5V*--but we are hoping to avoid level shifting as long as it works to keep things simple.</p>
<p><small>* According to the <a href="http://www.world-semi.com/DownLoadFile/108">WS2812B datasheet</a>, the minimum voltage for highs on the data input signal ought to be 0.7×V<sub>DD</sub> or 3.5V with a 5V power supply. However, this setup still works because it's <em>close enough?</em> <span style="white-space: nowrap;">¯\_(ツ)_/¯</span> You can try to avoid flickering issues by using a power supply that is no greater than 5V (some go to 5.25V).</small></p>
</section>
<section id="read-power-supply">
<h2>Reading a Power Supply</h2>
<p><a href="https://amzn.to/2Ro8FeA"><figure><img data-src="images/5v.jpg" style="margin:0;"/><figcaption><small>Amazon.com</small></figcaption></figure></a></p>
<p style="position:absolute;top:30%;left:-140px;background:rgba(0,0,0,.75);padding:10px;border-radius:10px;" class="fragment"><strong>INPUT</strong><br>∿ <a href="https://en.wikipedia.org/wiki/Alternating_current"><strong>A</strong>lternating <strong>C</strong>urrent</a><br>100-240V</p>
<p style="position:absolute;top:30%;right:-50px;background:rgba(0,0,0,.75);padding:10px;border-radius:10px;" class="fragment"><strong>OUTPUT</strong><br>⎓ <a href="https://en.wikipedia.org/wiki/Direct_current"><strong>D</strong>irect <strong>C</strong>urrent</a><br>5V<br>3000 mA</p>
<p style="position:absolute;top:15%;margin-left:auto;margin-right:auto;left:0;right:0;background:rgba(0,0,0,.75);padding:10px;border-radius:10px;" class="fragment">Switching (vs Transformer)</p>
<p style="position:absolute;bottom:10%;right:-50px;background:rgba(0,0,0,.75);padding:10px;border-radius:10px;" class="fragment"><a href="https://en.wikipedia.org/wiki/FCC_Declaration_of_Conformity"><abbr title="Federal Communications Commission">FCC</abbr> mark</a> and <a href="https://www.ul.com/marks/ul-listing-and-classification-marks/appearance-and-significance/marks-for-north-america/"><abbr title="Underwriters Laboratories">UL</abbr> Listed</a></p>
<p style="position:absolute;bottom:10%;left:0;background:rgba(0,0,0,.75);padding:10px;border-radius:10px;" class="fragment"><a href="https://www.cui.com/efficiency-standards">Efficiency Level</a> VI</p>
</section>
<section id="power-reqs">
<h2>⚡ Power Requirements ⚡</h2>
<ul>
<li>Each LED can draw up to 60 mA at maximum brightness white.</li>
<li>The D1 mini can also draw up to 250 mA.</li>
<li>Some computer USB ports can only supply 500 mA. Therefore, avoid setting the LEDs to max brightness or limit the number of LEDs lit at the same time.</li>
<li>The D1 mini also has a 500 mA fuse for the 5V pin. To support more LEDs, you should bypass powering the LEDs through the 5V pin (more on this later).</li>
<li class="equation">⌊(supply-250)/60⌋ = # of max brightness LEDs</li>
</ul>
</section>
<section id="fastled">
<h2>FastLED Library</h2>
<ul>
<li>Libraries are packages of code that can provide extra functionality for your sketches. Don't reinvent the wheel.</li>
<li><q cite="http://fastled.io/"><a href="http://fastled.io/">FastLED</a> is a fast, efficient, easy-to-use Arduino library for programming addressable LED strips and pixels</q></li>
<li>Support for <abbr title="hue saturation value">HSV</abbr> color model</li>
<li>Installation: Tools > Manage Libraries... > FastLED > Install</li>
</ul>
</section>
<section id="fastled-blink">
<h2>FastLED Blink</h2>
<p><small>After loading the FastLED Blink example, remove the commented out code and set <code>DATA_PIN</code> to our data pin and <code>NUM_LEDS</code> to the number of LEDs in the strip:</small></p>
<pre><code class="cpp copy">#include <FastLED.h>
#define NUM_LEDS 10 // number of LEDs in the strip/ring
#define DATA_PIN D2 // the GPIO pin for the LEDs' data
// Define the array of leds
CRGB leds[NUM_LEDS];
void setup() {
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
}
void loop() {
// Turn the LED on, then pause
leds[0] = CRGB::Red;
FastLED.show();
delay(500);
// Now turn the LED off, then pause
leds[0] = CRGB::Black;
FastLED.show();
delay(500);
}</code></pre>
<p><small>File > Examples > FastLED > Blink</small></p>
</section>
<section id="fastled-blink-without-delay">
<h2>Remove <code>delay()</code>... Again</h2>
<p>This time, we get to use FastLED's <code>EVERY_N_MILLISECONDS</code> function*! Replace the <code>loop()</code> function with the following:</p>
<pre><code class="cpp copy">void loop() {
EVERY_N_MILLISECONDS(500) {
if (leds[0].r == 0) {
leds[0] = CRGB::Red; // Turn the LED on
} else {
leds[0] = CRGB::Black; // Turn the LED off
}
FastLED.show();
}
}</code></pre>
<p><small>* actually a <a href="https://gcc.gnu.org/onlinedocs/cpp/Function-like-Macros.html#Function-like-Macros">preprocessor function-like macro</a></small></p>
</section>
<section id="show-current-color">
<h2>Now For Something Useful</h2>
<p>That blinking program was a good exercise, but we won't need to use <code>EVERY_N_MILLISECONDS</code> until later. Instead, let's work on making the LEDs light up with the color that we specify.</p>
</section>
<section id="show-current-color-1">
<p>First, add global variables under the <code>#include <FastLED.h></code> line, which will keep track of the current state:</p>
<pre><code class="cpp copy">// state
unsigned int hue = 180; // hue state (0-359)
uint8_t saturation = 100; // saturation state (0-100)
uint8_t value = 50; // value/brightness state (0-100)
boolean state = true; // on/off state (true = "ON", false = "OFF")
char effect[10] = "none"; // "none" | "colorloop" | "trail"
const char* on_state = "ON";
const char* off_state = "OFF";</code></pre>
</section>
<section id="show-current-color-2">
<p>Next, add a global variable <code>currentColor</code> under the declaration for <code>leds</code> near the top:</p>
<pre><code class="cpp copy">CRGB leds[NUM_LEDS];
CHSV currentColor = CHSV(hue * 255 / 359, saturation * 255 / 100, value * 255 / 100);</code></pre>
</section>
<section id="show-current-color-3">
<p>Finally, we'll need to replace the <code>loop()</code> function with:</p>
<pre><code class="cpp copy">void loop() {
// LED
loop_led();
}
void loop_led() {
if (state) {
FastLED.showColor(currentColor);
} else {
FastLED.clear(true); // turn off all LEDs
}
}</code></pre>
</section>
<section id="show-current-color-4">
<p class="stretch"><img data-src="images/currentcolor.jpg"/></p>
</section>
<section id="show-current-color-5">
<h2>Changing the Color</h2>
<p>Try setting the <code>hue</code> variable near the top to <code>0</code>.</p>
<pre><code class="cpp copy">unsigned int hue = 0; // hue state (0-359)</code></pre>
<div class="fragment">
<p>What about lowering the <code>value</code> variable to 25?</p>
<pre><code class="cpp copy">uint8_t value = 25; // value/brightness state (0-100)</code></pre>
</div>
<div class="fragment">
<p>Color changes made during compile time work, but run time changes are preferred.</p>
</div>
</section>
<section id="save">
<h2>💾 Save Your Work 💾</h2>
<p>This is a good point to save your sketch. The Blink example which we have based our work on so far is read only, so it will prompt you to save your sketch to a new location when using File > Save.</p>
<p>With the default preferences, the sketch will be automatically saved on each compile/upload going forward.</p>
</section>
<section id="wifi-begin">
<h2>Connecting to WiFi</h2>
<p>Let's start to add some WiFi capabilities. Our first task is to set up <a href="https://github.com/tzapu/WiFiManager">WiFiManager</a>, which will make it unnecessary to hardcode our WiFi connection info.</p>
<p>Start by installing the WiFiManager library (Tools > Manage Libraries... > WiFiManager > Install)</p>
</section>
<section id="wifimanager-global">
<p>Then, add the following includes at the top, near the <code>FastLED.h</code> include:</p>
<pre><code class="cpp copy">#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h></code></pre>
<p><small>(some of these libraries are installed as part of the ESP8266 board)</small></p>
<div class="fragment">
<p>Next, add a global <code>wifiManager</code> variable above the <code>setup()</code> function:</p>
<pre><code class="cpp copy">// WiFi
WiFiManager wifiManager;</code></pre>
</div>
</section>
<section id="wifi-setup">
<p>Finally, replace the <code>setup()</code> function with:</p>
<pre><code class="cpp copy">/**
Unique identifer used for wifi hostname, AP SSID, MQTT ClientId, etc.
*/
#define ID_PREFIX "LIGHT-"
String getIdentifier() {
// the ESP8266's chip ID is probably unique enough for our purposes
// see: https://bbs.espressif.com/viewtopic.php?t=1303
// see: https://github.com/esp8266/Arduino/issues/921
String chipId = String(ESP.getChipId(), HEX);
chipId.toUpperCase();
return ID_PREFIX + chipId;
}
void setup() {
Serial.begin(115200);
Serial.println();
// LED
FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
// WiFi
WiFi.hostname(getIdentifier().c_str());
wifiManager.autoConnect(getIdentifier().c_str());
Serial.printf("WiFi IP Address: %s\n", WiFi.localIP().toString().c_str());
Serial.printf("WiFi Hostname: %s\n", WiFi.hostname().c_str());
Serial.printf("WiFi MAC addr: %s\n", WiFi.macAddress().c_str());
Serial.printf("WiFi SSID: %s\n", WiFi.SSID().c_str());
}</code></pre>
</section>
<section id="wifimanager">
<p>After uploading the code, tell the D1 mini which network to connect to:</p>
<ol>
<li>In the Serial Monitor, look for a line that indicates the access point name:<pre class="serialmon">*WM: Configuring access point...
*WM: <mark>LIGHT-ABC123</mark></pre></li>
<li>On your phone or laptop, look for an open network that matches (e.g., LIGHT-ABC123) and connect to it.</li>
</ol>
</section>
<section id="wifimanager-ap">
<p class="stretch"><img data-src="images/wifimanager1.png"></p>
<p><small>Once connected, you should see a prompt to sign in (e.g., "Tap here to sign in to network"). Otherwise, open <a href="http://192.168.4.1">http://192.168.4.1</a> in a browser.</small></p>
</section>
<section id="wifimanager-captiveportal">
<p class="stretch"><img data-src="images/wifimanager2.png"> <img data-src="images/wifimanager3.png"> <img data-src="images/wifimanager4.png"></p>
<p><small>Select your network and provide the credentials</small></p>
</section>
<section id="wifi-connected">
<p>After the D1 mini restarts, verify connectivity to the network by checking for an assigned IP address in the Serial Monitor:</p>
<pre class="serialmon">*WM:
*WM: AutoConnect
*WM: Connecting as wifi client...
*WM: Using last saved values, should be faster
*WM: Connection result:
*WM: 3
*WM: IP Address:
*WM: 192.168.1.206
<mark>WiFi IP Address: 192.168.1.206</mark>
WiFi Hostname: LIGHT-ABC123</pre>
</section>
<section id="json">
<h2>JSON payload</h2>
<p>Let's define the kinds of messages we will be sending back and forth with our WiFi connected device. <a href="https://www.json.org/"><abbr title="JavaScript Object Notation">JSON</abbr></a> is a standard, compact, yet human-readable format:</p>
<pre><code class="json">{
"state" : "ON",
"brightness" : 100,
"color" : {
"h" : 0,
"s" : 100
},
"effect" : "none"
}</code></pre>
<p><small>used to describe the current state and to request a change, regardless of protocol</small></p>
</section>
<section id="json-optional">
<h2>Optional Fields</h2>
<p>When requesting a change, you can omit fields that are not required. This allows requests to be transmitted quicker and avoids unnecessary processing.</p>
<pre><code class="json">{"brightness":25}</code></pre>
<p><small>whitespace outside of double quotes is ignored</small></p>
</section>
<section id="arduino-json">
<h2>ArduinoJson Library</h2>
<p>We will be using the <a href="https://arduinojson.org">ArduinoJson</a> library to work with JSON in our program.</p>
<p>After installing the latest <mark>5.x</mark> version of the libary via the Library Manager (Tools > Manage Libraries...), add this include to the top:</p>
<pre><code class="cpp copy">#include <ArduinoJson.h></code></pre>
<p><small>⚠️ ArduinoJson 6.x is installed by default, but it is still in beta! ⚠️</small></p>
</section>
<section id="http-server">
<h2>HTTP server</h2>
<p>We will set up an <abbr title="Hypertext Transfer Protocol">HTTP</abbr> server to handle incoming requests to either query for the current state or change the current state.</p>
<p>First, we need to add a global variable for our HTTP server. You can put this under the "WiFi" section of global variables:</p>
<pre><code class="cpp copy">// HTTP server
#define HTTP_SERVER_PORT 80
ESP8266WebServer server(HTTP_SERVER_PORT);</code></pre>
</section>
<section id="http-server-setup">
<p>Next, add the following to the end of the <code>setup()</code> function, after the "WiFi" section:</p>
<pre><code class="cpp copy"> // HTTP server
server.on("/hello", HTTP_GET, []() {
server.send(200, "text/plain", "Hello, World!");
});
server.on("/light", HTTP_GET, []() {
server.send(200, "application/json", getStateJson());
});
server.on("/light", HTTP_PUT, []() {
if (!server.hasArg("plain")) {
server.send(400, "text/plain", "missing body on request");
return;
}
handleJsonPayload(server.arg("plain").c_str());
server.send(204);
});
server.begin();
Serial.printf("HTTP server started on port %d\n", HTTP_SERVER_PORT);</code></pre>
<p><small>Each <code>on()</code> call defines how incoming HTTP requests are handled.</small></p>
</section>
<section id="add-http-to-loop">
<p>We need to add this to the the main <code>loop()</code> function (can go at the beginning before the "LED" section):</p>
<pre><code class="cpp copy"> // HTTP
server.handleClient();</code></pre>
<p>This makes it so that on each pass of the loop, we handle any new incoming HTTP requests.</p>
</section>
<section id="get-state-json">
<p>Add the <code>getStateJson()</code> function before the <code>setup()</code> function:</p>
<pre><code class="cpp copy">/**
return a JSON string representation of the current state
*/
String getStateJson() {
// code partially generated using https://arduinojson.org/v5/assistant/
const size_t bufferSize = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(5);
DynamicJsonBuffer jsonBuffer(bufferSize);
JsonObject& root = jsonBuffer.createObject();
root["id"] = getIdentifier();
root["brightness"] = value;
root["state"] = state ? on_state : off_state;
root["effect"] = effect;
JsonObject& color = root.createNestedObject("color");
color["h"] = hue;
color["s"] = saturation;
String jsonString;
root.printTo(jsonString);
return jsonString;
}</code></pre>
</section>
<section id="handle-json-payload-temp">
<p>We'll come back to this... for now, let's just paste this function at the bottom so that it can compile while uploading:</p>
<pre><code class="cpp copy">void handleJsonPayload(const char* payload) {
// TODO actually handle the payload and not just print it out
Serial.println(payload);
}</code></pre>
</section>
<section id="curl-get">
<h2>HTTP Tests Using cURL</h2>
<p><a href="https://curl.haxx.se">curl</a> is a command line tool that can be used to make HTTP requests.</p>
<p>Connect to the same network as the microcontroller. Then, using the IP address printed out in the Serial Monitor, run the following in a Command Prompt/Terminal/shell:</p>
<pre><code class="shell copy">curl http://192.168.1.206/hello</code></pre>
<div class="fragment">
<p>You should see the following text:</p>
<pre><code class="shell">Hello, World!</code></pre>
</div>
</section>
<section id="curl-get2">
<p>Now let's hit a URL that gets the current state:</p>
<pre><code class="shell copy">curl http://192.168.1.206/light</code></pre>
<div class="fragment">
<p>You should see a JSON response which describes the current state variables.</p>
<pre><code class="shell">{"id":"LIGHT-ABC123","brightness":50,"state":"ON","effect":"none","color":{"h":180,"s":100}}</code></pre>
</div>
</section>
<section id="curl-put">
<p>Let's max out the brightness with a PUT request:</p>
<pre><code class="shell copy">curl -X PUT -H "Content-Type:application/json" -d "{\"brightness\":100}" http://192.168.1.206/light</code></pre>
<div class="fragment">
<p>You should see the JSON that was the "data" parameter printed out in the Serial Monitor...</p>
<pre class="serialmon">{"brightness":100}</pre>
</div>
<p class="fragment">Now that we have confirmed that <code>handleJsonPayload()</code> is working, let's actually implement it to affect our state variables.</p>
</section>
<section id="handle-json-payload">
<p>Replace the <code>handleJsonPayload()</code> function with the following:</p>
<pre><code class="cpp copy">/*
sets the hue state; an update will not be triggered until updateColor() is called
*/
void setHue(int h) {
hue = h % 360;
}
/*
sets the saturation state; an update will not be triggered until updateColor() is called
*/
void setSaturation(int s) {
saturation = constrain(s, 0, 100);
}
/*
sets the value state; an update will not be triggered until updateColor() is called
*/
void setBrightness(int brightness) {
value = constrain(brightness, 0, 100);
}
/**
Sets the toColor using a FastLED CHSV object based on the current color state, which requires converting traditional HSV scales to FastLED's 0-255
see: https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors#numeric-range-differences-everything-here-is-0-255
*/
void updateColor() {
setColor(CHSV(hue * 255 / 359, saturation * 255 / 100, value * 255 / 100));
}
/**
sets the toColor using a FastLED CHSV object; the fromColor is set to the currentColor
*/
void setColor(CHSV toChsv) {
Serial.printf("setting color to CHSV(%d,%d,%d)\n", toChsv.h, toChsv.s, toChsv.v);
currentColor = toChsv;
}
/**
returns true if the given effect string is non-empty, one of the valid effect values, and different from the current state's effect; false otherwise
*/
bool isValidAndDifferentEffect(const char *ef) {
return ef && (strcmp(ef, "none") == 0 || strcmp(ef, "colorloop") == 0 || strcmp(ef, "trail") == 0) && strcmp(ef, effect) != 0;
}
/**
Processes the given JSON string, making any state changes as necessary.
*/
void handleJsonPayload(const char* payload) {
// (somewhat) adheres to Home Assistant's MQTT JSON Light format (https://www.home-assistant.io/components/light.mqtt_json/)
// code partially generated using https://arduinojson.org/v5/assistant/
const size_t bufferSize = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4);
DynamicJsonBuffer jsonBuffer(bufferSize);
JsonObject& root = jsonBuffer.parseObject(payload);
// state
const char* stateJson = root["state"]; // "ON" or "OFF"
if (stateJson) {
if (!state && strcmp(stateJson, on_state) == 0) {
state = true;
} else if (state && strcmp(stateJson, off_state) == 0) {
state = false;
}
}
// effect
const char* effectJson = root["effect"];
if (isValidAndDifferentEffect(effectJson)) {
strcpy(effect, effectJson);
}
// color
bool colorChanged = false;
JsonVariant hueJson = root["color"]["h"]; // 0 to 359
if (hueJson.success() && hue != hueJson.as<int>()) {
setHue(hueJson.as<int>());
colorChanged = true;
}
JsonVariant saturationJson = root["color"]["s"]; // 0 to 100
if (saturationJson.success() && saturation != saturationJson.as<int>()) {
setSaturation(saturationJson.as<int>());
colorChanged = true;
}
JsonVariant brightness = root["brightness"]; // 0 to 100
if (brightness.success() && value != brightness.as<int>()) {
setBrightness(brightness.as<int>());
colorChanged = true;
}
if (colorChanged) {
updateColor();
}
}</code></pre>
</section>
<section id="curl-put2">
<h2>Max Brightness</h2>
<p>Upload the sketch and try that same max brightness PUT request again:</p>
<pre><code class="shell copy">curl -X PUT -H "Content-Type:application/json" -d "{\"brightness\":100}" http://192.168.1.206/light</code></pre>
<p class="fragment" style="font-size:100px;">😎</p>
</section>
<section id="curl-other">
<h2>Other Curl Commands To Try</h2>
<pre><code class="shell copy">curl -X PUT -H "Content-Type:application/json" -d "{\"state\":\"OFF\"}" http://192.168.1.206/light</code></pre>
<p><small><q>turn off the light, turn off the light</q> <span class="fragment">- Nelly Furtado</span></small></p>
<pre><code class="shell copy">curl -X PUT -H "Content-Type:application/json" -d "{\"state\":\"ON\"}" http://192.168.1.206/light</code></pre>
<p><small><q>just gimme the light</q> <span class="fragment">- Sean Paul</span></small></p>
<pre><code class="shell copy">curl -X PUT -H "Content-Type:application/json" -d "{\"color\":{\"h\":0,\"s\":100},\"brightness\":50}" http://192.168.1.206/light</code></pre>
<p><small><q>(Roxanne) put on the red light</q> <span class="fragment">- The Police</span></small></p>
<pre><code class="shell copy">curl -X PUT -H "Content-Type:application/json" -d "{\"color\":{\"s\":0},\"brightness\":50}" http://192.168.1.206/light</code></pre>
<p><small><q>all of the lights, all of the lights</q> <span class="fragment">- Ye</span></small></p>
</section>
<section id="webpage">
<h2>Time To Build A Webpage</h2>
<p>The <code>curl</code> command line tool has successfully demonstrated remote control of our LEDs. However, we probably want a more user-friendly way of interacting with our server using a browser.</p>
</section>
<section id="html-file">
<h2>Create an HTML File</h2>
<ol>
<li>Sketch > Show Sketch Folder</li>
<li>Create a new folder/directory named <code>data</code></li>
<li>Inside of <code>data</code>, create a new file named <code>index.html</code> with the following contents:<pre><code class="html copy"><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
Hello, World!
</body>
</html></code></pre></li>
</ol>
</section>
<section id="spiffs">
<h2>SPIFFS</h2>
<p><abbr title="Serial Peripheral Interface Flash File System">SPIFFS</abbr> is a file system that we can use to store <em>persistent</em> data. We will use it to store static web content (HTML, CSS, JS) to be served by the webserver.</p>
<p>It can also be used to store other data. For example, a JSON representation of the current state.</li>
</section>
<section id="esp8266fs-plugin">
<h2>Arduino ESP8266 filesystem uploader</h2>
<ul>
<li>follow the instructions at <a href="https://github.com/esp8266/arduino-esp8266fs-plugin" target="_blank">Arduino ESP8266 filesystem uploader's github page</a> to download and install the plugin</li>
<li>The D1 mini has a 4M flash chip and the file system can be configured to be 1M, 2M, or 3M. We will configure it to use 1M to allow more memory for the sketch, OTA, etc.: Tools > Flash Size > 4M (1M SPIFFS)</li>
</ul>
</section>
<section id="spiffs-upload">
<h2>Upload Data to SPIFFS</h2>
<ul>
<li>before uploading data, close the Serial Monitor window</li>
<li>Tools > ESP8266 Sketch Data Upload</li>
</ul>
<p class="stretch"><img data-src="images/sketch-data-upload.png"/></p>
</section>
<section id="serve-static">
<p>Changes to serve up the file on requests to "/":</p>
<ul>
<li>
<p>Add the following include:</p>
<pre><code class="cpp copy">#include <FS.h></code></pre>
</li>
<li>
<p>Add the following line in the <code>setup()</code> function before the calls to <code>server.on()</code>:</p>
<pre><code class="cpp copy"> server.serveStatic("/", SPIFFS, "/index.html"); // handle root requests</code></pre>
</li>
<li>
<p>Add the following to the beginning of the <code>setup()</code> function:</p>
<pre><code class="cpp copy"> SPIFFS.begin();</code></pre>
</li>
</ul>
</section>
<section id="test-html">
<p>After uploading, you should now be able to view the webpage on a browser:</p>
<img data-src="images/helloworld-webpage.png"/>
</section>
<section id="real-html">
<p>Now let's modify <code>index.html</code> to have something more useful and re-upload (Tools > ESP8266 Sketch Data Upload):</p>
<pre><code class="html copy"><!DOCTYPE html>
<html lange="en">
<head>
<meta charset="utf-8">
<title></title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<style>
input[type="range"] {
-webkit-appearance: none;
border-radius: .25rem;
padding: 0.375rem 0;
}
input[type=range]::-moz-range-track {
background: transparent;
}
</style>
</head>
<body>
<!-- header -->
<header>
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
<span class="navbar-brand"></span>
</nav>
</header>
<!-- content -->
<div role="main" class="container mt-3">
<form>
<div class="form-group form-inline">
<label class="col-sm-1 col-form-label"><i class="fas fa-power-off" aria-hidden title="Power"></i><span class="sr-only">Power:</span></label>
<div class="col-sm-11">
<div class="btn-group btn-group-toggle d-flex" data-toggle="buttons">
<label class="btn btn-primary w-100">
<input type="radio" name="state" autocomplete="off" value="ON">ON
</label>
<label class="btn btn-primary w-100">
<input type="radio" name="state" autocomplete="off" value="OFF">OFF
</label>
</div>
</div>
</div>
<div class="form-group form-inline">
<label for="valrange" class="col-sm-1 col-form-label"><i class="fas fa-sun" aria-hidden title="Brightness"></i><span class="sr-only">Brightness:</span></label>
<div class="col-sm-11">
<input type="range" min="0" max="100" value="50" class="form-control-range" id="valrange" title="brightness">
</div>
</div>
<div class="form-group form-inline">
<label for="huerange" class="col-sm-1 col-form-label"><i class="fas fa-palette" aria-hidden title="Color"></i><span class="sr-only">Color:</span></label>
<div class="col-sm-11">
<input type="range" min="0" max="359" value="180" class="form-control-range" id="huerange" title="hue">
<input type="range" min="0" max="100" value="100" class="form-control-range mt-2" id="satrange" title="saturation">
</div>
</div>
<div class="form-group form-inline">
<label class="col-sm-1 col-form-label"><i class="fas fa-magic" aria-hidden title="Effect"></i><span class="sr-only">Effect:</span></label>
<div class="col-sm-11">
<div class="btn-group btn-group-toggle d-flex" data-toggle="buttons">
<label class="btn btn-primary w-100">
<input type="radio" name="effect" autocomplete="off" value="none">none
</label>
<label class="btn btn-primary w-100">
<input type="radio" name="effect" autocomplete="off" value="colorloop">colorloop
</label>
<label class="btn btn-primary w-100">
<input type="radio" name="effect" autocomplete="off" value="trail">trail
</label>
</div>
</div>
</div>
</form>
</div>
<!-- js -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<script>
// https://codeburst.io/throttling-and-debouncing-in-javascript-b01cad5c8edf
const throttle = (func, limit) => {
let lastFunc
let lastRan
return function() {
const context = this
const args = arguments
if (!lastRan) {
func.apply(context, args)
lastRan = Date.now()
} else {
clearTimeout(lastFunc)
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args)
lastRan = Date.now()
}
}, limit - (Date.now() - lastRan))
}
}
}
var huerange = document.getElementById("huerange");
var satrange = document.getElementById("satrange");
var valrange = document.getElementById("valrange");
function updateBackground() {
var hue = huerange.value;
satrange.style.background = "linear-gradient(to right,#FFF,hsl(" + hue + ",100%,50%))";
var sat = satrange.value;
valrange.style.background = "linear-gradient(to right,#000,hsl(" + hue + ",100%," + (100 - sat/2) + "%))";
}
function changeColor() {
sendState({
"brightness" : valrange.value,
"color" : {
"h": huerange.value,
"s" : satrange.value
}
});
}
huerange.addEventListener("input", updateBackground);
satrange.addEventListener("input", updateBackground);
var throttledChangeColor = throttle(changeColor, 500);
huerange.addEventListener("input", throttledChangeColor);
satrange.addEventListener("input", throttledChangeColor);
valrange.addEventListener("input", throttledChangeColor);
var huerangeBackground = "linear-gradient(to right";
for (var i =0; i <= 359; i++) {
huerangeBackground += ",hsl(" + i + ",100%,50%)";
}
huerangeBackground += ")";
huerange.style.background = huerangeBackground;
updateBackground();
$('input[type=radio][name=state]').change(function() {
sendState({"state":this.value});
});
$('input[type=radio][name=effect]').change(function() {
sendState({"effect":this.value});
});
function updateRadio(name, value) {
var inputs = document.getElementsByName(name);
for (let input of inputs) {
if (input.value === value) {
input.checked = true;
input.parentElement.classList.add("active");
} else {
input.parentElement.classList.remove("active");
}
}
}
function handleStateObject(stateObj) {
console.log(stateObj);
$('.navbar-brand').text(stateObj.id);
$('title').text(stateObj.id);
updateRadio("state", stateObj.state);
updateRadio("effect", stateObj.effect);
var color = stateObj.color;