├── .gitattributes
├── .gitignore
├── ACDimmer
└── ACDimmer.ino
├── APA102x
├── APA102xLoadTester
│ └── APA102xLoadTester.ino
├── APA102xRGBFade
│ └── APA102xRGBFade.ino
├── APA102xRGBFade002
│ └── APA102xRGBFade002.ino
├── APA102xTester
│ └── APA102xTester.ino
├── APA102x_HSV_Tester
│ └── APA102x_HSV_Tester.ino
└── APA102x_SerialToColorConverter
│ └── APA102x_SerialToColorConverter.ino
├── Candles
├── 6-channel-LED_candle
│ └── 6-channel-LED_candle.ino
├── APA102xCandle
│ └── APA102xCandle.ino
├── NeoPixelCandle0002
│ └── NeoPixelCandle0002.ino
├── WS281xCandle
│ └── WS281xCandle.ino
└── WS281xColorConverterCandle
│ └── WS281xColorConverterCandle.ino
├── ColorTempFade
└── ColorTempFade.ino
├── ColorTempLamp001
└── ColorTempLamp001.ino
├── FadeCurves
├── CIE1931Fade
│ └── CIE1931Fade.ino
├── CIE1931FadeWithInput
│ └── CIE1931FadeWithInput.ino
├── CIE1931FadeWithKalmanInput
│ └── CIE1931FadeWithKalmanInput.ino
├── ExponentialFade
│ └── ExponentialFade.ino
├── SimpleFade
│ └── SimpleFade.ino
├── SimpleFadeWithInput
│ └── SimpleFadeWithInput.ino
├── SineFade
│ └── SineFade.ino
├── SineFadeWithInput
│ └── SineFadeWithInput.ino
└── XSquaredFade
│ └── XSquaredFade.ino
├── HueSkyLight
├── HueSkyLight.ino
├── arduino_secrets.h
└── readme.md
├── LICENSE
├── LightSensorProjects
├── LightSensorChart
│ ├── chart.js
│ ├── credentials.js
│ └── index.html
├── LightSensorClient
│ └── LightSensorClient.ino
└── readme.md
├── PatternMakers
├── ProcessingCirclesVectorExport
│ └── ProcessingCirclesVectorExport.pde
├── ProcessingVectorExport
│ └── ProcessingVectorExport.pde
├── p5CircleMaker
│ ├── index.html
│ └── sketch.js
└── p5HexagonMaker
│ ├── index.html
│ └── sketch.js
├── PulseLamp
└── PulseLamp.ino
├── README.md
├── SqueezeBulbLamp
├── SqueezeBulb_Lamp_circuit.fzz
├── SqueezeBulb_Lamp_circuit_bb.png
├── SqueezeBulb_Lamp_circuit_bb.svg
├── SqueezeBulb_Lamp_circuit_schem.png
├── SqueezeBulb_Lamp_circuit_schem.svg
├── SqueezeLamp001
│ └── SqueezeLamp001.ino
└── readme.md
├── StripControls
└── 555LEDFader.fzz
├── TelephoneDimmer
├── RotaryDial0002
│ └── RotaryDial0002.ino
├── RotaryDialDimmer0003
│ └── RotaryDialDimmer0003.ino
├── SleepTest
│ └── SleepTest.ino
└── dialtone
│ └── dialtone.ino
├── WS281x
├── WS281HSVColorDemo
│ └── WS281HSVColorDemo.ino
├── WS281HSVColorDemoSerial
│ └── WS281HSVColorDemoSerial.ino
├── WS281xColorOrderTester
│ └── WS281xColorOrderTester.ino
├── WS281xLoadTester
│ └── WS281xLoadTester.ino
├── WS281xOneColor
│ └── WS281xOneColor.ino
├── WS281xRGBFade
│ └── WS281xRGBFade.ino
├── WS281xTester
│ └── WS281xTester.ino
├── WS281x_HSV_Tester
│ └── WS281x_HSV_Tester.ino
├── WWA_WS281x_AdvancedMixer
│ └── WWA_WS281x_AdvancedMixer.ino
├── WWA_WS281x_Mixer
│ └── WWA_WS281x_Mixer.ino
└── readme.md
├── WarmCoolLEDStrip
└── WarmCoolLEDStrip.ino
├── _config.yml
├── _includes
└── nav.html
├── _layouts
└── default.html
├── addressable-leds.md
├── candles.md
├── chromaticity.md
├── color-spaces-color-temp.md
├── dream-machine
└── dreamMachine001
│ └── dreamMachine001.ino
├── fading.md
├── img
├── 12VDC_LED_MOSFET_circuit_bb.png
├── 12VDC_LED_MOSFET_lamp_circuit_bb.png
├── 12VDC_LED_TIP120_Nano_lamp_circuit_bb.png
├── 12VDC_LED_TIP120_circuit_bb.png
├── 12VDC_LED_TIP120_lamp_circuit_bb.png
├── 12VDC_Nano_LED_MOSFET_lamp_circuit_bb.png
├── 212069_Fig_07.jpg
├── APA102C_bb.png
├── AS7341_fritzing_bb.png
├── AS7341_fritzing_schem.png
├── CIExy1931.svg
├── MKR_MOSFET.fzz
├── SimpleFadeGraph.png
├── SineFadeGraph.png
├── WS281x_bb.png
├── arduino-ide-boards-manager.jpg
├── arduino-ide-boards-manager.png
├── arduino-ide-examples-menu.png
├── arduino-ide.png
├── attiny85_mosfet.fzz
├── attiny85_mosfet_bb.png
├── candles
│ ├── IDE-20-board-port-menu.png
│ ├── LED_ring.jpg
│ ├── LabTemplateNanoShort_bb.png
│ ├── LabTemplate_bb.png
│ ├── MKRZero-Neopixel-1.png
│ ├── MKRZero-e1530487647686.jpg
│ ├── MKRZero_breadboard-177x300.jpg
│ ├── MKR_bb.png
│ ├── Tools_menu-1024x450.png
│ ├── Uno_w_LED_ring_bb.png
│ ├── arduino-nano-33-iot.jpg
│ ├── breadboard_short-e1532116106284-150x150-1.jpeg
│ ├── hookup_wires.jpg
│ ├── leds1-300x200.jpg
│ ├── microUSB.jpg
│ ├── nano-neopixel-ring_bb.png
│ └── upload_button.png
├── chartjs_spectrometer.png
├── cie1931Graph.png
├── exponentialCurveGraph.png
├── fading-chromaticity.png
├── fritzing_files
│ ├── 12VDC_ATTiny_LED_MOSFET_lamp_circuit_bb.afdesign
│ ├── 12VDC_ATTiny_LED_MOSFET_lamp_circuit_bb.svg
│ ├── 12VDC_LED_MOSFET_circuit.fzz
│ ├── 12VDC_LED_MOSFET_circuit_bb.svg
│ ├── 12VDC_LED_MOSFET_lamp_circuit_bb.svg
│ ├── 12VDC_LED_MOSFET_potentiometer_circuit.fzz
│ ├── 12VDC_LED_MOSFET_switch_circuit.fzz
│ ├── 12VDC_LED_MOSFET_switch_circuit_2.fzz
│ ├── 12VDC_LED_TIP120_Nano_lamp_circuit_bb.svg
│ ├── 12VDC_LED_TIP120_circuit.fzz
│ ├── 12VDC_LED_TIP120_circuit_bb.svg
│ ├── 12VDC_LED_TIP120_lamp_circuit_bb.svg
│ ├── 12VDC_Nano_LED_MOSFET_lamp_circuit_bb.afdesign
│ ├── 12VDC_Nano_LED_MOSFET_lamp_circuit_bb.svg
│ ├── 555_mosfet_bb.fzz
│ ├── APA102C_bb.svg
│ ├── LED_Ring.svg
│ ├── LabTemplateNano-ring_bb.svg
│ ├── Uno_w_LED_ring_bb.svg
│ ├── WS281x_bb.svg
│ ├── addressable_strips_bb.svg
│ ├── attiny85_mosfet_bb.svg
│ ├── mkr1000-rotary-phone_bb.svg
│ ├── nano33_light-sensor_bb.svg
│ ├── nano33_light-sensor_schem.svg
│ └── rotary-dialer.svg
├── front-terminal-block.jpg
├── itp-arch-red.jpg
├── itp-arch-sky.jpg
├── led-strip-5-way-control.png
├── mkr1000-inside-phone.jpg
├── mkr1000-rotary-phone_bb.png
├── nano33-light-sensor-east.jpg
├── nano33-light-sensor-west.jpg
├── nano33_light-sensor_bb.png
├── nano33_light-sensor_schem.png
├── rotary-dial-back.jpg
├── rotary-phone-mounted.jpg
├── rotary_phone.jpg
├── rotarydImmer
│ └── nano33-inside-phone.jpg
├── side-terminal-block.jpg
├── sputnik.jpg
└── squareLawGraph.png
├── install-arduino-ide.md
├── inventory.md
├── led-lamps.md
├── led-strips.md
├── light-rendering-indices.md
├── patternMakers.md
├── qrcode.js
├── shopping.md
├── sky-lights.md
├── spectrometers
├── AS7341
│ ├── AS7341_Spectrometer_BLE
│ │ └── AS7341_Spectrometer_BLE.ino
│ ├── AS7341_Spectrometer_CCT_BLE
│ │ └── AS7341_Spectrometer_CCT_BLE.ino
│ ├── AS7341_Spectrometer_JSON_CCT
│ │ └── AS7341_Spectrometer_JSON_CCT.ino
│ ├── AS7341_Spectrometer_MQTT
│ │ ├── AS7341_Spectrometer_MQTT.ino
│ │ └── arduino_secrets.h
│ ├── AS7341_Spectrometer_MQTT_JSON
│ │ └── AS7341_Spectrometer_MQTT_JSON.ino
│ ├── AS7341_Spectrometer_serial
│ │ └── AS7341_Spectrometer_serial.ino
│ ├── AS7341_fritzing.fzz
│ ├── AS7341_fritzing_bb.svg
│ ├── AS7341_fritzing_schem.svg
│ ├── MatrixCalculationTest
│ │ └── MatrixCalculationTest.ino
│ ├── spectrograph_chartjs_ble
│ │ ├── index.html
│ │ └── sketch.js
│ ├── spectrograph_chartjs_mqttjs
│ │ ├── index.html
│ │ └── script.js
│ ├── spectrograph_chartjs_p5.webserial
│ │ ├── index.html
│ │ └── sketch.js
│ ├── spectrograph_chartjs_serial
│ │ ├── index.html
│ │ └── sketch.js
│ └── spectrograph_chartjs_web-serial
│ │ ├── index.html
│ │ └── sketch.js
├── SPD_001_02°_5791K.csv
├── clean_data.csv
├── melanopic-edi-readings.md
└── readme.md
└── telephone-dimmer.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *arduino_secrets.h
2 | .DS_Store
3 | .vscode/ipch/ea1480183a023fd0/mmap_address.bin
4 | TelephoneDimmer/RotaryDialDimmer0003/arduino_secrets.h
5 | .vscode/*
6 | docs/todo.md
7 | LightSensorProjects/LightSensorClient/arduino_secrets.h
8 | LightSensorProjects/LightSensorChart/credentials.js
9 | LightSensorProjects/LightSensorChart/credentials.js
10 | LightSensorProjects/LightSensorChart/credentials.js
11 | LightSensorProjects/LightSensorChart/credentials.js
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ACDimmer/ACDimmer.ino:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | AC phase controlled dimming
4 |
5 | based on
6 | // https://playground.arduino.cc/Main/ACPhaseControl
7 | // http://alfadex.com/2014/02/dimming-230v-ac-with-arduino-2/
8 |
9 | */
10 |
11 | const int triacPin = 4;
12 | const int zeroCrossPin = 5;
13 | const int timingFactor = 65;
14 | const int firingTime = 12;
15 | volatile int fadeLevel = 0;
16 |
17 | void setup() {
18 | Serial.begin(9600);
19 | pinMode(triacPin, OUTPUT);
20 | attachInterrupt(digitalPinToInterrupt(zeroCrossPin), zeroCross, RISING);
21 | }
22 |
23 | void loop() {
24 | int sensorReading = analogRead(A0);
25 | fadeLevel = sensorReading / 8;
26 | Serial.println(fadeLevel);
27 | }
28 |
29 | /*
30 | Generally,
31 | offTime = fadeLevel/fadeRange * (halfCycle - firingTime)
32 | For 60Hz,
33 | halfCycle = 1/120 sec = 8.33 ms = 8333 microseconds
34 | For the triac, firingTime = 10 microseconds, so:
35 | offTime = fadeLevel/128 * (8333 - 10)
36 | offTime = fadeLevel * (8323/128)
37 | offTime = fadeLevel * 65
38 | let's call 65 the timingFactor
39 | note that the light goes off above zero, and it's dependent on the light type
40 | e.g. incandescents fade differently than LEDs.
41 | And this formula is for offTime, not onTime.
42 | So to get the fade right, you need to work out the subset of the range,
43 | e.g. 7 - 120, that's actually operable. TBD.
44 | */
45 |
46 | void zeroCross() {
47 | // if the light should be off, don't fire the triac, just return:
48 | if (fadeLevel == 0) return;
49 | // if the triac should be on, delay for the offTime:
50 | int offTime = timingFactor * (128-fadeLevel);
51 | delayMicroseconds(offTime);
52 | // then fire the triac:
53 | digitalWrite(triacPin, HIGH);
54 | // then delay for the firingTime:
55 | delayMicroseconds(firingTime);
56 | // then turn the triac off:
57 | digitalWrite(triacPin, LOW);
58 | }
59 |
--------------------------------------------------------------------------------
/APA102x/APA102xLoadTester/APA102xLoadTester.ino:
--------------------------------------------------------------------------------
1 | /*
2 | APA102x loadTest with Adafruit_DotStar library
3 |
4 | This sketch loops through all the pixels of a APA102x-compatible device
5 | one pixel at a time, adding color cumulatively, in the order:
6 | blue, green, red
7 |
8 | If your power supply is not sufficient to control all your LEDs,
9 | then this sketch will fail when the power supply reaches its maximum current.
10 |
11 | Change pixelCount to the number of LEDs in your string.
12 |
13 | Uses Adafruit's DotStar library: https://github.com/adafruit/Adafruit_DotStar
14 |
15 | created 17 Jun 2019
16 | by Tom Igoe
17 | */
18 | #include
19 |
20 | const int pixelCount = 12; // number of pixels
21 | // if you're using software SPI, you need these pins:
22 | const int dataPin = 8; // data pin
23 | const int clockPin = 9; // clock pin
24 |
25 | // set up strip:
26 | //Adafruit_DotStar strip(pixelCount, dataPin, clockPin, DOTSTAR_BRG);
27 | // if you're using hardware SPI, use the built-in SPI CLK and MOSI pins
28 | // for clock and data:
29 | Adafruit_DotStar strip(pixelCount, DOTSTAR_BRG);
30 |
31 | unsigned long color = 0xFF; // start with blue
32 |
33 | void setup() {
34 | strip.begin(); // initialize pixel strip
35 | strip.clear(); // turn all LEDs off
36 | strip.show(); // refresh strip
37 | }
38 |
39 | void loop() {
40 | // loop over all the pixels:
41 | for (int pixel = 0; pixel < pixelCount; pixel++) {
42 | strip.setPixelColor(pixel, color); // set the color for this pixel
43 | strip.show(); // refresh the strip
44 | delay(500);
45 | }
46 |
47 | if (color >= 0xFFFFFF) { // if the color is greater than white (0xFF000000)
48 | color = 0xFF; // then set it back to blue
49 | } else {
50 | color = (color << 8) + 0xFF; // add the next color at full
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/APA102x/APA102xRGBFade/APA102xRGBFade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | APA102x RGB fade with Adafruit_DotStar library
3 |
4 | This sketch fades a string of APA102x LEDs by simply counting
5 | down from 0xFFFFFF (white) to 0x000000 (off). It makes no consideration
6 | for the different colors.
7 |
8 | Change pixelCount to the number of LEDs in your string.
9 |
10 | Uses Adafruit's DotStar library: https://github.com/adafruit/Adafruit_DotStar
11 |
12 | created 17 Jun 2019
13 | by Tom Igoe
14 | */
15 | #include
16 |
17 | const int pixelCount = 4; // number of pixels
18 | // you need these only if you're using sotware SPI:
19 | const int dataPin = 8; // data pin
20 | const int clockPin = 9; // clock pin
21 |
22 | // set up strip:
23 | //Adafruit_DotStar strip(pixelCount, dataPin, clockPin, DOTSTAR_BRG);
24 | Adafruit_DotStar strip(pixelCount, DOTSTAR_BGR);
25 | unsigned long color = 0xFFFFFF;
26 | void setup() {
27 | strip.begin(); // initialize pixel strip
28 | strip.clear(); // turn all LEDs off
29 | strip.show(); // refresh strip
30 | }
31 |
32 | void loop() {
33 | // loop over all the pixels:
34 | for (int pixel = 0; pixel < pixelCount; pixel++) {
35 | // set the color for each pixel:
36 | strip.setPixelColor(pixel, color);
37 | }
38 | // refresh the strip:
39 | strip.show();
40 | // decrement the color:
41 | color--;
42 | // if it reaches 0, set it back to 0xFFFFFF:
43 | if (color == 0) color = 0xFFFFFF;
44 | }
45 |
--------------------------------------------------------------------------------
/APA102x/APA102xRGBFade002/APA102xRGBFade002.ino:
--------------------------------------------------------------------------------
1 | /*
2 | APA102x RGB fade with Adafruit_DotStar library
3 |
4 | This sketch fades a string of APA102x LEDs by simply counting
5 | down from 0xFFFFFF (white) to 0x000000 (off). It fades
6 | all three colors at the same time.
7 |
8 | Change pixelCount to the number of LEDs in your string.
9 |
10 | Uses Adafruit's DotStar library: https://github.com/adafruit/Adafruit_DotStar
11 |
12 | created 17 Jun 2019
13 | by Tom Igoe
14 | */
15 | #include
16 |
17 | const int pixelCount = 4; // number of pixels
18 | // you need these only if you're using sotware SPI:
19 | const int dataPin = 8; // data pin
20 | const int clockPin = 9; // clock pin
21 |
22 | // set up strip:
23 | //Adafruit_DotStar strip(pixelCount, dataPin, clockPin, DOTSTAR_BRG);
24 | Adafruit_DotStar strip(pixelCount, DOTSTAR_BGR);
25 | unsigned long color = 0xFF;
26 | void setup() {
27 | strip.begin(); // initialize pixel strip
28 | strip.clear(); // turn all LEDs off
29 | strip.show(); // refresh strip
30 | }
31 |
32 | void loop() {
33 | // loop over all the pixels:
34 | for (int pixel = 0; pixel < pixelCount; pixel++) {
35 | // set the color for each pixel:
36 | strip.setPixelColor(pixel, color, color, color);
37 | }
38 | // refresh the strip:
39 | strip.show();
40 | // decrement the color:
41 | color--;
42 | // if it reaches 0, set it back to 0xFFFFFF:
43 | if (color == 0) color = 0xFF;
44 | delay(10);
45 | }
46 |
--------------------------------------------------------------------------------
/APA102x/APA102xTester/APA102xTester.ino:
--------------------------------------------------------------------------------
1 | /*
2 | APA102x Test with Adafruit_DotStar library
3 |
4 | This sketch loops through all the pixels of a APA102x-compatible device
5 | one pixel at a time, and one color at a time, in the order:
6 | blue, green, red
7 |
8 | Change pixelCount to the number of LEDs in your string.
9 |
10 | Uses Adafruit's DotStar library: https://github.com/adafruit/Adafruit_DotStar
11 |
12 | created 31 Jan 2017
13 | modified 17 Jun 2019
14 | by Tom Igoe
15 | */
16 | #include
17 |
18 | const int pixelCount = 4; // number of pixels
19 | // you need these only if you're using sotware SPI:
20 | const int dataPin = 8; // data pin
21 | const int clockPin = 9; // clock pin
22 |
23 | // set up strip:
24 | //Adafruit_DotStar strip(pixelCount, dataPin, clockPin, DOTSTAR_BRG);
25 | Adafruit_DotStar strip(pixelCount, DOTSTAR_BGR);
26 | unsigned long color = 0xFF; // start with blue
27 |
28 | void setup() {
29 | strip.begin(); // initialize pixel strip
30 | strip.clear(); // turn all LEDs off
31 | strip.show(); // refresh strip
32 | }
33 |
34 | void loop() {
35 | // loop over all the pixels:
36 | for (int pixel = 0; pixel < pixelCount; pixel++) {
37 | strip.setPixelColor(pixel, color); // set the color for this pixel
38 | if (pixel > 0) {
39 | strip.setPixelColor(pixel - 1, 0); // turn off the last pixel
40 | }
41 | strip.show(); // refresh the strip
42 | delay(500);
43 | }
44 |
45 | if (color >= 0xFF0000) { // if the color is greater than red (0xFF0000)
46 | color = 0xFF; // then set it back to blue
47 | } else {
48 | color = color << 8; // shift the FF (255) to the next color
49 | }
50 | strip.clear(); // clear the strip at the end of a color
51 | }
52 |
--------------------------------------------------------------------------------
/APA102x/APA102x_HSV_Tester/APA102x_HSV_Tester.ino:
--------------------------------------------------------------------------------
1 | /*
2 | APA102x HSV Test with Adafruit_DotStar library
3 |
4 | This sketch tests the HSV functionality of the Adafruit_DotStar library
5 |
6 | Change pixelCount to the number of LEDs in your string.
7 |
8 | Uses Adafruit's DotStar library: https://github.com/adafruit/Adafruit_DotStar
9 |
10 | created 31 Jan 2017
11 | modified 17 Jun 2019
12 | by Tom Igoe
13 | */
14 | #include
15 |
16 | const int pixelCount = 4; // number of pixels
17 | // you need these only if you're using sotware SPI:
18 | const int dataPin = 8; // data pin
19 | const int clockPin = 9; // clock pin
20 |
21 | // set up strip:
22 | //Adafruit_DotStar strip(pixelCount, dataPin, clockPin, DOTSTAR_BRG);
23 | Adafruit_DotStar strip(pixelCount, DOTSTAR_BGR);
24 | unsigned int hue = 0;
25 | int sat = 255;
26 | int intensity = 255;
27 |
28 | void setup() {
29 | Serial.begin(9600);
30 | strip.begin(); // initialize pixel strip
31 | strip.clear(); // turn all LEDs off
32 | strip.show(); // refresh strip
33 | }
34 |
35 | void loop() {
36 | // increment hue and rollover at 65535:
37 | hue++;
38 | hue %= 65536;
39 | // loop over all the pixels:
40 | for (int pixel = 0; pixel < pixelCount; pixel++) {
41 | // get RGB from HSV:
42 | unsigned long color = strip.ColorHSV(hue, sat, intensity);
43 | // do a gamma correction:
44 | unsigned long correctedColor = strip.gamma32(color);
45 | strip.setPixelColor(pixel, correctedColor); // set the color for this pixel
46 | Serial.println(hue);
47 | }
48 | strip.show(); // refresh the strip
49 | }
50 |
--------------------------------------------------------------------------------
/APA102x/APA102x_SerialToColorConverter/APA102x_SerialToColorConverter.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Serial to color converter for APA102x LEDs
3 |
4 | This sketch lets you test the color converter by setting a string of
5 | APA102/APA102C LEDs. To do so, open the serial monitor
6 | and send one of these strings:
7 |
8 | h xx xx xx\n
9 | A hue, saturation, intensity string that will return the corresponding RGB values
10 | hue: 0-360, saturation: 0-100, intensity: 0-100
11 |
12 | r h xx xx xx\n
13 | A red, green, blue string that will return the corresponding HSI values
14 | red: 0-255, green: 0-255, blue: 0-255
15 |
16 | created 26 June 2018
17 | modified 17 Jun 2019
18 | by Tom Igoe
19 | */
20 |
21 | //#include
22 | #include
23 |
24 | const int pixelCount = 4; // number of pixels
25 |
26 | // you need these only if you're doing software SPI:
27 | const int dataPin = 8; // data pin
28 | const int clockPin = 9; // clock pin
29 |
30 | // set up strip:
31 | //Adafruit_DotStar strip(pixelCount, dataPin, clockPin, DOTSTAR_BRG);
32 | Adafruit_DotStar strip(pixelCount, DOTSTAR_BGR);
33 |
34 | //ColorConverter converter;
35 | void setup() {
36 | Serial.begin(9600); // initialize serial communication
37 | while (!Serial); // wait until serial monitor is opened
38 | Serial.setTimeout(10); // set timeout for parseInt() to 10 ms
39 | strip.begin(); // initialize pixel strip
40 | strip.clear(); // turn all LEDs off
41 | strip.show(); // refresh strip
42 | }
43 |
44 | void loop() {
45 | if (Serial.available()) { // if there's any serial data,
46 | char c = Serial.read(); // read one byte
47 |
48 | if (c == 'h') { // if byte was h:
49 | int h = Serial.parseInt(); // read ASCII numeric strings
50 | int s = Serial.parseInt();
51 | int i = Serial.parseInt();
52 | long thisColor = strip.ColorHSV(h, s, i);
53 | // do the conversion:
54 | // RGBColor color = converter.HSItoRGB(h, s, i);
55 | // // print the results:
56 | // Serial.print(color.red);
57 | // Serial.print(" ");
58 | // Serial.print(color.green);
59 | // Serial.print(" ");
60 | // Serial.println(color.blue);
61 | for (int pixel = 0; pixel < pixelCount; pixel++) {
62 | strip.setPixelColor(pixel, thisColor); // set the color for this pixel
63 | }
64 | }
65 | if (c == 'r') { // if byte was r:
66 | float r = Serial.parseInt(); // read ASCII numeric strings
67 | float g = Serial.parseInt();
68 | float b = Serial.parseInt();
69 | // do the conversion:
70 | HSIColor color = converter.RGBtoHSI(r, g, b);
71 | // print the results:
72 | Serial.print(color.hue);
73 | Serial.print(" ");
74 | Serial.print(color.saturation);
75 | Serial.print(" ");
76 | Serial.println(color.intensity);
77 | for (int pixel = 0; pixel < pixelCount; pixel++) {
78 | strip.setPixelColor(pixel, r, g, b); // set the color for this pixel
79 | }
80 | }
81 | Serial.println(); // add a blank line
82 | strip.show(); // refresh the strip
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Candles/6-channel-LED_candle/6-channel-LED_candle.ino:
--------------------------------------------------------------------------------
1 | by Tom Igoe
2 |
--------------------------------------------------------------------------------
/Candles/APA102xCandle/APA102xCandle.ino:
--------------------------------------------------------------------------------
1 | /*
2 | APA102x candle using Adafruit_DotStar library
3 |
4 | This sketch creates a candle effect by randomizing
5 | hue, sat, and intensity
6 |
7 | Change pixelCount to the number of LEDs in your string.
8 |
9 | Uses Adafruit's DotStar library: https://github.com/adafruit/Adafruit_DotStar
10 |
11 | created 17 Jun 2019
12 | by Tom Igoe
13 | */
14 | #include
15 |
16 | const int pixelCount = 4; // number of pixels
17 | // you need these only if you're using sotware SPI:
18 | const int dataPin = 8; // data pin
19 | const int clockPin = 9; // clock pin
20 |
21 | // set up strip:
22 | //Adafruit_DotStar strip(pixelCount, dataPin, clockPin, DOTSTAR_BRG);
23 | Adafruit_DotStar candle(pixelCount, DOTSTAR_BGR);
24 | unsigned int hue = 3000;
25 | int sat = 127;
26 | int intensity = 127;
27 |
28 | void setup() {
29 | candle.begin(); // initialize pixel strip
30 | candle.clear(); // turn all LEDs off
31 | candle.show(); // update strip
32 | }
33 |
34 | void loop() {
35 | for (int p = 0; p < pixelCount; p++) {
36 | // change hue -1 to 2 points:
37 | int hueChange = random(4) - 1;
38 |
39 | hue += hueChange;
40 | // constrain to red to orange:
41 | hue = constrain(hue, 0, 6000);
42 |
43 | // change saturation -1 to 2 points:
44 | int satChange = random(4) - 1;
45 | sat += satChange;
46 | sat = constrain(sat, 0, 255);
47 |
48 | // change intensity -1 to 1 points:
49 | int intensityChange = random(3) - 1;
50 | intensity += intensityChange;
51 | intensity = constrain(intensity, 0, 255);
52 |
53 | // get RGB from HSV:
54 | unsigned long color = candle.ColorHSV(hue, sat, intensity);
55 | // do a gamma correction:
56 | unsigned long correctedColor = candle.gamma32(color);
57 | candle.setPixelColor(p, correctedColor);
58 | }
59 |
60 | candle.show();
61 | delay(30);
62 | }
63 |
--------------------------------------------------------------------------------
/Candles/NeoPixelCandle0002/NeoPixelCandle0002.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Candle example
3 | Runs only on SAMD boards (Nano 33 IoT, MKR series)
4 | uses Adafruit's NeoPixel library and the ColorHSV function therein
5 | Also uses Scheduler library, which runs only on SAMD boards.
6 |
7 | created 6 Jun 2020
8 | modified 6 Feb 2023
9 | by Tom Igoe
10 | */
11 |
12 | #include
13 | #include
14 |
15 | const int neoPixelPin = 5; // control pin
16 | const int pixelCount = 7; // number of pixels
17 |
18 | // set up strip:
19 | Adafruit_NeoPixel candle = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
20 |
21 | int hues[pixelCount];
22 | int saturations[pixelCount];
23 | int intensities[pixelCount];
24 | int changeValues[] = {1, 1, 1, 1, 1, 1, 1};
25 |
26 | int highPixels[] = {6, 3};
27 | int lowPixels[] = {1, 4};
28 | int bluePixel = 0;
29 | int lightPixels[] = {2, 5};
30 |
31 |
32 | void setup() {
33 | Serial.begin(9600);
34 |
35 | candle.begin(); // initialize pixel strip
36 | candle.clear(); // turn all LEDs off
37 | candle.show(); // update strip
38 |
39 | // set all initial hues, sats, intensities, and colorConverters
40 | for (int p = 0; p < 2; p++) {
41 | int thisPixel = highPixels[p];
42 | // between 1200 - 2400:
43 | hues[thisPixel] = random(1200) + 1200;
44 | // 240 - 250:
45 | saturations[thisPixel] = random(10) + 240;
46 | // 200 - 220:
47 | intensities[thisPixel] = random(20) + 200;
48 | }
49 |
50 | for (int p = 0; p < 2; p++) {
51 | int thisPixel = lowPixels[p];
52 | hues[thisPixel] = random(800) + 300;
53 | saturations[thisPixel] = 255;
54 | intensities[thisPixel] = random(20) + 100;
55 | }
56 |
57 | for (int p = 0; p < 2; p++) {
58 | int thisPixel = lightPixels[p];
59 | hues[thisPixel] = random(1500) + 800;
60 | saturations[thisPixel] = random(20) + 220;
61 | intensities[thisPixel] = random(40) + 110;
62 | }
63 |
64 |
65 | hues[bluePixel] = random(200) + 30000;
66 | saturations[bluePixel] = random(10) + 230;
67 | intensities[bluePixel] = random(20) + 30;
68 |
69 | // set up some loops for timing:
70 | Scheduler.startLoop(fastLoop);
71 | Scheduler.startLoop(medLoop);
72 | Scheduler.startLoop(slowLoop);
73 | }
74 |
75 | void loop() {
76 | for (int p = 0; p < 2; p++) {
77 | int thisPixel = highPixels[p];
78 |
79 | // change the hue:
80 | hues[thisPixel] = hues[thisPixel] + changeValues[thisPixel];
81 | // keep the change within the min/max range,
82 | // but change directions at the extremes:
83 | if (hues[thisPixel] < 8 || hues[thisPixel] > 18) {
84 | changeValues[thisPixel] = -changeValues[thisPixel];
85 | }
86 | long thisColor = candle.ColorHSV(hues[thisPixel],
87 | saturations[thisPixel],
88 | intensities[thisPixel]);
89 | candle.setPixelColor(thisPixel, thisColor);
90 | }
91 | candle.show();
92 | delay(50);
93 | yield();
94 | }
95 |
96 |
97 | void fastLoop() {
98 | for (int p = 0; p < 2; p++) {
99 | int thisPixel = lowPixels[p];
100 | // change the hue:
101 | hues[thisPixel] = hues[thisPixel] + changeValues[thisPixel];
102 | // keep the change within the min/max range,
103 | // but add a random -1 to 2:
104 | hues[thisPixel] += (random(3) - 1);
105 | hues[thisPixel] = constrain(hues[thisPixel], 4, 16);
106 |
107 | long thisColor = candle.ColorHSV(hues[thisPixel],
108 | saturations[thisPixel],
109 | intensities[thisPixel]);
110 | candle.setPixelColor(thisPixel, thisColor);
111 | }
112 | candle.show();
113 | delay(30);
114 | }
115 |
116 |
117 | void medLoop() {
118 | for (int p = 0; p < 2; p++) {
119 | int thisPixel = lightPixels[p];
120 |
121 | // change the hue:
122 | hues[thisPixel] = hues[thisPixel] + changeValues[thisPixel];
123 | // keep the change within the min/max range,
124 | // but change directions at the extremes:
125 | if (hues[thisPixel] < 4 || hues[thisPixel] > 20) {
126 | changeValues[thisPixel] = -changeValues[thisPixel];
127 | }
128 | long thisColor = candle.ColorHSV(hues[thisPixel],
129 | saturations[thisPixel],
130 | intensities[thisPixel]);
131 | candle.setPixelColor(thisPixel, thisColor);
132 | }
133 | candle.show();
134 | delay(60);
135 | }
136 |
137 | void slowLoop() {
138 | // change the hue:
139 | hues[bluePixel] = hues[bluePixel] + changeValues[bluePixel];
140 | // change the intensity and constrain it:
141 | intensities[bluePixel] += (random(3) - 1);
142 | intensities[bluePixel] = constrain(intensities[bluePixel], 0, 20);
143 |
144 | // keep the change within the min/max range,
145 | // but change directions at the extremes:
146 | if (hues[bluePixel] < 200 || hues[bluePixel] > 280) {
147 | changeValues[bluePixel] = -changeValues[bluePixel];
148 | }
149 | long thisColor = candle.ColorHSV(hues[bluePixel],
150 | saturations[bluePixel],
151 | intensities[bluePixel]);
152 | candle.setPixelColor(bluePixel, thisColor);
153 | candle.show();
154 | delay(100);
155 | yield();
156 | }
157 |
--------------------------------------------------------------------------------
/Candles/WS281xCandle/WS281xCandle.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x candle using Adafruit_NeoPixel library
3 |
4 | This sketch creates a candle effect by randomizing
5 | hue, sat, and intensity for each pixel
6 |
7 | Note: in the Adafruit libraries, hue is a 16-bit number
8 | where red: 64530 - 1006
9 | yellow: 10902 - 10944
10 | etc.
11 |
12 | Change pixelCount to the number of LEDs in your string.
13 |
14 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
15 |
16 | created 17 Jun 2019
17 | modified 4 July 2019
18 | by Tom Igoe
19 | */
20 | #include
21 |
22 | const int neoPixelPin = 4; // control pin
23 | const int pixelCount = 8; // number of pixels
24 |
25 | // set up strip:
26 | Adafruit_NeoPixel candle = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
27 |
28 | // arrays for each pixel's hue, sat, and intensity:
29 | unsigned int hue[pixelCount];
30 | int sat[pixelCount];
31 | int intensity[pixelCount];
32 |
33 | void setup() {
34 | // initialize the random number generator using a reading
35 | // from pin A6 (not connected to anything, so it will generate
36 | // a random number):
37 | randomSeed(analogRead(A6));
38 |
39 | // loop over the pixel arrays:
40 | for (int p = 0; p < pixelCount; p++) {
41 | // generate a random initial value for each pixel's
42 | // hue, saturation and intensity:
43 | hue[p] = random(2000, 5000); // red-orange to mid-orange
44 | sat[p] = random(192, 255); // high end of saturation
45 | intensity[p] = random(127, 192); // mid-high range of intensity
46 | }
47 |
48 | candle.begin(); // initialize pixel strip
49 | candle.clear(); // turn all LEDs off
50 | candle.show(); // update strip
51 |
52 | }
53 |
54 | void loop() {
55 | // loop over the pixels:
56 | for (int p = 0; p < pixelCount; p++) {
57 | // change hue -1 to 2 points:
58 | int hueChange = random(-1,2);
59 | hue[p] += hueChange;
60 | // constrain to red to orange:
61 | hue[p] = constrain(hue[p], 800, 8000);
62 |
63 | // change saturation -1 to 1 points:
64 | int satChange = random(-1,1);
65 | sat[p] += satChange;
66 | // constrain to the top end of the range:
67 | sat[p] = constrain(sat[p], 192, 255);
68 |
69 | // change intensity -2 to 3 points:
70 | int intensityChange = random(-2,3);
71 | intensity[p] += intensityChange;
72 | // constrain to 20-255
73 | intensity[p] = constrain(intensity[p], 20, 255);
74 |
75 | // get RGB from HSV:
76 | unsigned long color = candle.ColorHSV(hue[p], sat[p], intensity[p]);
77 | // do a gamma correction:
78 | unsigned long correctedColor = candle.gamma32(color);
79 | candle.setPixelColor(p, correctedColor);
80 | }
81 | candle.show();
82 | delay(5);
83 | }
84 |
--------------------------------------------------------------------------------
/Candles/WS281xColorConverterCandle/WS281xColorConverterCandle.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Uses this ColorConverter library:
3 | https://github.com/tigoe/ColorConverter
4 |
5 |
6 | */
7 | #include
8 | #include
9 |
10 | const int neoPixelPin = 5; // control pin
11 | const int pixelCount = 8; // number of pixels
12 | int change = 1; // increment to change hue by
13 |
14 | // set up strip:
15 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
16 | ColorConverter converter;
17 |
18 | int h = 10; // hue
19 | int s = 100; // saturation
20 | int i = 100; // intensity
21 |
22 | void setup() {
23 | strip.begin(); // initialize pixel strip
24 | strip.clear(); // turn all LEDs off
25 | strip.show(); // update strip
26 | }
27 |
28 | void loop() {
29 | // create a single color from hue, sat, intensity:
30 | RGBColor color = converter.HSItoRGBW(h, s, i);
31 |
32 | // loop over all the pixels:
33 | for (int pixel = 0; pixel < pixelCount; pixel++) {
34 | strip.setPixelColor(pixel, color.red, color.green, color.blue); // set the color for this pixel
35 | strip.show(); // update the strip
36 | delay(100);
37 | }
38 |
39 | // increment hue to fade from red (0) to reddish orange (15) and back:
40 | h = h + change;
41 | if (h < 0 || h > 15) {
42 | change = -change;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/ColorTempFade/ColorTempFade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Warm to cool fader
3 | Fades two channels of LEDs, one warm white, one cool white
4 | Intensity fader fades the whole level down and up.
5 |
6 | Circuit:
7 | - 10-kilohm potentiometer on pin A0.
8 | - 10-kilohm potentiometer on pin A1
9 | - 12V LEDs on pins 4 and 5
10 |
11 | created 19 Jan 2019
12 | modified 17 June 2019
13 | by Tom Igoe
14 | */
15 |
16 | void setup() {
17 |
18 | }
19 |
20 | void loop() {
21 | // read intensity potentiometer:
22 | int intensity = analogRead(A0) / 4;
23 | delay(1);
24 | // read color temp potentiometer, use it to set ratio of warm to cool:
25 | int colorTemp = analogRead(A1) / 4;
26 | // calculate warm and cool values:
27 | int warm = intensity * ((255 - colorTemp) / 255.0);
28 | int cool = intensity * (colorTemp / 255.0);
29 | // PWM the LED channels:
30 | analogWrite(4, warm);
31 | analogWrite(5, cool);
32 | }
33 |
--------------------------------------------------------------------------------
/ColorTempLamp001/ColorTempLamp001.ino:
--------------------------------------------------------------------------------
1 | #include
2 | #include "Adafruit_TCS34725.h"
3 | #include
4 |
5 | const int neoPixelPin = 5; // control pin
6 | const int pixelCount = 60; // number of pixels
7 |
8 | // set up strip:
9 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRB + NEO_KHZ800);
10 | unsigned long color = 0xFF; // start with blue
11 | int times = 0;
12 | Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_700MS, TCS34725_GAIN_1X);
13 |
14 | void setup(void) {
15 | Serial.begin(9600);
16 | delay(3000);
17 | while (!tcs.begin()) {
18 | Serial.println("No TCS34725 found ... check your connections");
19 | tcs.begin();
20 | delay(1000);
21 | }
22 | Serial.println("Found sensor");
23 | strip.begin(); // initialize pixel strip
24 | strip.clear(); // turn all LEDs off
25 | strip.show(); // refresh strip
26 | }
27 |
28 | void loop(void) {
29 | if (times % 100 == 0) {
30 | for (int p = 0; p < pixelCount; p++) {
31 | strip.setPixelColor(p, color);// set the color for this pixel
32 | }
33 | strip.show();
34 | }
35 | uint16_t r, g, b, c, colorTemp, lux;
36 |
37 | tcs.getRawData(&r, &g, &b, &c);
38 | colorTemp = tcs.calculateColorTemperature(r, g, b);
39 | lux = tcs.calculateLux(r, g, b);
40 |
41 | Serial.print("Color Temp: "); Serial.print(colorTemp, DEC); Serial.print(" K - ");
42 | Serial.print("Lux: "); Serial.print(lux, DEC); Serial.print(" - ");
43 | Serial.print("R: "); Serial.print(r, DEC); Serial.print(" ");
44 | Serial.print("G: "); Serial.print(g, DEC); Serial.print(" ");
45 | Serial.print("B: "); Serial.print(b, DEC); Serial.print(" ");
46 | Serial.print("C: "); Serial.print(c, DEC); Serial.print(" ");
47 | Serial.println(" ");
48 |
49 | color = color << 8; // shift the FF (255) to the next color
50 | if (color > 0xFF0000) { // if the color is greater than white (0xFF000000)
51 | color = 0xFF; // then set it back to blue
52 | }
53 | times++;
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/FadeCurves/CIE1931Fade/CIE1931Fade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | CIE1931 fade
3 | Produces an LED fade that appears visually linear, using the CIE1931
4 | perceived lightness formula.
5 | Modified levelTable to a byte so that the whole sketch will fit
6 | in an ATTiny85. To make this work with the ATTiny85,
7 | change the faderPin to one of the ATTiny85's PWM pins,
8 | and comment out all the references to Serial.
9 |
10 | references:
11 | - http://hyperphysics.phy-astr.gsu.edu/hbase/vision/cie.html
12 | - https://jared.geek.nz/2013/feb/linear-led-pwm
13 | - https://github.com/lawtalker/rotary_dimmer/wiki
14 | circuit:
15 | - LED attached to pin 5
16 |
17 | created 5 May 2019
18 | modified 17 Jan 2025
19 | by Tom Igoe
20 | */
21 |
22 |
23 | // number of steps = 2^(PWM resolution):
24 | const int steps = 256;
25 | const int faderPin = 5;
26 | // change between steps:
27 | int change = 1;
28 | // current level:
29 | int currentLevel = 1;
30 | // pre-calculated PWM levels:
31 | byte levelTable[steps];
32 |
33 | void setup() {
34 | Serial.begin(9600);
35 | // // wait for serial monitor to open:
36 | if (!Serial) delay(3000);
37 | // pre-calculate the PWM levels from the formula:
38 | fillLevelTable();
39 | // initialize digital pin 5 as an output:
40 | pinMode(faderPin, OUTPUT);
41 | }
42 |
43 | void loop() {
44 | // decrease or increase by 1 point each time
45 | // if at the bottom or top, change the direction:
46 | if (currentLevel <= 0 || currentLevel >= steps - 1) {
47 | change = -change;
48 | }
49 | currentLevel += change;
50 |
51 | //PWM output the result:
52 | analogWrite(faderPin, levelTable[currentLevel]);
53 | delay(10);
54 | Serial.println(levelTable[currentLevel]);
55 | }
56 |
57 | void fillLevelTable() {
58 | /*
59 | For CIE1931, the following formulas have Y as luminance, and
60 | Yn is the luminance of a white reference (basically, max luminance).
61 | This assumes a perceived lightness value L between 0 and 100,
62 | and a luminance value (Y) of 0-1.0.
63 | if L > 8: Y = ((L + 16) / 116)^3 * Yn
64 | if L <= 8: Y = L * 903.3 * Yn
65 | */
66 | // scaling factor to convert from 0-100 to 0-steps:
67 | float scalingFactor = 100.0 / float(steps);
68 | // luminance value:
69 | float luminance = 0.0;
70 |
71 | // iterate over the array and calculate the right value for it:
72 | for (int l = 0; l < steps; l++) {
73 | // you need to scale lightness from a 0-steps range to a 0-100 range:
74 | float lightness = float(l) * scalingFactor;
75 | if (lightness <= 8) {
76 | luminance = (lightness / 903.3);
77 | } else {
78 | luminance = (lightness + 16) / 116.0;
79 | luminance = pow(luminance, 3);
80 | }
81 | // multiply to get 0 to steps, and fill in the table:
82 | levelTable[l] = byte(luminance * steps);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/FadeCurves/CIE1931FadeWithInput/CIE1931FadeWithInput.ino:
--------------------------------------------------------------------------------
1 | /*
2 | CIE1931 fade
3 | Takes input from an analog sensor and produces an LED fade
4 | that appears visually linear, using the CIE1931
5 | perceived lightness formula.
6 |
7 | references:
8 | - http://hyperphysics.phy-astr.gsu.edu/hbase/vision/cie.html
9 | - https://jared.geek.nz/2013/feb/linear-led-pwm
10 | - https://github.com/lawtalker/rotary_dimmer/wiki
11 | circuit:
12 | - LED attached to pin 5
13 | - 10Kilohm potentiometer attached to A0
14 |
15 | created 9 June 2019
16 | modified 31 May 2023
17 | by Tom Igoe
18 | */
19 |
20 | // number of steps = 2^PWM resolution:
21 | const int steps = 256;
22 | // change between steps:
23 | int change = 1;
24 | // current level:
25 | int currentLevel = 1;
26 | // pre-calculated PWM levels:
27 | int levelTable[steps];
28 |
29 | void setup() {
30 | Serial.begin(9600);
31 | // wait for serial monitor to open:
32 | if (!Serial) delay(3000);
33 | // pre-calculate the PWM levels from the formula:
34 | fillLevelTable();
35 | // initialize digital pin 5 as an output:
36 | pinMode(5, OUTPUT);
37 | }
38 |
39 | void loop() {
40 | // read potentiometer:
41 | int sensorReading = analogRead(A0);
42 | // map to range:
43 | int currentLevel = map(sensorReading, 0, 1023, 0, steps - 1);
44 |
45 | //PWM output the result:
46 | analogWrite(5, levelTable[currentLevel]);
47 | delay(10);
48 | Serial.println(levelTable[currentLevel]);
49 | }
50 |
51 | void fillLevelTable() {
52 | /*
53 | For CIE1931, the following formulas have Y as luminance, and
54 | Yn is the luminance of a white reference (basically, max luminance).
55 | This assumes a perceived lightness value L between 0 and 100,
56 | and a luminance value (Y) of 0-1.0.
57 | if L > 8: Y = ((L + 16) / 116)^3 * Yn
58 | if L <= 8: Y = L * 903.3 * Yn
59 | */
60 | // scaling factor to convert from 0-100 to 0-steps:
61 | float scalingFactor = 100.0 / float(steps);
62 | // luminance value:
63 | float luminance = 0.0;
64 |
65 | // iterate over the array and calculate the right value for it:
66 | for (int l = 0; l < steps; l++) {
67 | // you need to scale lightness from a 0-steps range to a 0-100 range:
68 | float lightness = float(l) * scalingFactor;
69 | if (lightness <= 8) {
70 | luminance = (lightness / 903.3);
71 | } else {
72 | luminance = (lightness + 16) / 116.0;
73 | luminance = pow(luminance, 3);
74 | }
75 | // multiply to get 0 to steps, and fill in the table:
76 | levelTable[l] = int(luminance * steps);
77 | }
78 | }
--------------------------------------------------------------------------------
/FadeCurves/CIE1931FadeWithKalmanInput/CIE1931FadeWithKalmanInput.ino:
--------------------------------------------------------------------------------
1 | /*
2 | CIE1931 fade
3 | Takes input from an analog sensor and produces an LED fade
4 | that appears visually linear, using the CIE1931
5 | perceived lightness formula.
6 |
7 | Additionally, a Kalman filter using the SimpleKalmanFilter library
8 | smoothes the sensor input:
9 | http://librarymanager/All#SimpleKalmanFilter.h
10 |
11 | references:
12 | - http://hyperphysics.phy-astr.gsu.edu/hbase/vision/cie.html
13 | - https://jared.geek.nz/2013/feb/linear-led-pwm
14 | - https://github.com/lawtalker/rotary_dimmer/wiki
15 | - https://github.com/denyssene/SimpleKalmanFilter
16 | circuit:
17 | - LED attached to pin 5
18 | - 10Kilohm potentiometer attached to A0
19 |
20 | created 9 June 2019
21 | modified 31 May 2023
22 | by Tom Igoe
23 | */
24 |
25 | #include
26 | SimpleKalmanFilter filter(2, 2, 0.01);
27 |
28 | // number of steps = 2^resolution:
29 | const int steps = 256;
30 | // change between steps:
31 | int change = 1;
32 | // current level:
33 | int currentLevel = 1;
34 | // pre-calculated PWM levels:
35 | int levelTable[steps];
36 |
37 | void setup() {
38 | Serial.begin(9600);
39 | // wait for serial monitor to open:
40 | if (!Serial) delay(3000);
41 | // pre-calculate the PWM levels from the formula:
42 | fillLevelTable();
43 | // initialize digital pin 5 as an output:
44 | pinMode(5, OUTPUT);
45 | }
46 |
47 | void loop() {
48 | // read potentiometer:
49 | int sensorReading = analogRead(A0);
50 |
51 | // calculate the estimated value with Kalman Filter
52 | float estimate = filter.updateEstimate(sensorReading);
53 |
54 | // map to 0 to range:
55 | int currentLevel = map(estimate, 0, 1023, 0, steps - 1);
56 |
57 | //PWM output the result:
58 | analogWrite(5, levelTable[currentLevel]);
59 | delay(10);
60 | Serial.println(levelTable[currentLevel]);
61 | }
62 |
63 |
64 | void fillLevelTable() {
65 | /*
66 | For CIE1931, the following formulas have Y as luminance, and
67 | Yn is the luminance of a white reference (basically, max luminance).
68 | This assumes a perceived lightness value L between 0 and 100,
69 | and a luminance value (Y) of 0-1.0.
70 | if L > 8: Y = ((L + 16) / 116)^3 * Yn
71 | if L <= 8: Y = L * 903.3 * Yn
72 | */
73 | // scaling factor to convert from 0-100 to 0-steps:
74 | float scalingFactor = 100.0 / float(steps);
75 | // luminance value:
76 | float luminance = 0.0;
77 |
78 | // iterate over the array and calculate the right value for it:
79 | for (int l = 0; l < steps; l++) {
80 | // you need to scale lightness from a 0-steps range to a 0-100 range:
81 | float lightness = float(l) * scalingFactor;
82 | if (lightness <= 8) {
83 | luminance = (lightness / 903.3);
84 | } else {
85 | luminance = (lightness + 16) / 116.0;
86 | luminance = pow(luminance, 3);
87 | }
88 | // multiply to get 0 to steps, and fill in the table:
89 | levelTable[l] = int(luminance * steps);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/FadeCurves/ExponentialFade/ExponentialFade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Exponential fade
3 | Produces a fade on an exponential curve for dimming LEDs.
4 | Formula and explanation from
5 | https://diarmuid.ie/blog/pwm-exponential-led-fading-on-arduino-or-other-platforms
6 |
7 | to change from 10-bit resolution to 8, change the resolution variable
8 | and comment out the analogWriteResolution() command
9 |
10 | created by Diarmuid Mac Namara
11 | adapted 5 May 2019
12 | modified 31 May 2023
13 | by Tom Igoe
14 | */
15 |
16 | // number of steps = 2^PWM resolution:
17 | const int steps = 256;
18 | // change between steps:
19 | int change = 1;
20 | // current level:
21 | int currentLevel = 1;
22 | // pre-calculated PWM levels:
23 | int levelTable[steps];
24 |
25 | void setup() {
26 | Serial.begin(9600);
27 | // wait for serial monitor to open:
28 | if (!Serial) delay(3000);
29 | // pre-calculate the PWM levels from the formula:
30 | fillLevelTable();
31 | // initialize digital pin 5 as an output:
32 | pinMode(5, OUTPUT);
33 | }
34 |
35 | void loop() {
36 | // decrease or increase by 1 point each time
37 | // if at the bottom or top, change the direction:
38 | if (currentLevel <= 0 || currentLevel >= steps - 1) {
39 | change = -change;
40 | }
41 | currentLevel += change;
42 |
43 | //PWM output the result:
44 | analogWrite(5, levelTable[currentLevel]);
45 | delay(10);
46 | Serial.println(levelTable[currentLevel]);
47 | }
48 |
49 | void fillLevelTable() {
50 |
51 | // Calculate the scaling factor based on the
52 | // number of PWM steps you want:
53 | float scalingFactor = (steps * log10(2)) / (log10(steps));
54 |
55 | // iterate over the array and calculate the right value for it:
56 | for (int l = 0; l < steps; l++) {
57 | int lightLevel = pow(2, (l / scalingFactor)) - 1;
58 | levelTable[l] = lightLevel;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/FadeCurves/SimpleFade/SimpleFade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Linear Fade
3 |
4 | Fades an LED on a linear path
5 | created by nearly everyone who's used an Arduino
6 |
7 | to change from 10-bit resolution to 8, change the resolution variable
8 | and comment out the analogWriteResolution() command
9 |
10 | modified 31 May 2023
11 | by Tom Igoe
12 | */
13 |
14 |
15 | // number of steps = 2^PWM resolution:
16 | const int steps = 255;
17 | // change between steps:
18 | int change = 1;
19 | // current level:
20 | int currentLevel = 1;
21 |
22 | void setup() {
23 | Serial.begin(9600);
24 | // wait for serial monitor to open:
25 | if (!Serial) delay(3000);
26 | // initialize digital pin 5 as an output:
27 | pinMode(5, OUTPUT);
28 | }
29 | void loop() {
30 | // add change to brightness:
31 | currentLevel = currentLevel + change;
32 | // and constrain to 0-255:
33 | currentLevel = constrain(currentLevel, 0, steps);
34 | // if brightness is at either extreme, change the
35 | // direction of fading:
36 | if (currentLevel == 0 || currentLevel == steps) {
37 | change = -change;
38 | }
39 | // change the light:
40 | analogWrite(5, currentLevel);
41 | delay(10);
42 | Serial.println(currentLevel);
43 | }
44 |
--------------------------------------------------------------------------------
/FadeCurves/SimpleFadeWithInput/SimpleFadeWithInput.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Linear Fade
3 |
4 | Takes input from an analog sensor and
5 | fades an LED on a linear path
6 |
7 | circuit:
8 | - LED attached to pin 5
9 | - 10Kilohm potentiometer attached to A0
10 |
11 | to change from 10-bit resolution to 8, change the resolution variable
12 | and comment out the analogWriteResolution() command
13 |
14 | created by nearly everyone who's used an Arduino
15 | modified 31 May 2023
16 | by Tom Igoe
17 | */
18 |
19 | int brightness = 0;
20 | int change = 1;
21 | const int steps = 8;
22 |
23 | void setup() {
24 | Serial.begin(9600);
25 | // put your setup code here, to run once:
26 | while (!Serial) delay(3000);
27 | pinMode(5, OUTPUT);
28 | }
29 | void loop() {
30 | // read potentiometer:
31 | int sensorReading = analogRead(A0);
32 | // map to 0-255 range:
33 | int currentLevel = map(sensorReading, 0, 1023, 0, steps);
34 | // change the light:
35 | analogWrite(5, currentLevel);
36 | Serial.println(currentLevel);
37 | }
38 |
--------------------------------------------------------------------------------
/FadeCurves/SineFade/SineFade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Sine fade
3 | Produces a sinusoidal fade curve
4 |
5 | to change from 10-bit resolution to 8, change the resolution variable
6 | and comment out the analogWriteResolution() command
7 |
8 | created 30 Jan 2019
9 | modified 31 May 2023
10 | by Tom Igoe and lighting & interactivity 2019 class
11 | */
12 |
13 | // number of steps = 2^PWM resolution:
14 | const int steps = 256;
15 | // change between steps:
16 | int change = 1;
17 | // current level:
18 | int currentLevel = 1;
19 | // pre-calculated PWM levels:
20 | int levelTable[steps];
21 |
22 | void setup() {
23 | Serial.begin(9600);
24 | // wait for serial monitor to open:
25 | if (!Serial) delay(3000);
26 | // pre-calculate the PWM levels from the formula:
27 | fillLevelTable();
28 | // initialize digital pin 5 as an output:
29 | pinMode(5, OUTPUT);
30 | }
31 |
32 | void loop() {
33 | // decrease or increase by 1 point each time
34 | // if at the bottom or top, change the direction:
35 | if (currentLevel <= 0 || currentLevel >= steps - 1) {
36 | change = -change;
37 | }
38 | currentLevel += change;
39 |
40 | //PWM output the result:
41 | analogWrite(5, levelTable[currentLevel]);
42 | delay(10);
43 | Serial.println(levelTable[currentLevel]);
44 | }
45 |
46 | void fillLevelTable() {
47 | // iterate over the array and calculate the right value for it:
48 | for (int l = 0; l < steps; l++) {
49 | // map input to a 0-360 range:
50 | int angle = map(l, 0, steps, 0, 360);
51 | // convert to radians:
52 | float lightLevel = angle * PI / 180;
53 | // now subtract PI/2 to offset by 90 degrees, so yuu can start fade at 0:
54 | lightLevel -= PI / 2;
55 | // get the sine of that:
56 | lightLevel = sin(lightLevel);
57 | // now you have -1 to 1. Add 1 to get 0 to 2:
58 | lightLevel += 1;
59 | // multiply to get 0-255:
60 | lightLevel *= (steps - 1) / 2;
61 | // put it in the array:
62 | levelTable[l] = int(lightLevel);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/FadeCurves/SineFadeWithInput/SineFadeWithInput.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Sine fade
3 | Takes a linear input from an analog sensor
4 | and produces a sinusoidal fade curve
5 | circuit:
6 | - LED attached to pin 5
7 | - 10Kilohm potentiometer attached to A0
8 |
9 | to change from 10-bit resolution to 8, change the resolution variable
10 | and comment out the analogWriteResolution() command
11 |
12 | created 30 Jan 2019
13 | modified 31 May 2023
14 | by Tom Igoe and lighting & interactivity 2019 class
15 | */
16 |
17 | // number of steps = 2^PWM resolution:
18 | const int steps = 256;
19 | // change between steps:
20 | int change = 1;
21 | // current level:
22 | int currentLevel = 1;
23 | // pre-calculated PWM levels:
24 | int levelTable[steps];
25 |
26 | void setup() {
27 | Serial.begin(9600);
28 | // wait for serial monitor to open:
29 | if (!Serial) delay(3000);
30 | // pre-calculate the PWM levels from the formula:
31 | fillLevelTable();
32 | // initialize digital pin 5 as an output:
33 | pinMode(5, OUTPUT);
34 | }
35 |
36 | void loop() {
37 | // read potentiometer:
38 | int sensorReading = analogRead(A0);
39 | // map to 0-255 range:
40 | int currentLevel = map(sensorReading, 0, 1023, 0, steps - 1);
41 | //PWM output the result:
42 | analogWrite(5, levelTable[currentLevel]);
43 | Serial.println(levelTable[currentLevel]);
44 | }
45 |
46 | void fillLevelTable() {
47 | // iterate over the array and calculate the right value for it:
48 | for (int l = 0; l < steps; l++) {
49 | // map input to a 0-360 range:
50 | int angle = map(l, 0, steps, 0, 360);
51 | // convert to radians:
52 | float lightLevel = angle * PI / 180;
53 | // now subtract PI/2 to offset by 90 degrees, so yuu can start fade at 0:
54 | lightLevel -= PI / 2;
55 | // get the sine of that:
56 | lightLevel = sin(lightLevel);
57 | // now you have -1 to 1. Add 1 to get 0 to 2:
58 | lightLevel += 1;
59 | // multiply to get 0-255:
60 | lightLevel *= (steps - 1) / 2;
61 | // put it in the array:
62 | levelTable[l] = int(lightLevel);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/FadeCurves/XSquaredFade/XSquaredFade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | X-squared fade
3 | Takes a linear input and produces a fade curve
4 | based on the square of the input
5 |
6 | created 5 May 2019
7 | modified 25 Jan 2023
8 | by Tom Igoe
9 | */
10 |
11 | // analogWrite resolution (can be 10 for SAMD boards, has to be 8 for Uno):
12 | const int resolution = 10;
13 | // number of steps = 2^resolution:
14 | const int steps = pow(2, resolution);
15 | // change between steps:
16 | int change = 1;
17 | // current level:
18 | int currentLevel = 1;
19 | // pre-calculated PWM levels:
20 | int levelTable[steps];
21 |
22 | void setup() {
23 | Serial.begin(9600);
24 | // wait for serial monitor to open:
25 | if (!Serial) delay(3000);
26 | // pre-calculate the PWM levels from the formula:
27 | fillLevelTable();
28 | // set the analogWrite resolution:
29 | analogWriteResolution(resolution);
30 | // initialize digital pin 5 as an output:
31 | pinMode(5, OUTPUT);
32 | }
33 |
34 | void loop() {
35 | // decrease or increase by 1 point each time
36 | // if at the bottom or top, change the direction:
37 | if (currentLevel <= 0 || currentLevel >= steps - 1) {
38 | change = -change;
39 | }
40 | currentLevel += change;
41 |
42 | //PWM output the result:
43 | analogWrite(5, levelTable[currentLevel]);
44 | delay(10);
45 | Serial.println(levelTable[currentLevel]);
46 | }
47 |
48 | void fillLevelTable() {
49 | // iterate over the array and calculate the right value for it:
50 | for (int l = 0; l <= steps; l++) {
51 | // square the current value:
52 | float lightLevel = pow(l, 2);
53 | // map the result back to the resolution range:
54 | lightLevel = map(lightLevel, 0, 65535, 0, steps-1);
55 | levelTable[l] = lightLevel;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/HueSkyLight/HueSkyLight.ino:
--------------------------------------------------------------------------------
1 | /* Hue color temperature control example for ArduinoHttpClient library
2 |
3 | This example reads a TCS34725 light sensor to get the color temperature.
4 | Uses ArduinoHttpClient library to control Philips Hue
5 | For more on Hue developer API see http://developer.meethue.com
6 |
7 | Works with MKR1010, MKR1000, Nano 33 IoT
8 | Uses the following libraries:
9 | http://librarymanager/All#WiFi101 // use this for MKR1000
10 | http://librarymanager/All#WiFiNINA // use this for MKR1010, Nano 33 IoT
11 | http://librarymanager/All#ArduinoHttpClient
12 | http://librarymanager/All#Arduino_JSON
13 |
14 | see readme.mfd file for more details.
15 |
16 | note: WiFi SSID and password are stored in arduino_secrets.h file.
17 | If it is not present, add a new tab, call it "arduino_secrets.h"
18 | and add the following defines, and change to your own values:
19 |
20 | #define SECRET_SSID "ssid"
21 | #define SECRET_PASS "password"
22 |
23 | created 5 april 2021
24 | by Tom Igoe
25 | */
26 |
27 | #include
28 | //#include
29 | #include
30 | #include
31 | #include
32 | // I2C and light sensor libraries:
33 | #include
34 | #include
35 |
36 | #include "arduino_secrets.h"
37 |
38 | int status = WL_IDLE_STATUS; // the Wifi radio's status
39 | char hueHubIP[] = "192.168.0.4"; // IP address of the HUE bridge
40 | String hueUserName = "Huebridgeusername"; // hue bridge username
41 | int lightNumber = 5;
42 |
43 | // make a wifi instance and a HttpClient instance:
44 | WiFiClient wifi;
45 | HttpClient httpClient = HttpClient(wifi, hueHubIP);
46 | // change the values of these two in the arduino_serets.h file:
47 | char ssid[] = SECRET_SSID;
48 | char pass[] = SECRET_PASS;
49 | // set the content type:
50 | const char contentType[] = "application/json";
51 |
52 | // initialize the light sensor:
53 | Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_700MS, TCS34725_GAIN_1X);
54 |
55 | // time of the last succcessful request to the Hue, in ms:
56 | long lastRequestTime = 0;
57 | int sendInterval = 60000;
58 |
59 | // a JSON object for the body of the request:
60 | JSONVar body;
61 |
62 | void setup() {
63 | //Initialize serial and wait 3 secs for serial monitor to open:
64 | Serial.begin(9600);
65 | if (!Serial) delay(3000);
66 |
67 | // attempt to connect to Wifi network:
68 | while ( status != WL_CONNECTED) {
69 | Serial.print("Attempting to connect to WPA SSID: ");
70 | Serial.println(ssid);
71 | // Connect to WPA/WPA2 network:
72 | status = WiFi.begin(ssid, pass);
73 | delay(2000);
74 | }
75 |
76 | // you're connected now, so print out the data:
77 | Serial.print("You're connected to the network IP = ");
78 | IPAddress ip = WiFi.localIP();
79 | Serial.println(ip);
80 | }
81 |
82 | void loop() {
83 | // If the client is not connected:
84 | if (!httpClient.connected()) {
85 | // and the request interval has passed:
86 | if (millis() - lastRequestTime >= sendInterval) {
87 | // read the sensor
88 | readSensor();
89 | // print latest reading, for reference:
90 | Serial.println(JSON.stringify(body));
91 | // make a String for the HTTP route:
92 | String route = "/api/" + hueUserName;
93 | route += "/lights/";
94 | route += lightNumber;
95 | route += "/state/";
96 |
97 | // make a PUT request:
98 | httpClient.put(route, contentType, JSON.stringify(body));
99 | // timestamp the request:
100 | lastRequestTime = millis();
101 |
102 | }
103 | }
104 |
105 | // If there is a response available, read it:
106 | if (httpClient.available()) {
107 | // read the status code and body of the response
108 | int statusCode = httpClient.responseStatusCode();
109 | String response = httpClient.responseBody();
110 |
111 | // print out the response:
112 | Serial.print("Status code: ");
113 | Serial.println(statusCode);
114 | Serial.print("Response: " );
115 | Serial.println(response);
116 | // close the request:
117 | httpClient.stop();
118 | // timestamp the request if you got a good response:
119 | if (statusCode == 200) {
120 | Serial.println("Good response");
121 | }
122 | }
123 | }
124 |
125 | void readSensor() {
126 | // get lux and color temperature from sensor:
127 | uint16_t r, g, b, c, colorTemp, lux;
128 | tcs.getRawData(&r, &g, &b, &c);
129 | colorTemp = tcs.calculateColorTemperature_dn40(r, g, b, c);
130 | Serial.println("CT: " + String(colorTemp));
131 | lux = tcs.calculateLux(r, g, b);
132 | // crude formula for lux conversion, based on a sensor reading
133 | // of 35000 in direct sunlight and 1500 in indirect sunlight.
134 | // lux is actually a power law curve.
135 | // TODO: get the proper conversion:
136 | float maxLux = 2000.0;
137 | lux = constrain(lux, 0, maxLux);
138 | Serial.println("lux: " + String(lux));
139 | // convert to a range from 0 - 254:
140 | int bri = lux / maxLux * 254;
141 |
142 | // convert color temp to mired value (see Hue API docs on ct property):
143 | // which is what the Hue expects:
144 | int mired = 1000000 / colorTemp;
145 | mired = constrain(mired, 153, 500);
146 | Serial.println(mired);
147 | // and map it to 153 to 500 for the Hue:
148 |
149 | // update elements of request body JSON object:
150 | body["bri"] = bri;
151 | body["on"] = true;
152 | body["ct"] = mired;
153 | body["transitiontime"] = sendInterval / 100;
154 | }
155 |
--------------------------------------------------------------------------------
/HueSkyLight/arduino_secrets.h:
--------------------------------------------------------------------------------
1 | #define SECRET_SSID ""
2 | #define SECRET_PASS ""
3 |
--------------------------------------------------------------------------------
/HueSkyLight/readme.md:
--------------------------------------------------------------------------------
1 | # Hue color temperature control example for ArduinoHttpClient library
2 |
3 | This example reads a TCS34725 light sensor to get the color temperature. It then uses ArduinoHttpClient library to control Philips Hue. For more on Hue developer API see http://developer.meethue.com
4 |
5 | To control a light, the Hue expects a HTTP PUT request to:
6 | http://hue.hub.address/api/hueUserName/lights/lightNumber/state
7 |
8 | The body of the PUT request looks like this:
9 | ````
10 | {"on": true,
11 | "bri": brightness, // ranges from 0 - 254
12 | "ct": value, // where value ranges from 153 - 500
13 | "transitiontime": fadeTime //in increments of 100ms
14 | }
15 | ````
16 |
17 | ct is in the mired scale, which is 1000000/degrees Kelvin
18 |
19 | This example uses the Arduino_JSON library to assemble the
20 | PUT request and the body of the request.
21 |
22 | Works with MKR1010, MKR1000, Nano 33 IoT.
23 | Uses the following libraries:
24 | http://librarymanager/All#WiFi101 // use this for MKR1000
25 | http://librarymanager/All#WiFiNINA // use this for MKR1010, Nano 33 IoT
26 | http://librarymanager/All#ArduinoHttpClient
27 | http://librarymanager/All#Arduino_JSON
28 | http://librarymanager/All#Adafruit_TCS347525
29 |
30 | note: WiFi SSID and password are stored in arduino_secrets.h file.
31 | If it is not present, add a new tab, call it "arduino_secrets.h"
32 | and add the following defines, and change to your own values:
33 | ````
34 | #define SECRET_SSID "ssid"
35 | #define SECRET_PASS "password"
36 | ````
37 |
38 | created 5 april 2021
39 | by Tom Igoe (tigoe)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 tigoe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/LightSensorProjects/LightSensorChart/chart.js:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Graph page JavaScript
4 | Adapted from Don Coleman's ITP conndev visualizer:
5 | https://github.com/don/itp-conndev/tree/main/public
6 |
7 | */
8 |
9 | // Get data from the server for the selected device
10 | async function getChartData(device) {
11 | // assemble the URL from the URL input field on the HTML page:
12 | let serverUrl = 'https://' + document.getElementById('url').value + '/data';
13 | // add the mac address from the macAddr input field om the HTML page:
14 | let macAddress = document.getElementById('macAddr').value;
15 | // add the mac address from the sessKey input field om the HTML page:
16 | let sessionKey = document.getElementById('sessKey').value;
17 | // make an HTTPS call for the data:
18 | const response = await fetch(serverUrl + `?macAddress=`
19 | + macAddress + `&sessionKey=` + sessionKey);
20 | // When you get a response, put the result in a variable:
21 | const json = await response.json();
22 | // map the result into a big set of arrays that you can use to graph:
23 | const rows = json.map(row => [new Date(row.timestamp), JSON.parse(row.data).lux, JSON.parse(row.data).CCT]);
24 | // graphj the result:
25 | drawChart(rows);
26 | }
27 |
28 | // Draw the chart
29 | function drawChart(rows) {
30 | // make an instance of the DataTable library:
31 | var data = new google.visualization.DataTable();
32 | // add columns to the table for date, Lux, and CCT:
33 | data.addColumn('date', 'Date');
34 | data.addColumn('number', 'Lux');
35 | data.addColumn('number', 'CCT');
36 | // add the rows from the server:
37 | data.addRows(rows);
38 | // make the chart:
39 | var chart = new google.visualization.AnnotationChart(document.getElementById('chart_div'));
40 | // include the annotations:
41 | var options = {
42 | displayAnnotations: true
43 | };
44 | // draw the chart:
45 | chart.draw(data, options);
46 | }
47 |
48 | // here's the only line that actually gives a command. When the page loads,
49 | // load the google charts library:
50 | google.charts.load('current', { 'packages': ['annotationchart'] });
--------------------------------------------------------------------------------
/LightSensorProjects/LightSensorChart/credentials.js:
--------------------------------------------------------------------------------
1 | const credentials = {
2 | serverUrl : "",
3 | route : "/data",
4 | macAddress : "",
5 | sessionKey : ""
6 | };
7 |
8 | document.getElementById('url').value = credentials.serverUrl;
9 | // add the mac address from the macAddr input field om the HTML page:
10 | document.getElementById('macAddr').value = credentials.macAddress;
11 | // add the mac address from the sessKey input field om the HTML page:
12 | document.getElementById('sessKey').value = credentials.sessionKey;
--------------------------------------------------------------------------------
/LightSensorProjects/LightSensorChart/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Light Level Plot
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Server url:
12 |
MAC addr:
13 |
Session Key:
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/LightSensorProjects/LightSensorClient/LightSensorClient.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Networked datalogging light sensor client
3 |
4 | Connects to a server using HTTPS and uploads data.
5 | Follows the API of Mathura Govindarajan and Don Coleman's
6 | mySQL server for Connected Devices course:
7 | https://github.com/don/itp-connected-devices
8 | Works with MKR1010, MKR1000, ESP8266.
9 |
10 | circuit:
11 | - AMS TCS34725 light sensor attached to SDA and SCL pins
12 |
13 | arduino_secrets.h file contains:
14 | #define SECRET_SSID "" // your network SSID (name)
15 | #define SECRET_PASS "" // your network password
16 | #define SECRET_SESSIONKEY "" // UUID for client in database
17 | #define SECRET_SERVER "" // server IP
18 |
19 | created 5 Apr 2020
20 | by Tom Igoe
21 | */
22 | // include appropriate libraries for your WiFi module:
23 | #include
24 | //#include // for MKR1000 modules
25 | #include // for MKR1010 modules
26 | //#include // for ESP8266 modules
27 |
28 | // include libraries for HTTP calls and JSON formatting:
29 | #include
30 | #include
31 |
32 | #include "Adafruit_TCS34725.h" // light sensor library
33 | #include // realtime clock library
34 | #include "arduino_secrets.h" // sensitive information
35 |
36 | byte mac[6]; // mac address of your device
37 | WiFiSSLClient netSocket; // network socket to server
38 | String route = "/data"; // API route
39 | JSONVar sensorData; // JSON for sensor data
40 | JSONVar requestBody; // JSOn for body of the request
41 | String contentType = "application/json"; // content type header
42 |
43 | // the HTTP client is global so you can use it in multiple functions below:
44 | HttpClient client(netSocket, SECRET_SERVER, 443);
45 | // initialize the sensor:
46 | Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_700MS, TCS34725_GAIN_1X);
47 | // initialize the realtime clock:
48 | RTCZero rtc;
49 |
50 | void setup() {
51 | Serial.begin(9600); // initialize serial communication
52 | pinMode(LED_BUILTIN, OUTPUT);
53 | // while (!Serial);
54 | // start sensor:
55 | while (!tcs.begin()) {
56 | Serial.println("Looking for sensor");
57 | delay(100);
58 | }
59 | // start RTC:
60 | rtc.begin();
61 | // turn network connection LED off:
62 | digitalWrite(LED_BUILTIN, LOW);
63 | connectToNetwork();
64 | }
65 |
66 | void loop() {
67 | // read sensor:
68 | uint16_t r, g, b, c, colorTemp, lux;
69 | tcs.getRawData(&r, &g, &b, &c);
70 | sensorData["sensor"] = "AMS TCS34725";
71 | sensorData["lux"] = tcs.calculateLux(r, g, b);
72 | sensorData["CCT"] = tcs.calculateColorTemperature_dn40(r, g, b, c);
73 |
74 | // every 5 minutes on the 0 second, send data to the server:
75 | if ((rtc.getMinutes() % 5 == 0) && (rtc.getSeconds() == 0)) {
76 | // add the sensor data to the request body:
77 | requestBody["data"] = JSON.stringify(sensorData);
78 | Serial.println(JSON.stringify(requestBody));
79 | // make the request:
80 | client.post(route, contentType, requestBody);
81 |
82 | // read the status code and body of the response
83 | int statusCode = client.responseStatusCode();
84 | String response = client.responseBody();
85 |
86 | Serial.print("Status code: ");
87 | Serial.println(statusCode);
88 | Serial.print("Response: " );
89 | Serial.println(response);
90 | // when there's nothing left to the response,
91 | client.stop(); // close the request
92 | }
93 | // if disconnected from WiFi, attempt to reconnect:
94 | if (WiFi.status() != WL_CONNECTED) {
95 | digitalWrite(LED_BUILTIN, LOW);
96 | connectToNetwork();
97 | }
98 | }
99 |
100 | void connectToNetwork() {
101 | // try to connect to the network:
102 | while ( WiFi.status() != WL_CONNECTED) {
103 | Serial.println("Attempting to connect to: " + String(SECRET_SSID));
104 | //Connect to WPA / WPA2 network:
105 | WiFi.begin(SECRET_SSID, SECRET_PASS);
106 | delay(2000);
107 | }
108 | Serial.println("connected to: " + String(SECRET_SSID));
109 | // You're connected, turn on the LED:
110 | digitalWrite(LED_BUILTIN, HIGH);
111 |
112 | // get your MAC address:
113 | WiFi.macAddress(mac);
114 | IPAddress ip = WiFi.localIP();
115 | Serial.print(ip);
116 | Serial.print(" MAC Address: ");
117 | Serial.print(macToString(mac));
118 | requestBody["macAddress"] = macToString(mac);
119 | requestBody["sessionKey"] = SECRET_SESSIONKEY;
120 |
121 | Serial.print(" Signal Strength: ");
122 | Serial.println(WiFi.RSSI());
123 |
124 | // set the time from the network:
125 | unsigned long epoch;
126 | do {
127 | Serial.println("Attempting to get network time");
128 | epoch = WiFi.getTime();
129 | delay(2000);
130 | } while (epoch == 0);
131 |
132 | rtc.setEpoch(epoch);
133 | Serial.println(String(rtc.getHours()) +
134 | ":" + String(rtc.getMinutes()) +
135 | ":" + String(rtc.getSeconds()));
136 | }
137 |
138 | // convert the MAC address to a String:
139 | String macToString(byte mac[]) {
140 | String result;
141 | for (int i = 5; i >= 0; i--) {
142 | if (mac[i] < 16) {
143 | result += "0";
144 | }
145 | result += String(mac[i], HEX);
146 | if (i > 0) result += ":";
147 | }
148 | return result;
149 | }
150 |
--------------------------------------------------------------------------------
/LightSensorProjects/readme.md:
--------------------------------------------------------------------------------
1 | # Light Sensor Data Logging
2 |
3 | This project shows how to log data from a light sensor on a newtorked server. Thanks to Don Coleman and Mathura Govindarajan for this excellent [mySQL and node.js server](https://github.com/don/itp-connected-devices) for datalogging.
4 |
5 | The sensor is a [TCS34725 color sensor](https://ams.com/tcs34725) from AMS using [Adafruit's library](https://github.com/adafruit/Adafruit_TCS34725) for it. AMS has some pretty good [application notes](https://ams.com/tcs34725#tab/documents) on this sensor too.
6 |
7 | The [LightSensorChart](LightSensorChart) is based on Don's [visualizer for the connected devices class](https://github.com/don/itp-conndev) as well.
8 |
9 | The sensor sends data to a server every five minutes.
10 |
--------------------------------------------------------------------------------
/PatternMakers/ProcessingCirclesVectorExport/ProcessingCirclesVectorExport.pde:
--------------------------------------------------------------------------------
1 | /**
2 | Circle packing sketch that exports a vector (DXF or SVG) file
3 | when you press d or s. Every time you press one of these keys,
4 | the current image is saved and a new image is generated.
5 |
6 | created 13 Jan 2020
7 | by Tom Igoe
8 | */
9 |
10 | // import SVG and DXF libraries:
11 | import processing.dxf.*;
12 | import processing.svg.*;
13 |
14 |
15 | ArrayList circles = new ArrayList(); // arrayList to hold objects to be drawn
16 | int objectCount = 500; // how many circles to draw
17 | boolean recording = false; // whether you're recording a file or not
18 | int fileCount = 0; // count of files recorded so far
19 | String fileName; // file name and extension
20 | String extension = "";
21 |
22 | void setup() {
23 | // set up window and smooth drawing:
24 | size(800, 600, P3D);
25 | smooth();
26 | }
27 |
28 | void draw() {
29 | // generate objects to be drawn:
30 | while (circles.size() < objectCount) {
31 | makeCircles();
32 | }
33 |
34 | // start recording if s or d key has been pressed:
35 | if (recording) {
36 | // assemble fileName:
37 | fileName = "circles" + fileCount + "." + extension;
38 |
39 | // recording format depends on key pressed:
40 | if (extension.equals("svg")) {
41 | beginRaw(SVG, fileName);
42 | } else if (extension.equals("dxf")) {
43 | beginRaw(DXF, fileName);
44 | }
45 | }
46 |
47 | // you've got enough things to draw, now draw them:
48 | background(255);
49 | stroke(0);
50 | noFill();
51 |
52 | // draw the actual objects in the arrayList:
53 | for (int c=0; c < circles.size(); c++) {
54 | Circle thisCircle = circles.get(c);
55 | circle(thisCircle.x, thisCircle.y, thisCircle.d);
56 | }
57 |
58 | if (recording) {
59 | endRaw(); // end the recording:
60 | fileCount++; // increment file counter:
61 | circles.clear(); // clear the list of objects:
62 | recording = false; // clear recording flag:
63 | }
64 | }
65 |
66 | // determine what file format, SVG (press s) or DXF (press D):
67 | void keyPressed() {
68 | // record SVG
69 | if (key == 's' || key == 'S') {
70 | recording = true;
71 | extension = "svg";
72 | }
73 | // record DXF
74 | if (key == 'd' || key == 'D') {
75 | recording = true;
76 | extension = "dxf";
77 | }
78 | }
79 |
80 | // generate objects to be drawn:
81 | void makeCircles() {
82 | // generate a random position and diameter:
83 | Circle thisCircle = new Circle(random(width), random(height), random(10, 50));
84 |
85 | // check to see if new circle overlaps any of the existing ones:
86 | for (int c = 0; c < circles.size(); c++) {
87 | Circle thatCircle = circles.get(c);
88 | // don't add a circle if it overlaps an existing one:
89 | if (overlaps(thatCircle, thisCircle)) {
90 | return;
91 | }
92 | }
93 | // add to the list of objects to be drawn:
94 | circles.add(thisCircle);
95 | }
96 |
97 | // check to see if two circles overlap
98 | // (actually checks if they are less than a number of pixels from each other)
99 | boolean overlaps(Circle obj, Circle newObj) {
100 | boolean result = true;
101 | // safe margin of distance between objects:
102 | float margin = 5.0;
103 | // get the distance between the objects' centersL
104 | float distance = dist(newObj.x, newObj.y, obj.x, obj.y);
105 | // generate an overall safe distance between them:
106 | float safeDistance = newObj.d / 2 + obj.d / 2 + margin;
107 | if (distance >= safeDistance) {
108 | // if the distance > safeDistance, then they don't overlap:
109 | result = false;
110 | }
111 | return result;
112 | }
113 |
114 | // generates a new circle object. Used so we have all the circles' characteristics in memory
115 | class Circle {
116 | float x; // horizontal position
117 | float y; // vertical position
118 | float d; // diameter
119 |
120 | // constructor. Used to make new objects:
121 | Circle(float _x, float _y, float _d) {
122 | x = _x;
123 | y = _y;
124 | d = _d;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/PatternMakers/ProcessingVectorExport/ProcessingVectorExport.pde:
--------------------------------------------------------------------------------
1 | /**
2 | Simple sketch that exports a vector (DXF or SVG) file
3 | when you press d or s. Every time you press one of these keys,
4 | the current image is saved and a new image is generated.
5 |
6 | created 13 Jan 2020
7 | by Tom Igoe
8 | */
9 |
10 | // import SVG and DXF libraries:
11 | import processing.dxf.*;
12 | import processing.svg.*;
13 |
14 | boolean recording = false; // whether you're recording a file or not
15 | int fileCount = 0; // count of files recorded so far
16 | String fileName; // file name and extension
17 | String extension = "";
18 |
19 | void setup() {
20 | // set up window and smooth drawing:
21 | size(800, 600, P3D);
22 | smooth();
23 | // new pattern every 2 seconds:
24 | frameRate(0.5);
25 | }
26 |
27 | void draw() {
28 | // start recording if s or d key has been pressed:
29 | if (recording) {
30 | // assemble fileName:
31 | fileName = "pattern" + fileCount + "." + extension;
32 |
33 | // recording format depends on key pressed:
34 | if (extension.equals("svg")) {
35 | beginRaw(SVG, fileName);
36 | } else if (extension.equals("dxf")) {
37 | beginRaw(DXF, fileName);
38 | }
39 | }
40 |
41 | // you've got enough things to draw, now draw them:
42 | background(255);
43 | stroke(0);
44 | noFill();
45 |
46 | // draw the pattern:
47 | circle(random(width), random(height), random(20, 80));
48 | rect(random(width), random(height), random(20, 80), random(20, 80));
49 |
50 | if (recording) {
51 | endRaw(); // end the recording:
52 | fileCount++; // increment file counter
53 | recording = false; // clear recording flag:
54 | }
55 | }
56 |
57 | // determine what file format, SVG (press s) or DXF (press D):
58 | void keyPressed() {
59 | // record SVG
60 | if (key == 's' || key == 'S') {
61 | recording = true;
62 | extension = "svg";
63 | }
64 | // record DXF
65 | if (key == 'd' || key == 'D') {
66 | recording = true;
67 | extension = "dxf";
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/PatternMakers/p5CircleMaker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | circleMaker
9 |
10 |
11 |
12 |
13 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/PatternMakers/p5CircleMaker/sketch.js:
--------------------------------------------------------------------------------
1 | /**
2 | Circle packing sketch that exports an SVG file
3 | when you click the button.
4 |
5 | created 13 Jan 2020
6 | by Tom Igoe
7 | */
8 |
9 | let circles = new Array; // array to hold the circles
10 | let objectCount = 500; // how many circles to draw
11 | let h = 600;
12 | let w = 800;
13 | let button;
14 | let img;
15 |
16 | function setup() {
17 | // set up the SVG canvas:
18 | createCanvas(w, h, SVG);
19 | // create an image object to hold the pattern:
20 | img = createGraphics(w, h, SVG);
21 | // set image parameters:
22 | img.strokeWeight(1);
23 | img.ellipseMode(CENTER);
24 | img.noFill();
25 | // // create the file save button and give it properties:
26 | button = createButton('save');
27 | button.position(10, 10);
28 | button.mouseReleased(makeFile);
29 | noLoop();
30 | }
31 |
32 | function draw() {
33 | // generate pattern until there are enough circles:
34 | while (circles.length < objectCount) {
35 | makeCircles();
36 | }
37 | // // draw the image on the screen:
38 | image(img, 0, 0, w, h);
39 | }
40 |
41 | // make a new file with the SVG element:
42 | function makeFile() {
43 | // get the SVG string:
44 | let input = img.elt.svg.outerHTML;
45 | // put the string in an array:
46 | let output = [input];
47 | // save it out to a file:
48 | saveStrings(output, 'image', 'svg');
49 | }
50 |
51 | // generate objects to be drawn:
52 | function makeCircles() {
53 | let thisCircle = {
54 | x: random(w),
55 | y: random(h),
56 | d: random(10, 50)
57 | };
58 |
59 | // check to see if new circle overlaps any of the existing ones:
60 | for (let c = 0; c < circles.length; c++) {
61 | if (overlaps(circles[c], thisCircle)) {
62 | // don't add a circle if it overlaps an existing one:
63 | return;
64 | }
65 | }
66 | // add to the list of objects to be drawn:
67 | circles.push(thisCircle);
68 | // add the circle to the SVG image
69 | img.ellipse(thisCircle.x, thisCircle.y, thisCircle.d, thisCircle.d);
70 | }
71 |
72 | // check to see if two circles overlap
73 | // (actually checks if they are less than a number of pixels from each other)
74 | function overlaps(obj, newObj) {
75 | let result = true;
76 | // safe margin of distance between objects:
77 | let margin = 5.0;
78 | // get the distance between the objects' centers:
79 | let distance = dist(newObj.x, newObj.y, obj.x, obj.y);
80 | // generate an overall safe distance between them:
81 | let safeDistance = newObj.d / 2 + obj.d / 2 + margin;
82 | if (distance >= safeDistance) {
83 | // if the distance > safeDistance, then they don't overlap:
84 | result = false;
85 | }
86 |
87 | return result;
88 | }
--------------------------------------------------------------------------------
/PatternMakers/p5HexagonMaker/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Hexagon Maker
9 |
10 |
11 |
12 |
13 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/PatternMakers/p5HexagonMaker/sketch.js:
--------------------------------------------------------------------------------
1 | /**
2 | hexagon packing sketch that exports an SVG file
3 | when you click the button. Unlike the p5CircleMaker, this
4 | sketch will check to make sure all shapes are fully within
5 | the frame of the image.
6 |
7 | created 13 Jan 2020
8 | modified 18 July 2021
9 | by Tom Igoe
10 | */
11 |
12 | let shapes = new Array; // array to hold the shapes
13 | let objectCount = 500; // how many shapes to draw
14 | let h = 600;
15 | let w = 800;
16 | let button;
17 | let img;
18 | // change this for other numbers of sides to the shape:
19 | const SIDE_COUNT = 6;
20 |
21 | function setup() {
22 | // set up the SVG canvas:
23 | createCanvas(w, h, SVG);
24 | // create an image object to hold the pattern:
25 | img = createGraphics(w, h, SVG);
26 | // set image parameters:
27 | img.strokeWeight(1);
28 | img.ellipseMode(CENTER);
29 | img.noFill();
30 | // // create the file save button and give it properties:
31 | button = createButton('save');
32 | button.position(10, 10);
33 | button.mouseReleased(makeFile);
34 | noLoop();
35 | }
36 |
37 | function draw() {
38 | // generate pattern until there are enough shapes:
39 | while (shapes.length < objectCount) {
40 | makeShapes();
41 | }
42 | // // draw the image on the screen:
43 | image(img, 0, 0, w, h);
44 | }
45 |
46 | // make a new file with the SVG element:
47 | function makeFile() {
48 | // get the SVG string:
49 | let input = img.elt.svg.outerHTML;
50 | // put the string in an array:
51 | let output = [input];
52 | // save it out to a file:
53 | saveStrings(output, 'image', 'svg');
54 | }
55 |
56 | // generate objects to be drawn:
57 | function makeShapes() {
58 | // set the diameter of the shape:
59 | let thisSize = random(10, 50);
60 | // set the x and y such that it's always inside the canvas:
61 | let thisShape = {
62 | x: random(w - thisSize * 2) + thisSize,
63 | y: random(h - thisSize * 2) + thisSize,
64 | d: thisSize
65 | };
66 |
67 | // check to see if new shape overlaps any of the existing ones:
68 | for (let c = 0; c < shapes.length; c++) {
69 | // don't add a shape if it overlaps an existing one:
70 | if (overlaps(shapes[c], thisShape)) return;
71 | }
72 | // add to the list of objects to be drawn:
73 | shapes.push(thisShape);
74 | // add the shape to the SVG image
75 | polygon(thisShape.x, thisShape.y, thisShape.d / 2, SIDE_COUNT);
76 | }
77 |
78 | // check to see if two shapes overlap
79 | // (actually checks if they are less than a number of pixels from each other)
80 | function overlaps(obj, newObj) {
81 | let result = true;
82 | // safe margin of distance between objects:
83 | let margin = 5.0;
84 | // get the distance between the objects' centers:
85 | let distance = dist(newObj.x, newObj.y, obj.x, obj.y);
86 | // generate an overall safe distance between them:
87 | let safeDistance = newObj.d / 2 + obj.d / 2 + margin;
88 | if (distance >= safeDistance) {
89 | // if the distance > safeDistance, then they don't overlap:
90 | result = false;
91 | }
92 |
93 | return result;
94 | }
95 |
96 | // from https://p5js.org/examples/form-regular-polygon.html
97 | function polygon(x, y, radius, npoints) {
98 | let angle = TWO_PI / npoints;
99 | img.beginShape();
100 | for (let a = 0; a < TWO_PI; a += angle) {
101 | let sx = x + cos(a) * radius;
102 | let sy = y + sin(a) * radius;
103 | img.vertex(sx, sy);
104 | }
105 | img.endShape(CLOSE);
106 | }
107 |
--------------------------------------------------------------------------------
/PulseLamp/PulseLamp.ino:
--------------------------------------------------------------------------------
1 | /* Pulse sensor lamp dimmer
2 |
3 | Controls a lamp using the average heart rate detected from a
4 | MAX30101 particle sensor
5 |
6 | Uses the Sparkfun MAX 3010x sensor library:
7 | https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library
8 | HR code is based on the example 5 from that library.
9 |
10 | circuit:
11 | - MAX30301 sensor connected to SDA and SCL pins
12 | - Transistor connected to pin 3 to control a 12V DC lamp
13 |
14 | created 1 Dec 2020
15 | by Tom Igoe
16 | */
17 |
18 | // include libraries:
19 | #include
20 | #include "MAX30105.h"
21 | #include "heartRate.h"
22 |
23 | const int lampPin = 3;
24 | MAX30105 sensor;
25 |
26 | //Number of heart rate samples to average for the average heart rate.
27 | // increase this for a smoother rate, decrease for more responsivity.
28 | /// 4 is a decent compromise between the two:
29 | const byte SAMPLES = 4;
30 | //Array of heart rates:
31 | byte heartRates[SAMPLES];
32 | // index for the array:
33 | byte arrayIndex = 0;
34 | float beatsPerMinute;
35 | int avgBPM;
36 | // last time in ms that you got a heartbeat:
37 | long lastBeat = 0;
38 |
39 | // light variables:
40 | int level = 127;
41 | // last time the light level was changed:
42 | long lastChange = 0;
43 | // direction of the light level fade:
44 | int fadeChange = 1;
45 |
46 | // pre-calculated PWM levels to produce a nicer fade curve.
47 | // See https://tigoe.github.io/LightProjects/fading.html for other examples:
48 | byte cie1931[256];
49 |
50 | void setup() {
51 | Serial.begin(9600);
52 | Serial.println("Begin");
53 |
54 | // Initialize sensor:
55 | while (!sensor.begin()) {
56 | Serial.println("Heart rate sensor was not found. Please check wiring/power. ");
57 | delay(1000);
58 | }
59 | // Configure sensor with default settings:
60 | sensor.setup();
61 | // Turn Red LED to low to indicate sensor is running:
62 | sensor.setPulseAmplitudeRed(0x0A);
63 | // Turn off Green LED:
64 | sensor.setPulseAmplitudeGreen(0);
65 |
66 | // initiate the I/O pin for the transistor that's controlling the lamp:
67 | pinMode(lampPin, OUTPUT);
68 | pinMode (LED_BUILTIN, OUTPUT);
69 | // fill the table of PWM values to produce a nice light fade curve:
70 | fillCIETable();
71 | }
72 |
73 | void loop() {
74 | byte intensity;
75 |
76 | // change the level of the light every (beatAverage /2) milliseconds:
77 | if (millis() - lastChange > avgBPM ) {
78 | // reverse direction of fade when level is 0 or 255:
79 | if (level == 0 || level == 255) {
80 | fadeChange = -fadeChange;
81 | }
82 | // change the level using the fadeChange value:
83 | level += fadeChange;
84 | // get the PWM level from the table:
85 | intensity = cie1931[level];
86 | // set the PWM pin with it:
87 | analogWrite(lampPin, intensity);
88 | lastChange = millis();
89 | }
90 |
91 | // read the sensor:
92 | long irValue = sensor.getIR();
93 | // if the IR level is too high, there's no finger there to sense.
94 | // you can skip the rest of the loop() and return to the top of the loop:
95 | if (irValue < 50000) {
96 | Serial.println(" put your index finger on the sensor");
97 | return;
98 | }
99 |
100 | // Once you have an IR value, check to see if it counts as a heart beat
101 | // (uses a function in heartRate.h):
102 | if (checkForBeat(irValue)) {
103 | // turn on built in LED each heartbeat:
104 | digitalWrite(LED_BUILTIN, HIGH);
105 | // get the duration of this beat:
106 | long beatDuration = millis() - lastBeat;
107 | //save current time for comparison next time:
108 | lastBeat = millis();
109 | // calculte BPM from beat duration:
110 | beatsPerMinute = 60 / (beatDuration / 1000.0);
111 |
112 | // if BPM is in a reasonable range for human heart rate (20 - 255?),
113 | // put it in the array for averaging:
114 | if (beatsPerMinute < 255 && beatsPerMinute > 20) {
115 | // Store this reading in the array:
116 | heartRates[arrayIndex++] = (byte)beatsPerMinute;
117 | // limit the array pointer to the size of the array:
118 | arrayIndex %= SAMPLES;
119 |
120 | //Take average of readings
121 | avgBPM = 0;
122 | for (byte x = 0 ; x < SAMPLES ; x++) {
123 | avgBPM += heartRates[x];
124 | }
125 | avgBPM = avgBPM / SAMPLES;
126 | }
127 | } else {
128 | // turn off built in LED if no heartbeat:
129 | digitalWrite(LED_BUILTIN, LOW);
130 | }
131 | // print the intensity and heart rate data:
132 | Serial.print("Intensity: ");
133 | Serial.print(intensity);
134 | Serial.print(", BPM=");
135 | Serial.print(beatsPerMinute);
136 | Serial.print(", Avg BPM=");
137 | Serial.println(avgBPM);
138 | }
139 |
140 |
141 | void fillCIETable() {
142 | /*
143 | For CIE, the following formulas have Y as luminance, and
144 | Yn is the luminance of a white reference (basically, max luminance).
145 | This assumes a perceived lightness value L* between 0 and 100,
146 | and a luminance value Y of 0-1.0.
147 | if L* > 8: Y = ((L* + 16) / 116)^3 * Yn
148 | if L* <= 8: Y = L* *903.3 * Yn
149 | */
150 | // set the range of values:
151 | float maxValue = 255;
152 | // scaling factor to convert from 0-100 to 0-maxValue:
153 | float scalingFactor = 100 / maxValue;
154 | // luminance value:
155 | float Y = 0.0;
156 |
157 | // iterate over the array and calculate the right value for it:
158 | for (int l = 0; l <= maxValue; l++) {
159 | // you need to scale L from a 0-255 range to a 0-100 range:
160 | float lScaled = float(l) * scalingFactor;
161 | if ( lScaled <= 8 ) {
162 | Y = (lScaled / 903.3);
163 | } else {
164 | float foo = (lScaled + 16) / 116.0;
165 | Y = pow(foo, 3);
166 | }
167 | // multiply to get 0-maxValue, and fill in the table:
168 | cie1931[l] = Y * maxValue;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Light Projects
2 |
3 | A collection of lighting projects controlled by microcontrollers.
4 |
5 | ## Low-voltage DC Lamps
6 |
7 | Low-voltage [LED Lamps](led-lamps) are easy to control from a microcontroller when you know what properties you need to be aware of. In fact, there are so many low-voltage sources on the market now that you can manage most indoor lighting needs without needing to control high-voltage AC sources.
8 |
9 | ## Recommended Parts
10 |
11 | This [list of parts](inventory) is not comprehensive, it's just a list of some of the parts I've used in building this repository.
12 |
13 | You may find it useful to combine the examples in this repository with some of my [Arduino general examples](https://github.com/tigoe/ArduinoGeneralExamples) or [Sensor examples](https://github.com/tigoe/SensorExamples) to add interactive conntrol over your light projects.
14 |
15 | ## LED Strip Control
16 | Single-channel and multi-channel [LED strips](led-strips) are very popular these days, and pretty easy to control with a microcontroller and a few transistors. You can find LED strips in a variety of combinations:
17 | * Red/Green/Blue (RGB)
18 | * Red/Green/Blue/White (RGBW). These come in cool white, warm white, and natural white variations.
19 | * Tunable White. These come in a variety of styles: Warm/cool white, Warm/cool/natural white, and White/white/amber.
20 |
21 | ## Addressable LED Control
22 | There are a number of [addressable LED components](addressable-leds) on the market now, and they come in a variety of form factors. Covered here are a couple of the most popular, the WS28xx/SK68xx LEDs, and the APA102C LEDs. Adafruit calls their products that use these LEDs NeoPixels and DotStars, respectively. SparkFun calls their APA102C line Lumenati. Whatever you call them, they're relatively easy to use, and there are many good tools for controlling them. Addressable strips can be found in the many of the same variations as non-addressable strips, including RGB, RGBW, RGBAW, and WWA.
23 |
24 | The examples below are written mostly using Adafruit's NeoPixel and DotStar libraries. Since they have mostly the same API, you can convert any of the sketches from one protocol to the other with minimal changes.
25 |
26 | ### Examples:
27 | * LED Test - turns on each channel of each LED individually, to make sure they all work. [APA102xTester](https://github.com/tigoe/LightProjects/tree/main/APA102x/APA102xTester) and [WS281xTester](https://github.com/tigoe/LightProjects/tree/main/WS281x/WS281xTester)
28 | * Load test - turns on all the LEDs in a strip cumulatively so you can test if your power supply will power them all. [APA102xLoadTester](https://github.com/tigoe/LightProjects/tree/main/APA102x/APA102xLoadTester) and [WS281xLoadTester](https://github.com/tigoe/LightProjects/tree/main/WS281x/WS281xLoadTester)
29 | * Turns on a single color. [WS281xOneColor](https://github.com/tigoe/LightProjects/tree/main/WS281x/WS281xOneColor)
30 |
31 | ## Fading
32 | The relationship between perceived brightness and the power controlling a light source is non-linear. In otherwise, if you fade an LED source in a straight line, it won't appear to you to be fading evenly in time. Depending on the effect you're looking for, you might want a light source to start fading slowly and then speed up, or slow down at the end of its fade. It helps to [have methods for a few different fade curves](fading) available.
33 |
34 | ### Examples
35 | * [SimpleFade](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/SimpleFade)
36 | * [SineFade](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/SineFade)
37 | * [XSquaredFade](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/XSquaredFade)
38 | * [ExponentialFade](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/ExponentialFade)
39 | * [CIE1931Fade](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/CIE1931Fade)
40 | * [CIE1931FadeWithKalmanInput](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/CIE1931FadeWithKalmanInput)
41 | * [CIE1931FadeWithInput](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/CIE1931FadeWithInput)
42 | * [SimpleFadeWithInput](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/SimpleFadeWithInput)
43 | * [SineFadeWithInput](https://github.com/tigoe/LightProjects/tree/main/FadeCurves/SineFadeWithInput)
44 |
45 | ## Color and Color Temperature Fading
46 |
47 | Whether you're working with tunable white lighting sources or color sources, it's useful to understand a few things about
48 | [Color Spaces and Color Temperature](color-spaces-color-temp) in order to get the most our of your sources.
49 |
50 | ### Color Space Examples
51 |
52 | Examples:
53 | * RGB fade. Treats the whole RGB space as a single value and fades all LEDs one point at a time. [APA102xRGBFade](https://github.com/tigoe/LightProjects/tree/main/APA102x/APA102xRGBFade)
54 | * RGB Fade 002. Fades red, green, and blue of all LEDs one point at a time. [APA102xRGBFade002](https://github.com/tigoe/LightProjects/tree/main/APA102x/APA102xRGBFade002)
55 | * HSV Tester. Fades hue through the whole hue color wheel on all LEDs, while keeping saturation and intensity constant. [APA102x_HSV_Tester](https://github.com/tigoe/LightProjects/tree/main/APA102x/APA102x_HSV_Tester) and [WS281x_HSV_Tester](https://github.com/tigoe/LightProjects/tree/main/WS281x/WS281x_HSV_Tester)
56 | * A digital candle effect made by varying the hue of LEDs from red to orange. [APA102xCandle](https://github.com/tigoe/LightProjects/tree/main/Candles/APA102xCandle)
57 |
58 | ### Tunable White (Color Temperature) Examples
59 |
60 | * Color temperature fade. Fades a strip of tunable white LEDs (non-addressable) from warm to cool. [ColorTempFade](https://github.com/tigoe/LightProjects/tree/main/ColorTempFade/)
61 |
62 | The examples below use the ColorConverter library to control a WWA strip by calculating relative mix of warm, cool, and amber as hue. Intensity can then be controlled independently of color temperature.
63 | Examples:
64 | * [WS281x_Mixer](https://github.com/tigoe/LightProjects/tree/main/WS281x/WWA_WS281x_Mixer)
65 | * [WS281x_AdvancedMixer](https://github.com/tigoe/LightProjects/tree/main/WS281x/WWA_WS281x_AdvancedMixer)
66 |
67 | ### Lighting Control Systems
68 |
69 | * [Philips Hue repository](https://github.com/tigoe/hue-control)
70 | * [DMX-512 and streaming ACN respository](https://tigoe.github.io/DMX-Examples/)
71 |
72 | ### Light Sensing
73 |
74 | * [Sensor Datalogging](https://github.com/tigoe/LightProjects/tree/main/LightSensorProjects/)
75 | * [Arduino Light Sensor Examples](https://github.com/tigoe/SensorExamples/tree/main/LightSensors)
76 |
77 | ### Pattern Making in Code
78 |
79 | Sometimes you need to make patterns for lighting fixtures. Here are [some examples](patternMakers).
80 |
81 | ### Projects
82 |
83 | [Rotary telephone dimmer for Philips Hue systems](telephone-dimmer)
84 |
85 | [Color temperature control of Philips Hue systems with light sensors](sky-lights)
--------------------------------------------------------------------------------
/SqueezeBulbLamp/SqueezeBulb_Lamp_circuit.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/SqueezeBulbLamp/SqueezeBulb_Lamp_circuit.fzz
--------------------------------------------------------------------------------
/SqueezeBulbLamp/SqueezeBulb_Lamp_circuit_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/SqueezeBulbLamp/SqueezeBulb_Lamp_circuit_bb.png
--------------------------------------------------------------------------------
/SqueezeBulbLamp/SqueezeBulb_Lamp_circuit_schem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/SqueezeBulbLamp/SqueezeBulb_Lamp_circuit_schem.png
--------------------------------------------------------------------------------
/SqueezeBulbLamp/SqueezeLamp001/SqueezeLamp001.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Squeeze bulb lamp
3 |
4 | Reads an MPX2012 sensor on pin A0, looks for peaks, and increments
5 | a variable called intensity with each peak.
6 | Reads a potentiometer on pin A1, uses it to set a ratio of warm to cool
7 | controls two LED channels on pins 4 and 5
8 |
9 | Circuit:
10 | MPX2102 attached to an LM358 op amp attached to pin A0.
11 | potentiometer on pin A1
12 | 12V LEDs on pins 4 and 5
13 | See circuit drawing for details
14 |
15 | created 19 Jan 2019
16 | by Tom Igoe
17 | */
18 | #include
19 | #include
20 | #include "Adafruit_TCS34725.h"
21 |
22 | Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_700MS, TCS34725_GAIN_1X);
23 |
24 | // first 2 numbers are the amount of variation,
25 | // third is how fast the measurement moves. recommended: 0.01:
26 | SimpleKalmanFilter filter(2, 2, 0.01);
27 |
28 | int intensity = 0; // LED intensity (0-255)
29 | int lastEstimate = 0; // last sensor estimate from the Kalman filter
30 | int peakValue = 0; // peak value
31 | int threshold = 1000; // threshold for peak reading
32 |
33 | void setup() {
34 | // initialize serial communication:
35 | Serial.begin(9600);
36 | }
37 |
38 | void loop() {
39 | // read the input:
40 | float sensorValue = analogRead(A0);
41 | Serial.print(sensorValue);
42 | Serial.print(",");
43 | // calculate the estimated value using the Kalman Filter:
44 | float estimate = filter.updateEstimate(sensorValue);
45 | Serial.println(estimate);
46 | // look for a peak, above the threshold:
47 | if (estimate > peakValue && estimate > threshold) {
48 | peakValue = estimate;
49 | }
50 | if (estimate <= threshold) {
51 | if (peakValue > threshold) {
52 | // you have a peak value:
53 | // at each peak, increase the intensity by 10%
54 | intensity += 25;
55 | // keep it at or below 255:
56 | intensity = min(intensity, 255);
57 | // reset the peak variable:
58 | peakValue = 0;
59 | }
60 | }
61 |
62 | // every second, decrement the intensity by ~1%:
63 | if (millis() % 1000 < 2) {
64 | intensity -= 2;
65 | // keep it at or above 0:
66 | intensity = max(intensity, 0);
67 | }
68 | // read potentiometer, use it to set ratio of warm to cool:
69 | int potReading = analogRead(A1) / 4;
70 | int warm = intensity * (255 - potReading) / 255.0;
71 | int cool = intensity * (potReading / 255.0);
72 | // PWM the LED channels:
73 | analogWrite(4, warm);
74 | analogWrite(5, cool);
75 | }
--------------------------------------------------------------------------------
/SqueezeBulbLamp/readme.md:
--------------------------------------------------------------------------------
1 | # Squeeze Bulb Lamp
2 |
3 | This sketch and circuit are for a squeeze bulb lamp. It's made of a strip of 12-volt LEDs with two channels: warm white and cool white. The main control is a squeeze bulb attached to an air pressure sensor (an MPX2102, seen in Fig. 1 below). Squeezing the bulb increases the intensity of the LEDs by 10%. On their own, they fade by 1% every second. A potentiometer controls the balance of warm to cool LEDs.
4 |
5 |
6 | ## The Circuit
7 | Figures 1 and 2 below show the circuit. The sensor circuit is based on my MBX2102 pressure sensor example. The components are:
8 |
9 | * a MKR Zero microcontroller
10 | * a strip of 12-volt LED lights with two channels, warm and cool white
11 | * 2 TIP120 transistors to control the strip
12 | * an MPX2102 pressure sensor
13 | * an LM358 op amp to read the sensor
14 | * 1 10-kilohm potentiometer
15 | * 4 1-kilohm resistors
16 | * 1 100-kilohm resistor
17 | * 1 470-kilohm resistor
18 | * 1 12-volt DC powee supply. Amperage depends on how many LEDs you are using. I used a 1.2A supply
19 |
20 | The LED strip's' 12-volt connection is connected to +12-volt power supply. The warm and cool connections are connected to the collectors of the two transistors. The emitters of the transistors are connected to ground, and the bases of the transistors are connected to pins D4 and D5 of the MKR Zero, respectively, through 1-kilohm resistors. The ground of the 12-volt power supply is connected to the ground of the microcontroller as well.
21 |
22 | The LM358 op amp is powered from the Vcc pin of the MKR Zero, so the voltage across the op amp is 3.3V. Pin 8 is the op amp's Vcc pin, and pin 4 is the ground. The inverting and non-inverting inputs of the op amp (pins 2 and 3) are connected to the pressure sensor's outputs through 1-kilohm resistors. A 100-kilohm resistor is the feedback resistor, connecting the op amp's output (pin 1) to the inverting input (pin 2). A 470-kilohm resistor is connected from the non-inverting input (pin 3) to ground. The op amp's output (pin 1) is connected to pin A0 on the microcontroller (pin 2). The sensor is also connected to ground and Vcc on its pins 1 and 3, respectively.
23 |
24 | The potentiometer's two outside pins are connected to Vcc and ground, and its center pin is connected to pin A1 of the microcontroller (pin 3).
25 |
26 | Images made in Fritzing and Illustrator CS.
27 |
28 | 
29 |
30 | _Figure 1. Breadboard view of Squeeze bulb lamp circuit_
31 |
32 |
33 | 
34 |
35 | _Figure 2. Schematic view of Squeeze bulb lamp circuit_
36 |
--------------------------------------------------------------------------------
/StripControls/555LEDFader.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/StripControls/555LEDFader.fzz
--------------------------------------------------------------------------------
/TelephoneDimmer/RotaryDial0002/RotaryDial0002.ino:
--------------------------------------------------------------------------------
1 |
2 | const int rotaryPin = 2;
3 | const int endRotaryPin = 3;
4 | const int cradleHookPin = 9;
5 | const int debounceDelay = 12;
6 |
7 | volatile bool dialing = false;
8 | volatile int dialCount = 0;
9 | volatile bool hungUp = true;
10 |
11 | void setup() {
12 | Serial.begin(9600);
13 | pinMode (rotaryPin, INPUT_PULLUP);
14 | pinMode (endRotaryPin, INPUT_PULLUP);
15 | pinMode (cradleHookPin, INPUT_PULLUP);
16 | hungUp = digitalRead(cradleHookPin);
17 | attachInterrupt(digitalPinToInterrupt(rotaryPin), count, FALLING);
18 | attachInterrupt(digitalPinToInterrupt(endRotaryPin), dial, CHANGE);
19 | }
20 |
21 | void loop() {
22 | if (!dialing) {
23 | hungUp = digitalRead(cradleHookPin);
24 | }
25 | if (hungUp) {
26 | Serial.println("hung up");
27 | delay(500);
28 | }
29 | if (millis() % 1000 < 1 && !dialing) {
30 | Serial.print("Dial count: ");
31 | Serial.println(dialCount);
32 | delay(1);
33 | }
34 | }
35 |
36 |
37 |
38 | void dial() {
39 | delay(debounceDelay);
40 | dialing = !digitalRead(endRotaryPin);
41 | if (dialing) {
42 | dialCount = 0;
43 | }
44 | }
45 |
46 | void count() {
47 | delay(debounceDelay);
48 | if (digitalRead(rotaryPin) == LOW) {
49 | dialCount++;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/TelephoneDimmer/SleepTest/SleepTest.ino:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 | const int cradleHookPin = 9;
5 | RTCZero rtc;
6 | int sleepMinutes = -1;
7 |
8 | void setup() {
9 | //Initialize serial and wait for port to open:
10 | Serial.begin(9600);
11 | delay(5000);
12 | pinMode(LED_BUILTIN, OUTPUT);
13 | digitalWrite(LED_BUILTIN, LOW);
14 |
15 | pinMode (cradleHookPin, INPUT_PULLUP);
16 |
17 | LowPower.attachInterruptWakeup(digitalPinToInterrupt(cradleHookPin), wakePhone, FALLING);
18 | rtc.begin();
19 |
20 | rtc.setTime(0, 59, 50);
21 | digitalWrite(LED_BUILTIN, HIGH);
22 | }
23 |
24 | void loop() {
25 | Serial.begin(9600);
26 | digitalWrite(LED_BUILTIN, HIGH);
27 | delay(1000);
28 | Serial.print(rtc.getHours());
29 | Serial.print(":");
30 | Serial.print(rtc.getMinutes());
31 | Serial.print(":");
32 | Serial.println(rtc.getSeconds());
33 |
34 | if (rtc.getMinutes() == sleepMinutes) {
35 | sleepPhone();
36 | }
37 |
38 | if (digitalRead(cradleHookPin) == HIGH) {
39 | sleepMinutes = (rtc.getMinutes() + 3) % 60;
40 | Serial.println("sleep at: " + String(sleepMinutes));
41 | }
42 |
43 | }
44 |
45 | void sleepPhone() {
46 | Serial.println("Sleeping phone");
47 | digitalWrite(LED_BUILTIN, LOW);
48 | LowPower.sleep();
49 | }
50 |
51 | void wakePhone() {
52 | Serial.println("waking phone");
53 | digitalWrite(LED_BUILTIN, HIGH);
54 | sleepMinutes = -1;
55 | }
56 |
--------------------------------------------------------------------------------
/TelephoneDimmer/dialtone/dialtone.ino:
--------------------------------------------------------------------------------
1 | void setup() {
2 |
3 | }
4 |
5 | void loop() {
6 | tone(A0, 440);
7 | tone(A0, 350);
8 |
9 | delay(1000);
10 | noTone(A0);
11 | delay(1000);
12 | }
13 |
--------------------------------------------------------------------------------
/WS281x/WS281HSVColorDemo/WS281HSVColorDemo.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x strip using Adafruit_NeoPixel library
3 |
4 | This sketch shows the colors of the HSV wheel
5 |
6 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
7 |
8 | modified 31 Jan 2022
9 | by Tom Igoe
10 | */
11 | #include
12 |
13 | const int neoPixelPin = 5; // control pin
14 | const int pixelCount = 7; // number of pixels
15 |
16 |
17 |
18 | // set up strip:
19 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
20 | unsigned int hue;
21 | int sat = 255;
22 | int intensity = 255;
23 | long lastRgbColor = 0;
24 |
25 | void setup() {
26 | strip.begin(); // initialize pixel strip
27 | strip.clear(); // turn all LEDs off
28 | strip.show(); // update strip
29 | Serial.begin(9600);
30 | }
31 |
32 | void loop() {
33 | int rgbColor;
34 | if (hue < 65536) {
35 | hue++;
36 | }
37 | else {
38 | hue = 0;
39 | }
40 |
41 | for (int p = 0; p < pixelCount; p++) {
42 | // get RGB from HSV:
43 | unsigned long color = strip.ColorHSV(hue, sat, intensity);
44 | // do a gamma correction:
45 | unsigned long correctedColor = strip.gamma32(color);
46 | strip.setPixelColor(p, correctedColor);
47 | rgbColor = strip.getPixelColor(p);
48 | }
49 | strip.show();
50 | Serial.print(rgbColor, HEX);
51 | Serial.print("\t");
52 | Serial.print(hue);
53 | Serial.print("\t");
54 |
55 | switch (rgbColor) {
56 | case 0xFF0000: // 64530 - 1006
57 | Serial.println("Red");
58 | break;
59 | case 0xFF00: //20839-22851
60 | Serial.println("Green");
61 | break;
62 | case 0xFF: // 42685-44697
63 | Serial.println("blue");
64 | break;
65 | case 0xFFFF00: // 10902-10944
66 | Serial.println("yellow");
67 | break;
68 | case 0xFFFF: // 32747 - 32789
69 | Serial.println("cyan");
70 | break;
71 | case 0xFF00FF: // 54592-54634
72 | Serial.println("magenta");
73 | break;
74 | default:
75 | Serial.println();
76 | }
77 | delay(10);
78 | }
79 |
--------------------------------------------------------------------------------
/WS281x/WS281HSVColorDemoSerial/WS281HSVColorDemoSerial.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x strip using Adafruit_NeoPixel library
3 |
4 | This sketch shows the colors of the HSV wheel
5 | You can stop it by sending ' ' from the serial monitor
6 | and restart it by sending any letter from the serial monitor
7 |
8 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
9 |
10 | modified 4 July 2019
11 | by Tom Igoe
12 | */
13 | #include
14 |
15 | const int neoPixelPin = 5; // control pin
16 | const int pixelCount = 7; // number of pixels
17 |
18 |
19 |
20 | // set up strip:
21 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
22 | unsigned int hue;
23 | int sat = 255;
24 | int intensity = 255;
25 | long lastRgbColor = 0;
26 | bool stopped = false;
27 | void setup() {
28 | strip.begin(); // initialize pixel strip
29 | strip.clear(); // turn all LEDs off
30 | strip.show(); // update strip
31 | Serial.begin(9600);
32 | }
33 |
34 | void loop() {
35 |
36 | int rgbColor;
37 | // a variable to read incoming characters from the serial port:
38 | char inChar = -1;
39 | // if there are any characters available to read from the serial port:
40 | if (Serial.available() > 0) {
41 | // read the first character to arrive:
42 | inChar = Serial.read();
43 | // if the character is a space:
44 | if (inChar == ' ') {
45 | stopped = true;
46 | }
47 | // if the character is anything from a-z or A-Z:
48 | if (inChar > 'A') {
49 | stopped = false;
50 | }
51 | }
52 | // if stopped is false, scroll through the colors:
53 | if (!stopped) {
54 | if (hue < 65536) {
55 | hue++;
56 | }
57 | else {
58 | hue = 0;
59 | }
60 |
61 | for (int p = 0; p < pixelCount; p++) {
62 | // get RGB from HSV:
63 | unsigned long color = strip.ColorHSV(hue, sat, intensity);
64 | // do a gamma correction:
65 | unsigned long correctedColor = strip.gamma32(color);
66 | strip.setPixelColor(p, correctedColor);
67 | rgbColor = strip.getPixelColor(p);
68 | }
69 | strip.show();
70 | Serial.print(rgbColor, HEX);
71 | Serial.print("\t");
72 | Serial.print(hue);
73 | Serial.print("\t");
74 |
75 | switch (rgbColor) {
76 | case 0xFF0000: // 64530 - 1006
77 | Serial.println("Red");
78 | break;
79 | case 0xFF00: //20839-22851
80 | Serial.println("Green");
81 | break;
82 | case 0xFF: // 42685-44697
83 | Serial.println("blue");
84 | break;
85 | case 0xFFFF00: // 10902-10944
86 | Serial.println("yellow");
87 | break;
88 | case 0xFFFF: // 32747 - 32789
89 | Serial.println("cyan");
90 | break;
91 | case 0xFF00FF: // 54592-54634
92 | Serial.println("magenta");
93 | break;
94 | default:
95 | Serial.println();
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/WS281x/WS281xColorOrderTester/WS281xColorOrderTester.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x Test with Adafruit_NeoPixel library
3 |
4 | This sketch turns on all the LEDs first blue, then green, then red, then white.
5 |
6 | Change pixelCount to the number of LEDs in your string.
7 |
8 | Note: if you don't plan to use an RGBW string, change NEO_GRBW to NEO_GRB
9 | and change " if (color < 0xFF000000) {" to " if (color < 0xFF0000) {"
10 |
11 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
12 |
13 | created 31 Jan 2017
14 | modified 27 May 2023
15 | by Tom Igoe
16 | */
17 | #include
18 |
19 | const int neoPixelPin = 5; // control pin
20 | const int pixelCount = 8; // number of pixels
21 | int color = 0xFF; // blue
22 |
23 | // set up strip:
24 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
25 |
26 | void setup() {
27 | strip.begin(); // initialize pixel strip
28 | strip.clear(); // turn all LEDs off
29 | strip.show(); // refresh strip
30 | Serial.begin(9600);
31 | }
32 |
33 | void loop() {
34 | // loop over all the pixels:
35 | for (int pixel = 0; pixel < pixelCount; pixel++) {
36 | strip.setPixelColor(pixel, color); // set the color for this pixel
37 | strip.show(); // refresh the strip
38 | }
39 | delay(1000);
40 |
41 | // shift color by 8 bits each time through the loop:
42 | if (color < 0xFF000000) {
43 | color = color << 8; // shift to the next color
44 | } else {
45 | color = 0xFF; // when you've gone through all the colors, shift to the first.
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/WS281x/WS281xLoadTester/WS281xLoadTester.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x loadTest with Adafruit_NeoPixel library
3 |
4 | This sketch loops through all the pixels of a WS281x-compatible device
5 | one pixel at a time, adding color cumulatively, in the order:
6 | blue, green, red
7 |
8 | If your power supply is not sufficient to control all your LEDs,
9 | then this sketch will fail when the power supply reaches its maximum current.
10 |
11 | Change pixelCount to the number of LEDs in your string.
12 |
13 | Uses Adafruit's DotStar library: https://github.com/adafruit/Adafruit_NeoPixel
14 |
15 | created 17 Jun 2019
16 | modified 31 Jan 2022
17 | by Tom Igoe
18 | */
19 | #include
20 |
21 | const int neoPixelPin = 5; // control pin
22 | const int pixelCount = 7; // number of pixels
23 |
24 |
25 | // set up strip:
26 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW+ NEO_KHZ800);
27 |
28 | unsigned long color = 0xFF; // start with blue
29 |
30 | void setup() {
31 | strip.begin(); // initialize pixel strip
32 | strip.clear(); // turn all LEDs off
33 | strip.show(); // refresh strip
34 | }
35 |
36 | void loop() {
37 | // loop over all the pixels:
38 | for (int pixel = 0; pixel < pixelCount; pixel++) {
39 | strip.setPixelColor(pixel, color); // set the color for this pixel
40 | strip.show(); // refresh the strip
41 | delay(1000);
42 | }
43 |
44 | if (color >= 0xFFFFFF) { // if the color is greater than white (0xFF000000)
45 | color = 0xFF; // then set it back to blue
46 | } else {
47 | color = (color << 8) + 0xFF; // add the next color at full
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/WS281x/WS281xOneColor/WS281xOneColor.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x Test with Adafruit_NeoPixel library
3 |
4 | This sketch sets the pixels to one color. Change pixelCount
5 | to the number of LEDs in your string. It listens on the serial port
6 | for a 4 comma-separated values: white, red, green, blue, all 0-255.
7 |
8 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
9 |
10 | created 17 Jun 2019
11 | modified 27 May 2023
12 | by Tom Igoe
13 | */
14 | #include
15 |
16 | const int neoPixelPin = 5; // control pin
17 | const int pixelCount = 8; // number of pixels
18 |
19 | // set up strip:
20 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
21 |
22 | void setup() {
23 | strip.begin(); // initialize pixel strip
24 | strip.clear(); // turn all LEDs off
25 | strip.show(); // refresh strip
26 | }
27 |
28 | void loop() {
29 | unsigned long color = 0;
30 |
31 | // incoming serial string should be 4 comma-separated values:
32 | // white, red, green, blue. All 0-255:
33 | if (Serial.available()) {
34 | int white = Serial.parseInt();
35 | int red = Serial.parseInt();
36 | int green = Serial.parseInt();
37 | int blue = Serial.parseInt();
38 | // shift all the colors int one variable to set pixels:
39 | color = (white << 24) + (red << 16) + (green << 8) + blue;
40 | Serial.println(color, HEX);
41 | }
42 |
43 | // loop over all the pixels:
44 | for (int pixel = 0; pixel < pixelCount; pixel++) {
45 | strip.setPixelColor(pixel, color); // set the color for this pixel
46 | }
47 |
48 | strip.show(); // refresh the strip
49 | }
50 |
--------------------------------------------------------------------------------
/WS281x/WS281xRGBFade/WS281xRGBFade.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x RGB fade with Adafruit_NepPixel library
3 |
4 | This sketch fades a string of WS281x LEDs by simply counting
5 | down from 0xFFFFFF (white) to 0x000000 (off). It makes no consideration
6 | for the different colors.
7 |
8 | Change pixelCount to the number of LEDs in your string.
9 |
10 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
11 |
12 | created 17 Jun 2019
13 | modified 27 May 2023
14 | by Tom Igoe
15 | */
16 | #include
17 |
18 | const int neoPixelPin = 5; // control pin
19 | const int pixelCount = 7; // number of pixels
20 |
21 | // set up strip:
22 | // set up strip:
23 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
24 |
25 | unsigned long color = 0xFFFFFF;
26 | void setup() {
27 | strip.begin(); // initialize pixel strip
28 | strip.clear(); // turn all LEDs off
29 | strip.show(); // refresh strip
30 | }
31 |
32 | void loop() {
33 | // loop over all the pixels:
34 | for (int pixel = 0; pixel < pixelCount; pixel++) {
35 | // set the color for each pixel:
36 | strip.setPixelColor(pixel, color);
37 | }
38 | // refresh the strip:
39 | strip.show();
40 | // decrement the color:
41 | color--;
42 | // if it reaches 0, set it back to 0xFFFFFF:
43 | if (color == 0) color = 0xFFFFFF;
44 | }
45 |
--------------------------------------------------------------------------------
/WS281x/WS281xTester/WS281xTester.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x Test with Adafruit_NeoPixel library
3 |
4 | This sketch loops through all the pixels of a WS281x-compatible device
5 | one pixel at a time, and one color at a time, in the order:
6 | blue, green, red, white
7 |
8 | Change pixelCount to the number of LEDs in your string.
9 |
10 | Note: if you don't plan to use an RGBW string, change NEO_GRBW to NEO_GRB
11 | and the line "if (color > 0xFF000000) {" to "if (color > 0xFF0000) {"
12 |
13 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
14 |
15 | created 31 Jan 2017
16 | modified 17 Jun 2019
17 | by Tom Igoe
18 | */
19 | #include
20 |
21 | const int neoPixelPin = 5; // control pin
22 | const int pixelCount = 8; // number of pixels
23 |
24 | // set up strip:
25 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW+NEO_KHZ800);
26 | unsigned long color = 0xFF; // start with blue
27 |
28 | void setup() {
29 | strip.begin(); // initialize pixel strip
30 | strip.clear(); // turn all LEDs off
31 | strip.show(); // refresh strip
32 | delay(2000); // delay 2 seconds before starting
33 | }
34 |
35 | void loop() {
36 | // loop over all the pixels:
37 | for (int pixel = 0; pixel < pixelCount; pixel++) {
38 | strip.setPixelColor(pixel, color); // set the color for this pixel
39 | if (pixel > 0) {
40 | strip.setPixelColor(pixel - 1, 1); // turn off the last pixel
41 | }
42 | strip.show(); // refresh the strip
43 | delay(250); // 1/4 sec delay after each pixel
44 | }
45 |
46 | if (color >= 0xFF000000) { // if the color is greater than white (0xFF000000)
47 | color = 0xFF; // then set it back to blue
48 | } else {
49 | color = color << 8; // shift the FF (255) to the next color
50 | }
51 | strip.clear(); // clear the strip at the end of a color
52 | }
53 |
--------------------------------------------------------------------------------
/WS281x/WS281x_HSV_Tester/WS281x_HSV_Tester.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WS281x HSV Test with Adafruit_NeoPixel library
3 |
4 | This sketch tests the HSV functionality of the Adafruit_NeoPixel library
5 |
6 | Change pixelCount to the number of LEDs in your string.
7 |
8 | Uses Adafruit's NeoPixel library: https://github.com/adafruit/Adafruit_NeoPixel
9 |
10 | created 31 Jan 2017
11 | modified 31 Jan 2022
12 | by Tom Igoe
13 | */
14 | #include
15 |
16 | const int neoPixelPin = 5; // control pin
17 | const int pixelCount = 7; // number of pixels
18 |
19 | // set up strip:
20 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_GRBW + NEO_KHZ800);
21 |
22 | unsigned int hue = 0;
23 | int sat = 255;
24 | int intensity = 255;
25 |
26 | void setup() {
27 | Serial.begin(9600);
28 | strip.begin(); // initialize pixel strip
29 | strip.clear(); // turn all LEDs off
30 | strip.show(); // refresh strip
31 | }
32 |
33 | void loop() {
34 | // increment hue and rollover at 65535:
35 | hue++;
36 | hue %= 65535;
37 | // loop over all the pixels:
38 | for (int pixel = 0; pixel < pixelCount; pixel++) {
39 | // get RGB from HSV:
40 | unsigned long color = strip.ColorHSV(hue, sat, intensity);
41 | // do a gamma correction:
42 | unsigned long correctedColor = strip.gamma32(color);
43 | strip.setPixelColor(pixel, color); // set the color for this pixel
44 | Serial.println(hue);
45 | }
46 | strip.show(); // refresh the strip
47 | }
48 |
--------------------------------------------------------------------------------
/WS281x/WWA_WS281x_AdvancedMixer/WWA_WS281x_AdvancedMixer.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WWA WS281x strip using ColorConverter
3 |
4 | This sketch uses the Adafruit NeoPixel library to drive a strip of
5 | WS281x warm white-cool white-amber (WWA) LEDs
6 | and the ColorConverter library to do warm-to-cool conversion.
7 | A potentimeter on A0 fades the strip from warm to cool.
8 |
9 | created 11 Feb 2019
10 | modified 17 June 2019
11 | by Tom Igoe
12 | */
13 |
14 | #include
15 | #include
16 | #include
17 |
18 | const int neoPixelPin = 4;
19 | const int pixelCount = 20; // number of pixels
20 |
21 | // set up strip. Note BRG setting. You might have to experiment
22 | // to determine which way your LEDs are wired:
23 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_BRG + NEO_KHZ800);
24 | ColorConverter converter;
25 |
26 | int mix[] = {0, 100, 100}; // array for color temp, saturation, intensity
27 |
28 | Encoder knob(0, 1);
29 | int lastPosition = 0;
30 | const int buttonPin = 5;
31 | int lastButtonState = HIGH;
32 |
33 | int currentProp = 0;
34 |
35 | void setup() {
36 | Serial.begin(9600);
37 | strip.begin(); // initialize pixel strip
38 | strip.clear(); // turn all LEDs off
39 | strip.show(); // update strip
40 | pinMode(buttonPin, INPUT_PULLUP);
41 | knob.write(0);
42 | }
43 |
44 | void loop() {
45 | // read the sensors:
46 | int buttonState = digitalRead(buttonPin);
47 | int encoderPos = knob.read();
48 |
49 | // if the button has changed:
50 | if (buttonState != lastButtonState) {
51 | // debounce the button:
52 | delay(10);
53 | // if button is pressed:
54 | if (buttonState == LOW) {
55 | // increment currentProp and make sure it
56 | // stays in the 0-2 range:
57 | currentProp++;
58 | currentProp %= 3;
59 | }
60 | // save current state for next time through the loop:
61 | lastButtonState = buttonState;
62 | }
63 |
64 | // calculate direction of change of the encoder:
65 | int encoderChange = (encoderPos - lastPosition) / 4;
66 |
67 | if (encoderChange != 0) {
68 | // save current position for next time
69 | lastPosition = encoderPos;
70 | // add the change to the value of the current property:
71 | mix[currentProp] += encoderChange;
72 | // constrain differently depending on whether it's CT, sat, ir intensity:
73 | switch (currentProp) {
74 | // constraon the result to 0 - 240
75 | // (not 360, because you want warm to cool, not
76 | // warm to cool and back:
77 | case 0: //CT
78 | mix[currentProp] = constrain(mix[currentProp], 0, 240);
79 | break;
80 | // constrain sat and intensity to 0-100:
81 | case 1: // sat
82 | case 2: // intensity
83 | mix[currentProp] = constrain(mix[currentProp], 0, 100);
84 | break;
85 | }
86 | Serial.println(String(currentProp) + "," + String(mix[currentProp]));
87 |
88 | // create a single color from hue, sat, intensity:
89 | RGBColor color = converter.HSItoRGB(mix[0], mix[1], mix[2]);
90 |
91 | // change the ColorConverter labels to something you can use:
92 | int amber = color.red;
93 | int warm = color.green;
94 | int cool = color.blue;
95 |
96 | // loop over all the pixels:
97 | for (int pixel = 0; pixel < pixelCount; pixel++) {
98 | // set the color for this pixel:
99 | strip.setPixelColor(pixel, amber, warm, cool);
100 | Serial.println(String(amber) + "," + String(warm) + "," + String(cool));
101 | }
102 | // update the strip
103 | strip.show();
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/WS281x/WWA_WS281x_Mixer/WWA_WS281x_Mixer.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WWA WS281x strip using ColorConverter
3 |
4 | This sketch uses the Adafruit NeoPixel library to drive a strip of
5 | WS281x warm white-cool white-amber (WWA) LEDs
6 | and the ColorConverter library to do warm-to-cool conversion.
7 | A potentimeter on A0 fades the strip from warm to cool.
8 |
9 | The arrangement of LEDs was discovered through trial and error.
10 |
11 | created 11 Feb 2019
12 | modified 17 June 2019
13 | by Tom Igoe
14 | */
15 | #include
16 | #include
17 |
18 | const int neoPixelPin = 4;
19 | const int pixelCount = 20; // number of pixels
20 |
21 | // set up strip. Note BRG setting. You might have to experiment
22 | // to determine which way your LEDs are wired:
23 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(pixelCount, neoPixelPin, NEO_BRG + NEO_KHZ800);
24 | ColorConverter converter;
25 |
26 | int s = 100; // saturation
27 | int i = 100; // intensity
28 |
29 | void setup() {
30 | Serial.begin(9600);
31 | strip.begin(); // initialize pixel strip
32 | strip.clear(); // turn all LEDs off
33 | strip.show(); // update strip
34 | }
35 |
36 | void loop() {
37 | int sensorReading = analogRead(A0);
38 | // map the result from 0 - 240
39 | // (not 360, because you want warm to cool, not
40 | // warm to cool and back:
41 | int mix = map(sensorReading, 0, 1023, 0, 240);
42 | // create a single color from hue, sat, intensity:
43 | RGBColor color = converter.HSItoRGB(mix, s, i);
44 |
45 | // change the ColorConverter labels to something you can use:
46 | int amber = color.red;
47 | int warm = color.green;
48 | int cool = color.blue;
49 |
50 | // loop over all the pixels:
51 | for (int pixel = 0; pixel < pixelCount; pixel++) {
52 | // set the color for this pixel:
53 | strip.setPixelColor(pixel, amber, warm, cool);
54 | }
55 | // update the strip
56 | strip.show();
57 | }
58 |
--------------------------------------------------------------------------------
/WarmCoolLEDStrip/WarmCoolLEDStrip.ino:
--------------------------------------------------------------------------------
1 | /*
2 | Warm-Cool lamp using 12V LED strips
3 |
4 | Reads a potentiometer on pin A1, uses it to set a ratio of warm to cool
5 | controls two LED channels on pins 4 and 5
6 |
7 | Circuit:
8 | potentiometer on pin A1
9 | 12V LEDs on pins 4 and 5
10 |
11 | created 19 Jan 2019
12 | modified 16 Feb 2020
13 | by Tom Igoe
14 | */
15 | void setup() {
16 | // initialize serial communication:
17 | Serial.begin(9600);
18 | }
19 |
20 | void loop() {
21 | // read potentiometer, use it to set ratio of warm to cool:
22 | int potReading = analogRead(A0) / 4;
23 | int warm = 255 - potReading;
24 | int cool = potReading;
25 |
26 | // PWM the LED channels:
27 | analogWrite(5, cool);
28 | analogWrite(6, warm);
29 | }
30 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-minimal
2 | title: Light Projects
3 | description: A collection LED control examples for Arduino
4 | codeurl: https://github.com/tigoe/LightProjects/tree/main
--------------------------------------------------------------------------------
/_includes/nav.html:
--------------------------------------------------------------------------------
1 |
2 |
50 |
51 | {% if site.google_analytics %}
52 |
60 | {% endif %}
61 |
62 |
--------------------------------------------------------------------------------
/dream-machine/dreamMachine001/dreamMachine001.ino:
--------------------------------------------------------------------------------
1 | /*
2 | An attempt to create Brion Gysin's Dreammachine device
3 | based on https://www.theguardian.com/artanddesign/2022/may/09/dreamachine-review-as-close-to-state-funded-psychedelic-drugs-as-you-can-get
4 | Inspiration by Adam Greenfield
5 |
6 | Flashing light between 8 and 13 flashes a second.
7 | Assumes 50% duty cycle
8 |
9 | created 9 May 2022
10 | by Tom Igoe
11 | */
12 |
13 | int ledState = LOW; // state of the light
14 | const int ledPin = 2; // output pin for light control
15 | long lastChange = 0; // timestamp for light change
16 | // flashing rates, in ms:
17 | const float fast = 1000 / 13;
18 | const float slow = 1000 / 8;
19 |
20 | void setup() {
21 | // set pin mode of the output pin:
22 | pinMode(ledPin, OUTPUT);
23 | }
24 |
25 | void loop() {
26 | // read potentiometer:
27 | int knob = analogRead(A0);
28 | // map sensor values to flashing rates
29 | // at 50% duty cycle (/2):
30 | float interval = map(knob, 0, 1023, fast, slow) / 2;
31 | // if duty cycle interval has passed,
32 | // change light state:
33 | if (millis() - lastChange > interval) {
34 | digitalWrite(ledPin, ledState);
35 | // if HIGH, go LOW and vice versa:
36 | ledState = !ledState;
37 | // timestamp for next change:
38 | lastChange = millis();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/img/12VDC_LED_MOSFET_circuit_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/12VDC_LED_MOSFET_circuit_bb.png
--------------------------------------------------------------------------------
/img/12VDC_LED_MOSFET_lamp_circuit_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/12VDC_LED_MOSFET_lamp_circuit_bb.png
--------------------------------------------------------------------------------
/img/12VDC_LED_TIP120_Nano_lamp_circuit_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/12VDC_LED_TIP120_Nano_lamp_circuit_bb.png
--------------------------------------------------------------------------------
/img/12VDC_LED_TIP120_circuit_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/12VDC_LED_TIP120_circuit_bb.png
--------------------------------------------------------------------------------
/img/12VDC_LED_TIP120_lamp_circuit_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/12VDC_LED_TIP120_lamp_circuit_bb.png
--------------------------------------------------------------------------------
/img/12VDC_Nano_LED_MOSFET_lamp_circuit_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/12VDC_Nano_LED_MOSFET_lamp_circuit_bb.png
--------------------------------------------------------------------------------
/img/212069_Fig_07.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/212069_Fig_07.jpg
--------------------------------------------------------------------------------
/img/APA102C_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/APA102C_bb.png
--------------------------------------------------------------------------------
/img/AS7341_fritzing_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/AS7341_fritzing_bb.png
--------------------------------------------------------------------------------
/img/AS7341_fritzing_schem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/AS7341_fritzing_schem.png
--------------------------------------------------------------------------------
/img/MKR_MOSFET.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/MKR_MOSFET.fzz
--------------------------------------------------------------------------------
/img/SimpleFadeGraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/SimpleFadeGraph.png
--------------------------------------------------------------------------------
/img/SineFadeGraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/SineFadeGraph.png
--------------------------------------------------------------------------------
/img/WS281x_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/WS281x_bb.png
--------------------------------------------------------------------------------
/img/arduino-ide-boards-manager.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/arduino-ide-boards-manager.jpg
--------------------------------------------------------------------------------
/img/arduino-ide-boards-manager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/arduino-ide-boards-manager.png
--------------------------------------------------------------------------------
/img/arduino-ide-examples-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/arduino-ide-examples-menu.png
--------------------------------------------------------------------------------
/img/arduino-ide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/arduino-ide.png
--------------------------------------------------------------------------------
/img/attiny85_mosfet.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/attiny85_mosfet.fzz
--------------------------------------------------------------------------------
/img/attiny85_mosfet_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/attiny85_mosfet_bb.png
--------------------------------------------------------------------------------
/img/candles/IDE-20-board-port-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/IDE-20-board-port-menu.png
--------------------------------------------------------------------------------
/img/candles/LED_ring.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/LED_ring.jpg
--------------------------------------------------------------------------------
/img/candles/LabTemplateNanoShort_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/LabTemplateNanoShort_bb.png
--------------------------------------------------------------------------------
/img/candles/LabTemplate_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/LabTemplate_bb.png
--------------------------------------------------------------------------------
/img/candles/MKRZero-Neopixel-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/MKRZero-Neopixel-1.png
--------------------------------------------------------------------------------
/img/candles/MKRZero-e1530487647686.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/MKRZero-e1530487647686.jpg
--------------------------------------------------------------------------------
/img/candles/MKRZero_breadboard-177x300.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/MKRZero_breadboard-177x300.jpg
--------------------------------------------------------------------------------
/img/candles/MKR_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/MKR_bb.png
--------------------------------------------------------------------------------
/img/candles/Tools_menu-1024x450.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/Tools_menu-1024x450.png
--------------------------------------------------------------------------------
/img/candles/Uno_w_LED_ring_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/Uno_w_LED_ring_bb.png
--------------------------------------------------------------------------------
/img/candles/arduino-nano-33-iot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/arduino-nano-33-iot.jpg
--------------------------------------------------------------------------------
/img/candles/breadboard_short-e1532116106284-150x150-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/breadboard_short-e1532116106284-150x150-1.jpeg
--------------------------------------------------------------------------------
/img/candles/hookup_wires.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/hookup_wires.jpg
--------------------------------------------------------------------------------
/img/candles/leds1-300x200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/leds1-300x200.jpg
--------------------------------------------------------------------------------
/img/candles/microUSB.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/microUSB.jpg
--------------------------------------------------------------------------------
/img/candles/nano-neopixel-ring_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/nano-neopixel-ring_bb.png
--------------------------------------------------------------------------------
/img/candles/upload_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/candles/upload_button.png
--------------------------------------------------------------------------------
/img/chartjs_spectrometer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/chartjs_spectrometer.png
--------------------------------------------------------------------------------
/img/cie1931Graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/cie1931Graph.png
--------------------------------------------------------------------------------
/img/exponentialCurveGraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/exponentialCurveGraph.png
--------------------------------------------------------------------------------
/img/fading-chromaticity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fading-chromaticity.png
--------------------------------------------------------------------------------
/img/fritzing_files/12VDC_ATTiny_LED_MOSFET_lamp_circuit_bb.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/12VDC_ATTiny_LED_MOSFET_lamp_circuit_bb.afdesign
--------------------------------------------------------------------------------
/img/fritzing_files/12VDC_LED_MOSFET_circuit.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/12VDC_LED_MOSFET_circuit.fzz
--------------------------------------------------------------------------------
/img/fritzing_files/12VDC_LED_MOSFET_potentiometer_circuit.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/12VDC_LED_MOSFET_potentiometer_circuit.fzz
--------------------------------------------------------------------------------
/img/fritzing_files/12VDC_LED_MOSFET_switch_circuit.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/12VDC_LED_MOSFET_switch_circuit.fzz
--------------------------------------------------------------------------------
/img/fritzing_files/12VDC_LED_MOSFET_switch_circuit_2.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/12VDC_LED_MOSFET_switch_circuit_2.fzz
--------------------------------------------------------------------------------
/img/fritzing_files/12VDC_LED_TIP120_circuit.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/12VDC_LED_TIP120_circuit.fzz
--------------------------------------------------------------------------------
/img/fritzing_files/12VDC_Nano_LED_MOSFET_lamp_circuit_bb.afdesign:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/12VDC_Nano_LED_MOSFET_lamp_circuit_bb.afdesign
--------------------------------------------------------------------------------
/img/fritzing_files/555_mosfet_bb.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/fritzing_files/555_mosfet_bb.fzz
--------------------------------------------------------------------------------
/img/front-terminal-block.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/front-terminal-block.jpg
--------------------------------------------------------------------------------
/img/itp-arch-red.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/itp-arch-red.jpg
--------------------------------------------------------------------------------
/img/itp-arch-sky.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/itp-arch-sky.jpg
--------------------------------------------------------------------------------
/img/led-strip-5-way-control.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/led-strip-5-way-control.png
--------------------------------------------------------------------------------
/img/mkr1000-inside-phone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/mkr1000-inside-phone.jpg
--------------------------------------------------------------------------------
/img/mkr1000-rotary-phone_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/mkr1000-rotary-phone_bb.png
--------------------------------------------------------------------------------
/img/nano33-light-sensor-east.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/nano33-light-sensor-east.jpg
--------------------------------------------------------------------------------
/img/nano33-light-sensor-west.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/nano33-light-sensor-west.jpg
--------------------------------------------------------------------------------
/img/nano33_light-sensor_bb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/nano33_light-sensor_bb.png
--------------------------------------------------------------------------------
/img/nano33_light-sensor_schem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/nano33_light-sensor_schem.png
--------------------------------------------------------------------------------
/img/rotary-dial-back.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/rotary-dial-back.jpg
--------------------------------------------------------------------------------
/img/rotary-phone-mounted.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/rotary-phone-mounted.jpg
--------------------------------------------------------------------------------
/img/rotary_phone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/rotary_phone.jpg
--------------------------------------------------------------------------------
/img/rotarydImmer/nano33-inside-phone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/rotarydImmer/nano33-inside-phone.jpg
--------------------------------------------------------------------------------
/img/side-terminal-block.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/side-terminal-block.jpg
--------------------------------------------------------------------------------
/img/sputnik.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/sputnik.jpg
--------------------------------------------------------------------------------
/img/squareLawGraph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/img/squareLawGraph.png
--------------------------------------------------------------------------------
/install-arduino-ide.md:
--------------------------------------------------------------------------------
1 | # Installing the Arduino IDE for Light Projects
2 |
3 | Most of the projects in this repository use the Arduino Integrated Development Environment (IDE) and various models of Arduino. This page explains how to install the IDE and how to install board definitions and libraries.
4 |
5 | ## Download the IDE
6 |
7 | The Arduino IDE can be downloaded for MacOS, Windows, or Linux at [this link](https://www.arduino.cc/en/software). All of the code examples in this repository will work with version 2.1.0 or later. Download the installer package for your OS, unzip it, and run the installer. It will install the program in your Applications folder.
8 |
9 | ## Open the IDE and Install Board Definitions
10 |
11 | After the IDE is installed, open it and you'll get a new blank sketch. Figure 1 shows the IDE with a blank sketch.
12 |
13 | 
14 | _Figure 1. The Arduino IDE._
15 |
16 | Click on the Boards Manager icon (the second icon on the left) to open the Boards Manager, then type in the name of your board to filter the list of boards, then click the Install button when you find your board type. Figure 2 below shows the board type for the Nano Every.
17 |
18 | 
19 | _Figure 2. The Arduino IDE with Boards Manager open._
20 |
21 | ## Install Libraries
22 |
23 | To install any libraries needed, click the Libraries icon (third icon on the left) to open the Library Manager. The Library Manager works like the Boards manager: type in the library you want to filter the list. For the [Electronic Candles](candles.md) project in this repository, you'll need the Adafruit NeoPixel library, or Adafruit DMA NeoPixel library if you're using a Nano 33 IoT or other SAMD-based board.
24 |
25 | When you install libraries, they usually come with examples. Click on the File menu, choose Examples, and navigate to your library and you'll see the relevant examples. Figure 3 shows the File menu open to the Adafruit NeoPixel examples.
26 |
27 | 
28 | _Figure 3. The Arduino IDE with File menu open to the examples._
29 |
30 | Once you've installed the IDE, your board definitions, and your libraries, you're ready to begin working.
--------------------------------------------------------------------------------
/inventory.md:
--------------------------------------------------------------------------------
1 | # Light Parts
2 |
3 | This is a list of parts used in building this repository, and a few more that might be useful.
4 |
5 | ## Programmable LEDs (WS2812b, SK6812, APA102C or equivalent):
6 | * [WS281x modules](http://www.ledlightinghut.com/8-sk6812-rgbw-5050-digital-led-ring.html) - There are many variations on this. It's useful to keep one of the small 4-8 LED modules handy for testing code, even if you're planning to use longer strips in your actual project.
7 | * [ SK6812 RGBW Digital Addressable LED Strip, 60LEDs/m, 5VDC](http://www.ledlightinghut.com/sk6812-4-in-1-rgbw-led-strip.html) - These typically come in 60 LEDs/m or 144 LEDs/m
8 | * [Programmable LED strips, WWA, 60 LEDs (or greater) per meter, 5V](http://www.ledlightinghut.com/144led-sk6812-wwa-led-strip.html). These are WWA versions of the WS281x LEDs. The same code will control them, but you get tunable white instead of colors.
9 |
10 | ## LED Strips (not individually addressable):
11 | * [Tunable White LED Strip Light/Tape Light - 12V - 350 Lumens/ft ](https://www.superbrightleds.com/moreinfo/flexible-led-strip-lights/3528-tunable-white-led-strip-lighttape-light-24v-ip20-350-lumensft/5497/12019/). Strips like these come in variable brightness as well as length and density.
12 | * [RGBW LED Strip Lights, 245 Lumens/ft](https://www.superbrightleds.com/moreinfo/flexible-led-strip-lights-color-changing/outdoor-rgbw-led-strip-lights-weatherproof-12v-led-tape-light-w-white-and-multicolor-leds-245-lumensft/1711/)
13 | * [Low Profile Aluminum LED Strip Channel - Surface Mount LED Extrusion - KLUS MICRO-ALU Series](https://www.superbrightleds.com/moreinfo/aluminum-channels/klus-b1888_k7-micro-alu-series-surface-mount-black-anodized-aluminum-led-profile-housing/2039/4738/) - These are used to mount LED strips. They come with a diffuser lens which fits in the channel.
14 | * [Klus 0945 - HR-ALU series LED Profile Heavy Duty Frosted Lens](https://www.superbrightleds.com/moreinfo/housing-accessories/klus-0945--hr-alu-series-led-profile-heavy-duty-frosted-lens/961/2363/) - These are a heavier diffuser than the one that comes with the strips above.
15 |
16 | ## DC power supplies:
17 | * [Wall-Mounted AC Adapter - 12 VDC Power Supply - 12-36W](https://www.superbrightleds.com/moreinfo/power-supplies-accessories/12v-dc-cps-series-power-supply/4273/9569/) - Many standard 12V DC supplies will do the job. This will pair with a 2.1mm I.D./5.5mm O.D. DC jack.
18 | * [Mean Well LED Switching Power Supply, AP Series 12-35W Single Output LED Power Supply, 12V DC](https://www.superbrightleds.com/moreinfo/power-supplies/12vdc-mean-well-led-power-supply--apv-series/1748/5179/) - I usually get these because they're a constant current supply. They are good for powering LED strips. Get them with power cord or get a power cord that will work from a local electrical supplier.
19 |
20 | ## Connectors:
21 | * [DC Barrel Jack Adapter, Male](https://www.superbrightleds.com/moreinfo/bar-strip-connectors/cps-f2st-female-standard-barrel-connector-to-screw-terminal-adapter/856/) - These are for making connections to 2.1mm I.D./5.5mm OD D.C. power supplies like the one above.
22 | * [DC Barrel Jack Adapter, Female](https://www.superbrightleds.com/moreinfo/bar-strip-connectors/cps-f2st-female-standard-barrel-connector-to-screw-terminal-adapter/856/) - To connect to the item above
23 |
24 | ## LED standard-base 12V bulbs (G6.35 2-pin base):
25 | * [Bi-Pin Socket for GU5.3/G4/GX5.3/GY6.35/GZ4 Base Bulbs ](https://www.superbrightleds.com/moreinfo/household-bulb-sockets-adapters/mr16-socket-mr11-bi-pin-socket-for-gu53g4gx53gy635gz4-base-bulbs/499/2027/) - This socket works with the lamps below, and many others.
26 | * [GY6.35 LED Landscape Light Bulb - 40 Watt Equivalent - Bi-Pin LED Bulb - 450 Lumens ](https://www.superbrightleds.com/moreinfo/landscape-bulbs/gy635-led-landscape-light-bulb-40-watt-equivalent-bi-pin-led-bulb-450-lumens/4539/10110/)
27 | * [GY6.35 LED Landscape Light Bulb - 40 Watt Equivalent - Bi-Pin LED Bulb - 275 Lumens ](https://www.superbrightleds.com/moreinfo/landscape-bulbs/gy635-led-landscape-light-bulb-40-watt-equivalent-bi-pin-led-bulb-275-lumens/4541/10111/)
28 |
29 | ## Components
30 | **Transistors**. For most 12-24V sources, you'll need transistors to control them from a microcontroller. The following transistors have been tested with the examples here:
31 | * [IRLB8721](https://www.digikey.com/products/en?keywords=IRLB8721PBF) - A MOSFET that operates well on 3.3V. Here's its [data sheet](https://www.infineon.com/dgdl/irlb8721pbf.pdf?fileId=5546d462533600a40153566056732591). It can switch a load up to 30V and theoretically 60A with proper head dissipation.
32 | * [FQP30N06L](https://www.digikey.com/products/en?keywords=FQP30N06L) - Another 3.3V-tolerant MOSFET. Here's its [datasheet](https://cdn.sparkfun.com/datasheets/Components/General/FQP30N06L.pdf). This one can switch 60V and 30A.
33 | * [TIP120](https://www.digikey.com/product-detail/en/stmicroelectronics/TIP120/497-2539-5-ND/603564) This Darlington transistor, well-known in Arduino circles for switching motors, will also work for switching LED sources. Here's its [data sheet](https://www.st.com/content/ccc/resource/technical/document/datasheet/f9/ed/f5/44/26/b9/43/a4/CD00000911.pdf/files/CD00000911.pdf/jcr:content/translations/en.CD00000911.pdf). It requires a base resistor of about 1 kilohm for the examples used here.
34 |
35 | **Tangible controls**. These are included for example purposes only. You can use whatever input you want for your LED projects.
36 | * [pushbuttons](https://www.digikey.com/products/en?keywords=PRT-14460) - I like these ones because they fit on a breadboard well but are big enough to push in a satisfying way.
37 | * [rotary encoders](https://www.digikey.com/product-detail/en/bourns-inc/PEC12R-4225F-S0024/PEC12R-4225F-S0024-ND/4499648) - These are breadboard-friendly as well, if you bend the side tabs up.
38 | * [potentiometers](https://www.digikey.com/product-detail/en/tt-electronics-bi/P160KN2-4QC20B10K/987-1310-ND/2408887) - These are breadboard-friendly as well.
39 |
40 | ## Microcontrollers
41 | I tend to use the MKR series Arduinos a lot:
42 | * [MKR Zero](https://store.arduino.cc/usa/arduino-mkrzero)
43 | * [MKR 1010 WiFi/BLE](https://store.arduino.cc/usa/mkr-wifi-1010)
44 | * [MKR 1000 WiFi](https://store.arduino.cc/usa/arduino-mkr1000)
45 | * The [Nano 33 IoT](https://store.arduino.cc/usa/nano-33-iot) works well for these examples also
46 |
47 | ## Spectrometers
48 |
49 | The AS7341 is an 11-channel spectral sensor that can detect light levels in multiple frequencies from around 400nm to 900nm. It has 8 that sense light in visible spectrum; one channel in the near infrared spectrum; one clear channel without a filter; and one channel that detects 50Hz-60Hz light flicker.
50 |
51 | * [product page](https://ams.com/as7341)
52 | * [datasheet](https://ams.com/documents/20143/36005/AS7341_DS000504_3-00.pdf/5eca1f59-46e2-6fc5-daf5-d71ad90c9b2b).
53 | * [Sparkfun](https://www.sparkfun.com/products/17719) breakout board
54 | * [Adafruit](https://www.adafruit.com/product/4698) breakout board
55 | * [DFRobot](https://www.dfrobot.com/product-2132.html) breakout board
--------------------------------------------------------------------------------
/light-rendering-indices.md:
--------------------------------------------------------------------------------
1 | # Light Rendering Indices
2 |
3 | Here is a good [video](https://youtu.be/dIFIKTDDTtM) introducing the [Sekonic C-800-U spectrometer](https://sekonic.com/sekonic-c-800-u-spectromaster-spectrometer/). It describes the various light rendering indices that the spectrometer can display. Some of those indices are measured relative to the human eye's sensitivity, while others are measured relative to a camera's sensitivity. Below is a summary of the various light rendering indices that the meter can read.
4 |
5 | **CRI - Color Rendering Index** Based on the evaluation of [15 colors](https://www.waveformlighting.com/tech/cri-ra-test-color-samples-tcs) using the human eye as observer. First 8 colors are what the CRI number is based on, and the rest are for further evaluating the quality of the source. **CRI-extended** is an evaluation using all 15. CRI is a measure of how the human eye can accurately perceive colors under daylight conditions. Was originally designed to judger fluoresecent lamps.
6 |
7 | **TLCI - Television Lighting Consistency Index** The observer is a 3-chip broadcast camera. Uses 18 standard colors (from an X-Rite [Color Checker classic](https://www.xrite.com/categories/calibration-profiling/colorchecker-classic) card; leaves out the grays), and considers that some of the CRI colors (like R9) may be out of gamut for some cameras, so not a good index to use.
8 |
9 | **TLMF - Television Luminaire Matching Factor** A companion to TLCI, but based on using a reference luminant. Lets you evaluate two light sources relative to each other. Uses all colors from the [Color Checker Classic](https://www.xrite.com/categories/calibration-profiling/colorchecker-classic). Based on a pre-digital camera standard (Rec. 709), so may be out of date for some modern cameras.
10 |
11 | **TM-30-18** - The final number is the version number. Uses 99 colors, and gives the ability to judge how saturated colors will look under a source. Color graph lets you know if specific colors are over or under saturated, and what the tendency to shift is. Is expected to replace CRI. TM-30 Rf measures color fidelity, 0-100. Rg is a 60-140 scale, measuring saturation. 100 is proper saturation relative to the reference color. Below 100 is less saturated, above 100 is more saturated.
12 |
13 | More on TMC-30 from [Using TM-30 to Improve Your Lighting Design](https://www.ies.org/fires/using-tm-30-to-improve-your-lighting-design/):
14 |
15 | TM-30 is an attempt to improve upon the color rendering index (CRI) standard for evaluating how well a light source renders the color of an object lit by that source. It’s a standard for lighting manufacturers to use to describe and test their sources, and for designers to use in evaluating those sources in use. The major properties, or design intents of TM-30 are Preference, Vividness, and Fidelity.
16 |
17 | From the IES document above:
18 |
19 | “The Preference design intent captures subjective evaluations of pleasantness, naturalness, acceptability, and related qualities, and may be the dominant color rendition design intent in retail, office, hospitality, and residential lighting applications.”
20 |
21 | “The Vividness design intent captures subjective evaluations of color vividness, saturation, or vibrancy and may be the dominant color rendition design intent in some entertainment, display, or retail applications.”
22 |
23 | “The Fidelity design intent captures the match between a test light source and its reference. Note that fidelity should not be considered equivalent to naturalness, a common misconception.”
24 |
25 | There are multiple rendering indices in TM-30, each labeled Rsubscript (e.g. Rf for fidelity index, Rg for gamut index, etc). Each index offers a different measurement of the light source. Rf, for example, measures the average similarity between a source and the reference light source for all colors (this is where you may want to read up on [standard illuminants](https://sensing.konicaminolta.us/us/blog/understanding-standard-illuminants-in-color-measurement/)). Each design intent uses a subset of the rendering indices to articulate how well a light source meets the design intent.
26 |
27 | **SSI - Spectral Similarity Index** compares spectra between two light sources. Eliminates the observer. Unlike TMLF, it looks at spectral distribution from 380 - 670nm, with 10nm bins.
28 |
29 | For more on light and color rendering, see the [chromaticity](chromaticity.md) description in this repository.
30 |
31 |
--------------------------------------------------------------------------------
/patternMakers.md:
--------------------------------------------------------------------------------
1 | # Pattern Making With Code
2 |
3 | For many lighting projects, you need to generate patterns. Two common uses for this would be to make lampshade or diffuser patterns. These can often be cut on aser cutters or vinyl cutters, mills or other CNC machines. To cut a pattern on one of these, you'll likely need your pattern in a vector file. Most laser cutters will take an SVG file (scalable vector graphics), for example, and many vinyl cutters will take a DXF file (AutoCAD Drawing Interchange Format).
4 |
5 | One way to do this is to generate your pattern in software using a graphics-friendly programming environment like [Processing](https://processing.org/) or [p5.js](https://p5js.org/), then output the pattern to an SVG or DXF file. The challenge is finding a graphics-friendly environment that can generate vector graphics.
6 |
7 | Processing has two good libraries, one for [generating SVGs](https://processing.org/reference/libraries/svg/index.html) and one for [generating DXF](https://processing.org/reference/libraries/dxf/index.html) files. Here's [an example](https://github.com/tigoe/LightProjects/tree/main/PatternMakers/ProcessingVectorExport) of how to generate either file format from a very simple pattern. This Processing sketch will generate a new random pattern every two seconds, because the frameRate is set to 0.5. Here's a [slightly more complex example](https://github.com/tigoe/LightProjects/tree/main/PatternMakers/ProcessingCirclesVectorExport) that uses a circle packing algorithm to generate 500 circles every time you press the S or D key, and saves the pattern as SVG or DXF.
8 |
9 | p5.js doesn't have a DXF library, but it does have most current [SVG library for it](https://github.com/zenozeng/p5.js-svg) has been updated to work with p5 version 1.3.1, though seems fine with 1.4.0 as well Here's a version of the [circle packing sketch done in p5.js](https://tigoe.github.io/LightProjects/PatternMakers/p5CircleMaker/). Reload the page if you don't like the pattern. (here's the [code](https://github.com/tigoe/LightProjects/tree/main/PatternMakers/p5CircleMaker) ).Because p5.js is based on Processing, their APIs are similar enough that it's relatively easy to convert a pattern-making program from p5.js to Processing.
10 |
11 | Rune Madsen's [rune.js](https://runemadsen.github.io/rune.js/) is a pretty good tool for generating SVGs, and can be combined with p5.js as well.
--------------------------------------------------------------------------------
/qrcode.js:
--------------------------------------------------------------------------------
1 | // add a QR code of the URL when the page loads
2 | function getQrCode() {
3 | // make the QR code:
4 | let qr = qrcode(0, 'L');
5 | qr.addData(document.URL);
6 | qr.make();
7 | // create an image from it:
8 | let qrImg = qr.createImgTag(2, 8, "qr code of " + document.URL);
9 | // get the image and label:
10 | let label = document.getElementById('qrcode');
11 | label.innerHTML = qrImg;
12 | }
13 |
14 | // on page load, call the QR code function:
15 | document.addEventListener('DOMContentLoaded', getQrCode);
--------------------------------------------------------------------------------
/sky-lights.md:
--------------------------------------------------------------------------------
1 | # Sky Lights
2 |
3 | For a long time I've wanted to make indoor lights change with the light from the sky outside. Today, I had a free hour, so I built a test of it.
4 |
5 | I started with a Philips Hue system we have installed at ITP, and a couple of Arduino Nano 33 IoT modules. Nun and Name installed a few of the Hue lightstrips as an arch to welcome people to the floor, as shown in Figure 1. I decided that if I could get the arch to reflect the color temperature from the eastern and western sides of the building, that would be a decent test.
6 |
7 | 
8 |
9 | _Figure 1. Archway of light in the entrance of ITP's 370 Jay St space._
10 |
11 | I have a few TCS34725 light sensor boards from Adafruit, which I attached to two Nano 33 IoT boards to use as my networked light sensors. Figure 2 shows one on the southeast corner of the floor, late morning, and Figure 3 shows the one on the southwest corner of the floor, same time.
12 |
13 | 
14 |
15 | _Figure 2. Nano 33 IoT and light sensor in a southeast window. In the morning, this sensor will be hit with direct sunlight, but for most of the day, it will be in indirect sunlight._
16 |
17 | 
18 |
19 | _Figure 3. Nano 33 IoT and light sensor in a southwest window. In the afternoon, this sensor will be hit with direct sunlight, but for most of the day, it will be in indirect sunlight. The reflection off the pale yellow wall next door will probably lower the color temperature a little._
20 |
21 |
22 | ## The Circuit
23 |
24 | The circuit is fairly simple. The sensors draw their power from the Arduino's voltage, and the I2C pins of the sensor are connected to the I2C pins of the Arduino. The sensor's LED pin is wired to ground, because there is a built-in LED which will turn on otherwise. Figure 4 shows the breadboard layout, and Figure 5 shows the schematic.
25 |
26 | 
27 |
28 | _Figure 4. Nano 33 IoT and light sensor, breadboard view._
29 |
30 | 
31 |
32 | _Figure 4. Nano 33 IoT and light sensor, breadboard view._
33 |
34 | ## The Code
35 |
36 | I started my code using the [HueBlink example](https://github.com/arduino-libraries/ArduinoHttpClient/blob/main/examples/HueBlink/HueBlink.ino) from the [ArduinoHTTPClient](https://github.com/arduino-libraries/ArduinoHttpClient) library. I also referred to the Philips' documentation of the [Hue API](https://developers.meethue.com/). Knowing from the Hue documentation that the API uses JSON, I incorporated the [Arduino_JSON](https://github.com/arduino-libraries/Arduino_JSON) library to simplify the data formatting. I also used [Adafruit's TCS34725 library](https://github.com/adafruit/Adafruit_TCS34725) for the sensor.
37 |
38 | The pseudocode for my program is as follows:
39 |
40 | setup:
41 | * Initialize the sensor
42 | * Make the WiFi connection
43 |
44 | Loop:
45 | * If a minute has passed, and you're not in the middle of a request,
46 | * Read the sensor
47 | * Make a HTTP Request to the Hue hub
48 | * If you are in the middle of a request, wait for a reply
49 | * Print the reply
50 |
51 | Sensor reading:
52 | * Get the raw values
53 | * Convert to illuminance (lux) and color temperature (degrees Kelvin)
54 | * Convert illuminance to a 0-254 range for the Hue
55 | * Convert color temperature to the [Mired scale](https://en.wikipedia.org/wiki/Mired) for the Hue
56 |
57 | ## The HTTP Request
58 |
59 | The HTTP Request to the Hue hub to change a light looks like this:
60 | ````
61 | http://hue.hub.address/api/hueUserName/lights/lightNumber/state
62 | ````
63 |
64 | You need to [establish a hub user name for your device]ªhttps://github.com/tigoe/hue-control#connecting-to-your-hub first, and you need to know which light you are controlling and what properties it has. For more on that, see the [Hue developers site](https://developers.meethue.com/) or [these notes](https://github.com/tigoe/hue-control).
65 |
66 | The body of the PUT request looks like this:
67 | ````
68 | {"on": true,
69 | "bri": brightness,
70 | "ct": value,
71 | "transitiontime": fadeTime
72 | }
73 | ````
74 |
75 | * `on` is true or false
76 | * `bri` ranges from 0 - 254
77 | * `ct` is in the mired scale, which is 1000000/degrees Kelvin. It ranges from 153 - 500
78 | * `transitiontime` is in increments of 100ms
79 |
80 | There are other properties, depending on the type of Hue light you are using, but these are the important ones for this project.
81 |
82 | There are two important details to the HTTP request to the Hue hub: it has to be a PUT request, and the `Content-Type` has to be `application/json`. To make this easy, my sketch takes the sensor readings and puts them in a JSON object like so:
83 |
84 | ````
85 | body["bri"] = bri;
86 | body["on"] = true;
87 | body["ct"] = mired;
88 | body["transitiontime"] = sendInterval / 100;
89 | ````
90 |
91 | When it makes the request, it does it like so:
92 | ````
93 | // print latest reading, for reference:
94 | Serial.println(JSON.stringify(body));
95 | // make a String for the HTTP route:
96 | String route = "/api/" + hueUserName;
97 | route += "/lights/";
98 | route += lightNumber;
99 | route += "/state/";
100 |
101 | // make a PUT request:
102 | httpClient.put(route, contentType, JSON.stringify(body));
103 |
104 | ````
105 |
106 | Those are the important parts of the communication.
107 |
108 | That's basically it. The full sketch can be found [here](https://github.com/tigoe/LightProjects/tree/main/HueSkyLight). The biggest challenge was the compression of the light levels (brightness or illuminance), because the illuminance levels can go from below 20 in a dim space to over 35000 lux in full sunlight. The Hue has a 0-254 range for brightness. I derived my range by experimetation. I found that it went to about 2000 lux in indirect sunlight, so I constrained my result to 2000 points, mapped it to a 0-254 range to get a reading the Hue could accept. It's crude, but we'll see how it does over time.
--------------------------------------------------------------------------------
/spectrometers/AS7341/AS7341_Spectrometer_BLE/AS7341_Spectrometer_BLE.ino:
--------------------------------------------------------------------------------
1 | /*
2 | AS7341 sensor readings. Reads and sends the results via BLE
3 | as a CSV string. This version uses a non-blocking
4 | approach, using the startReading() and checkReadingProgress()
5 | functions. It's about 2 seconds between readings.
6 |
7 | Updated to use the BLE event handlers to manage the central connection.
8 |
9 | Library:
10 | http://librarymanager/All#Adafruit_AS7341
11 | http://librarymanager/All#ArduinoBLE
12 |
13 | created 18 Jun 2021
14 | modified 6 Mar 2022
15 | by Tom Igoe
16 | */
17 |
18 | #include
19 | #include
20 |
21 | // the uuid for the service. To make the uuids for the characteristics,
22 | // replace the 0000 with higher values (0001, etc):
23 | char serviceUuid[] = "9af01fc3-0000-44b8-8acc-f3ed7a225431";
24 | char characteristicUuid[] = "9af01fc3-0001-44b8-8acc-f3ed7a225431";
25 |
26 | // fill in your device name here"
27 | char bleName[] = "spectroscope";
28 |
29 | // instance of the sensor library:
30 | Adafruit_AS7341 as7341;
31 |
32 | // array to hold the raw readings:
33 | uint16_t readings[12];
34 | // readingString will be 50 bytes in length:
35 | const int readingLength = 50;
36 | // string for readings:
37 | String readingString;
38 |
39 | // BLE service and characteristic:
40 | BLEService spectroService(serviceUuid);
41 | // create sensor characteristic and allow remote device to get notifications:
42 | BLECharacteristic spectroCharacteristic(characteristicUuid, BLERead | BLENotify, readingLength);
43 |
44 | void setup() {
45 | // init serial, wait 3 secs for serial monitor to open:
46 | Serial.begin(9600);
47 | // if the serial port's not open, wait 3 seconds:
48 | if (!Serial) delay(3000);
49 | // use builtin LED for connection indicator:
50 | pinMode(LED_BUILTIN, OUTPUT);
51 |
52 | // readingString will need 50 bytes for all values:
53 | readingString.reserve(readingLength);
54 |
55 | pinMode(2, OUTPUT);
56 |
57 | if (!as7341.begin()) {
58 | digitalWrite(2, HIGH);
59 | Serial.println("Sensor not found, check wiring");
60 | while (true);
61 | }
62 | // set sensor integration time, etc:
63 | as7341.setATIME(35);
64 | as7341.setASTEP(10000);
65 | as7341.setGain(AS7341_GAIN_256X);
66 | // start a new reading cycle:
67 | as7341.startReading();
68 |
69 | // begin BLE initialization
70 | if (!BLE.begin()) {
71 | Serial.println("starting BLE failed");
72 | while (true);
73 | }
74 |
75 | // set the local name peripheral advertises:
76 | BLE.setLocalName(bleName);
77 | // print it:
78 | Serial.println(bleName);
79 | // set the UUID for the service this peripheral advertises:
80 | BLE.setAdvertisedService(spectroService);
81 | // add the characteristic:
82 | spectroService.addCharacteristic(spectroCharacteristic);
83 | // add the service:
84 | BLE.addService(spectroService);
85 |
86 | // assign event handlers for connected, disconnected to peripheral
87 | BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
88 | BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);
89 |
90 | // convert readingString to a char array set the value:
91 | spectroCharacteristic.writeValue(readingString.c_str());
92 |
93 | // start advertising
94 | BLE.advertise();
95 | Serial.println("Bluetooth device active, waiting for connections...");
96 | }
97 |
98 |
99 | void loop() {
100 | // poll for BLE events:
101 | BLE.poll();
102 |
103 | // if you get a good sensor reading:
104 | if (readSensor()) {
105 | // if a central subscribes to the spectro characteristic:
106 | if (spectroCharacteristic.subscribed()) {
107 | Serial.println("subscribed");
108 | // update the characteristic:
109 | spectroCharacteristic.writeValue(readingString.c_str());
110 | }
111 | Serial.println(readingString);
112 | }
113 | }
114 |
115 | void blePeripheralConnectHandler(BLEDevice central) {
116 | // central connected event handler
117 | Serial.print("Connected event, central: ");
118 | Serial.println(central.address());
119 | // turn on the builtin LED when connected:
120 | digitalWrite(LED_BUILTIN, HIGH);
121 | }
122 |
123 | void blePeripheralDisconnectHandler(BLEDevice central) {
124 | // central disconnected event handler
125 | Serial.print("Disconnected event, central: ");
126 | Serial.println(central.address());
127 | // turn off the builtin LED when disconnected:
128 | digitalWrite(LED_BUILTIN, LOW);
129 | }
130 |
131 | bool readSensor() {
132 | // clear the reading string:
133 | readingString = "";
134 | // check to see if readings are done
135 | // checkReadingProgress() is non-blocking:
136 |
137 | float result = as7341.checkReadingProgress();
138 | // if there is a reading, do corrections and parse it
139 | // into readingString:
140 | if (result) {
141 | // correction factors:
142 | // TODO: This is crude, and could be better with matrix math.
143 | // from https://ams.com/documents/20143/36005/AS7341_AN000633_1-00.pdf/fc552673-9800-8d60-372d-fc67cf075740
144 | // fig 10
145 |
146 | // Added in 0 for 4 and 5 since those channels are not used below.
147 | float corrections[] = {3.20, 3.00, 2.07, 1.30, 0, 0, 1.07, 0.93, 0.78, 0.71};
148 |
149 | // get the readings:
150 | as7341.getAllChannels(readings);
151 | // loop over the readings and print them out:
152 | for (int r = 0; r < 12; r++) {
153 | // skip 4 and 5 as they are repeats:
154 | if (r == 4 || r == 5) continue;
155 | float thisReading = as7341.toBasicCounts(readings[r]);
156 | // if this is one of the channels with a correction, apply it:
157 | if (r < 10) {
158 | thisReading = thisReading * corrections[r];
159 | }
160 | readingString += String(thisReading);
161 | if (r < 11) readingString += ",";
162 | }
163 |
164 | // start a new reading cycle:
165 | as7341.startReading();
166 | }
167 | return result;
168 | }
169 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/AS7341_Spectrometer_JSON_CCT/AS7341_Spectrometer_JSON_CCT.ino:
--------------------------------------------------------------------------------
1 | /*
2 | This sketch calculates lux, CCT, x, y, and z from the basic count readings of an
3 | AMS AS7341 sensor.
4 |
5 | This is a first draft and needs corrections. Readings do not correlate
6 | with a Sekonic C-800-U measuring next to the sensor.
7 | The sensor is diffused with a single sheet of Kimoto Optsaver L-57 diffuser.
8 |
9 | It sends the following out as a JSON object,
10 | using the Arduino_JSON library:
11 |
12 | {"415": // 0.0-1.0,
13 | "445":// 0.0-1.0,
14 | "480":// 0.0-1.0,
15 | "515":// 0.0-1.0,
16 | "555":// 0.0-1.0,
17 | "590":// 0.0-1.0,
18 | "630":// 0.0-1.0,
19 | "680":// 0.0-1.0,
20 | "910":// 0.0-1.0,
21 | "clear":// 0.0-1.0,
22 | "x":// 0.0-1.0,
23 | "y":// 0.0-1.0,
24 | "z":// 0.0-1.0,
25 | "lux":// lx,
26 | "cct":// cct,
27 | "flicker": // Hz}
28 |
29 | Libraries used:
30 | http://librarymanager/All#Arduino_JSON
31 | http://librarymanager/All#Adafruit_AS7341
32 | http://librarymanager/All#BasicLinearAlgebra
33 |
34 | See calibration methods doc and spreadsheet from AMS for more detail:
35 | https://ams.com/documents/20143/36005/AS7341_AN000633_1-00.pdf/fc552673-9800-8d60-372d-fc67cf075740
36 | https://ams.com/documents/20143/36005/AS7341_AD000198_3-00.xlsx
37 |
38 | created 2 May 2023
39 | by Tom Igoe
40 | */
41 |
42 | #include
43 | #include
44 | #include
45 |
46 | // use the BasicLinearAlgebra namespace.
47 | // You might need to add BLA:: before all the Matrix declarations
48 | using namespace BLA;
49 |
50 | // instance of the sensor library:
51 | Adafruit_AS7341 as7341;
52 | // make a JSON data object
53 | JSONVar lightReadings;
54 |
55 | // properties of the object (the channels of the sensor):
56 | int wavelengths[] = { 415, 445, 480, 515, 555, 590, 630, 680, 910 };
57 |
58 |
59 | // XYZ correction matrix from the spreadsheet at
60 | // https://ams.com/documents/20143/36005/AS7341_AD000198_3-00.xlsx
61 | // (tab: used correction values, O6:X8):
62 | Matrix<3, 10> corrMatrix = { 0.39814, 1.29540, 0.36956, 0.10902, 0.71942, 1.78180, 1.10110, -0.03991, -0.27597, -0.02347,
63 | 0.01396, 0.16748, 0.23538, 1.42750, 1.88670, 1.14200, 0.46497, -0.02702, -0.24468, -0.01993,
64 | 1.95010, 6.45490, 2.78010, 0.18501, 0.15325, 0.09539, 0.10563, 0.08866, -0.61140, -0.00938 };
65 |
66 |
67 | // offsets and correction factors, from the spreadsheet:
68 | // (tab: used Correction Values, O15:X15)):
69 | Matrix<10> offsets = { 0.00197, 0.00725, 0.00319, 0.00131, 0.00147, 0.00186, 0.00176, 0.00522, 0.00300, 0.00100 };
70 | // (tab: used Correction Values, O26:X26)):
71 | Matrix<10> correctionFactors = { 1.02811, 1.03149, 1.03142, 1.03125, 1.03390, 1.03445, 1.03508, 1.03359, 1.23384, 1.26942 };
72 |
73 | // sensor readings:
74 | Matrix<10> readings;
75 | // corrected sensor values:
76 | Matrix<10> correctedValues;
77 | // normalized sensor values:
78 | Matrix<10> normalizedValues;
79 |
80 |
81 | void setup() {
82 | // init serial, wait 3 secs for serial monitor to open:
83 | Serial.begin(9600);
84 | // if the serial port's not open, wait 3 seconds:
85 | if (!Serial) delay(3000);
86 |
87 | // check for the sensor:
88 | // initialize the sensor:
89 | if (!as7341.begin()) {
90 | Serial.println("Sensor not found, check wiring");
91 | while (true)
92 | ;
93 | }
94 |
95 | // set integration time:
96 | as7341.setATIME(35);
97 | as7341.setASTEP(10000);
98 | as7341.setGain(AS7341_GAIN_256X);
99 | // start a new reading cycle:
100 | as7341.startReading();
101 | }
102 |
103 | void loop() {
104 | // read the sensor, print if there are good values:
105 | if (as7341.checkReadingProgress()) {
106 | if (readSensor()) {
107 | calculateLightValues();
108 | Serial.println(lightReadings);
109 | }
110 | // start a new reading cycle:
111 | as7341.startReading();
112 | }
113 | }
114 |
115 | bool readSensor() {
116 | // check to see if readings are done
117 | uint16_t data[12];
118 |
119 | // if you get no readings, return:
120 | if (!as7341.readAllChannels(data)) return false;
121 | // there are ten channels, eight of which are visible bands
122 | // but two channels, the NIR and clear, repeat.
123 | // so we need to read from the 12 channels into 10 places of a data array:
124 | int c = 0;
125 |
126 | // loop over the readings and convert them:
127 | for (int r = 0; r < 12; r++) {
128 | // skip 4 and 5, they repeat other channels:
129 | if (r == 4 || r == 5) continue;
130 | // after channel 5, c = r - 2 because of the skip:
131 | readings(c) = as7341.toBasicCounts(data[r]);
132 | // increment channel counter:
133 | c++;
134 | }
135 | return true;
136 | }
137 |
138 | void calculateLightValues() {
139 | // XYZ readings:
140 | Matrix<3> XYZ;
141 | // zero out the corrected and normalized matrices:
142 | correctedValues.Fill(0);
143 | normalizedValues.Fill(0);
144 | // max reading for the normalization:
145 | float maxReading = 0.0;
146 | // loop over the readings and find the max:
147 | for (int r = 0; r < 10; r++) {
148 | correctedValues(r) = correctionFactors(r) * (readings(r) - offsets(r));
149 | maxReading = max(correctedValues(r), maxReading);
150 | normalizedValues(r) = correctedValues(r) / maxReading;
151 |
152 | // give the lightReadings JSON elements names from the properties array,
153 | // and give them values from the correctedValues matrix:
154 | String myWavelength = String(wavelengths[r]);
155 | if (r < 9) lightReadings[myWavelength] = correctedValues(r);
156 | // the clear reading comes last:
157 | else if (r == 9) lightReadings["clear"] = correctedValues(r);
158 | }
159 |
160 |
161 | // from spreadsheet , tab: demonstration calculations, J10-12:
162 | XYZ = corrMatrix * correctedValues;
163 | // from spreadsheet, tab: demonstration calculations, J14-16:
164 | float XYZSum = XYZ(0) + XYZ(1) + XYZ(2);
165 | float x = XYZ(0) / XYZSum;
166 | float y = XYZ(1) / XYZSum;
167 | float z = XYZ(2) / XYZSum;
168 | lightReadings["x"] = x;
169 | lightReadings["y"] = y;
170 | lightReadings["z"] = z;
171 |
172 | // not clear on the origin of the constants below:
173 | // from spreadsheet, tab: demonstration calculations, J17:
174 | lightReadings["lux"] = XYZ(1) * 683.0;
175 | // from spreadsheet, tab: demonstration calculations, J22:
176 | lightReadings["cct"] = 437 * pow(((x - 0.332) / (0.1858 - y)), 3) + 3601 * pow(((x - 0.332) / (0.1858 - y)), 2) + 6861 * ((x - 0.332) / (0.1858 - y)) + 5517;
177 | // add flicker detection:
178 | int flickerHz = as7341.detectFlickerHz();
179 | lightReadings["flicker"] = flickerHz;
180 | }
181 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/AS7341_Spectrometer_MQTT/AS7341_Spectrometer_MQTT.ino:
--------------------------------------------------------------------------------
1 | /*
2 | MQTT Client
3 | Sends a CSV string of spectrometer readings to public.cloud.shiftr.io
4 | Uses AS7341 spectrometer.
5 |
6 | Works with MKR1010, MKR1000, Nano 33 IoT
7 | Uses the following libraries:
8 | http://librarymanager/All#WiFi101 // use this for MKR1000
9 | http://librarymanager/All#WiFiNINA // use this for MKR1010, Nano 33 IoT, Nano RP2040 Connect
10 | http://librarymanager/All#ArduinoMqttClient
11 | http://librarymanager/All#Adafruit_AS7341 (for the sensor)
12 |
13 | created 13 Jun 2021
14 | modified 8 Jan 2025
15 | by Tom Igoe
16 | */
17 | // include required libraries and config files
18 | //#include // for MKR1000 modules
19 | #include // for MKR1010, Nano 33 IoT, and Nano RP2040 modules
20 | // #include // for Nano ESP32 modules; doesn't work in SSL mode
21 | #include
22 | // I2C and light sensor libraries:
23 | #include
24 | #include
25 | #include "arduino_secrets.h"
26 |
27 | // instance of the sensor library:
28 | Adafruit_AS7341 as7341;
29 | // array to hold the raw readings:
30 | uint16_t readings[12];
31 | String readingString = "";
32 |
33 | // initialize WiFi connection using SSL
34 | // (use WIFiClient and port number 1883 for unencrypted connections):
35 | WiFiSSLClient wifi;
36 | // WiFiClient wifi;
37 | MqttClient mqttClient(wifi);
38 | // details for MQTT client:
39 | const char broker[] = "public.cloud.shiftr.io";
40 | const int port = 8883;
41 |
42 | // variables for setting interval for sending:
43 | long lastUpdate = 0;
44 | int interval = 30 * 1000;
45 |
46 | // you should use more unique names than this:
47 | const char topic[] = "spectrometer";
48 | String clientID = "spectrometerClient";
49 |
50 | void setup() {
51 | Serial.begin(9600); // initialize serial communication
52 | // if serial monitor is not open, wait 3 seconds:
53 | if (!Serial) delay(3000);
54 |
55 | // set the credentials for the MQTT client:
56 | mqttClient.setId(clientID);
57 | mqttClient.setUsernamePassword(SECRET_MQTT_USER, SECRET_MQTT_PASS);
58 | // initialize the sensor:
59 | if (!as7341.begin()) {
60 | Serial.println("Sensor not found, check wiring");
61 | while (true)
62 | ;
63 | }
64 | // set integration time:
65 | as7341.setATIME(35);
66 | as7341.setASTEP(10000);
67 | as7341.setGain(AS7341_GAIN_256X);
68 | // start a new reading cycle:
69 | as7341.startReading();
70 | // connect to WiFi:
71 | connectToNetwork();
72 | }
73 |
74 | void loop() {
75 | //if you disconnected from the network, reconnect:
76 | if (WiFi.status() != WL_CONNECTED) {
77 | connectToNetwork();
78 | return;
79 | }
80 |
81 | // if not connected to the broker, try to connect:
82 | if (!mqttClient.connected()) {
83 | connectToBroker();
84 | return;
85 | }
86 | // only read sensor once every interval:
87 | if (millis() - lastUpdate < interval) return;
88 |
89 | // if you got a good read, send it:
90 | if (readSensor()) {
91 | mqttClient.beginMessage(topic);
92 | mqttClient.println(readingString);
93 | // send the message:
94 | mqttClient.endMessage();
95 | Serial.println(readingString);
96 | lastUpdate = millis();
97 | }
98 | }
99 |
100 | boolean readSensor() {
101 | // clear the reading string:
102 | readingString = "";
103 | // check to see if readings are done
104 | // checkReadingProgress() is non-blocking:
105 |
106 | float result = as7341.checkReadingProgress();
107 | // if there is a reading, do corrections and parse it
108 | // into readingString:
109 | if (result) {
110 | // correction factors:
111 | // TODO: This is crued, and could be better with matrix math.
112 | // from https://ams.com/documents/20143/36005/AS7341_AN000633_1-00.pdf/fc552673-9800-8d60-372d-fc67cf075740
113 | // fig 10
114 |
115 | // Added in 0 for 4 and 5 since those channels are not used below.
116 | float corrections[] = { 3.20, 3.00, 2.07, 1.30, 0, 0, 1.07, 0.93, 0.78, 0.71 };
117 |
118 | // get the readings:
119 | as7341.getAllChannels(readings);
120 | // loop over the readings and print them out:
121 | for (int r = 0; r < 12; r++) {
122 | // skip 4 and 5 as they are repeats:
123 | if (r == 4 || r == 5) continue;
124 | float thisReading = as7341.toBasicCounts(readings[r]);
125 | // if this is one of the channels with a correction, apply it:
126 | if (r < 10) {
127 | thisReading = thisReading * corrections[r];
128 | }
129 | readingString += String(thisReading);
130 | if (r < 11) readingString += ",";
131 | }
132 |
133 | // start a new reading cycle:
134 | as7341.startReading();
135 | }
136 | return result;
137 | }
138 |
139 | void connectToNetwork() {
140 | // try to connect to the network:
141 | while (WiFi.status() != WL_CONNECTED) {
142 | Serial.println("Attempting to connect to: " + String(SECRET_SSID));
143 | //Connect to WPA / WPA2 network:
144 | WiFi.begin(SECRET_SSID, SECRET_PASS);
145 | delay(2000);
146 | }
147 | Serial.println("connected.");
148 | IPAddress ip = WiFi.localIP();
149 | Serial.print(ip);
150 | Serial.print(" Signal Strength: ");
151 | Serial.println(WiFi.RSSI());
152 | }
153 |
154 | boolean connectToBroker() {
155 | // if the MQTT client is not connected:
156 | if (!mqttClient.connect(broker, port)) {
157 | // print out the error message:
158 | Serial.print("MOTT connection failed. Error no: ");
159 | Serial.println(mqttClient.connectError());
160 | // return that you're not connected:
161 | return false;
162 | }
163 | // once you're connected, you can proceed:
164 | mqttClient.subscribe(topic);
165 | // return that you're connected:
166 | return true;
167 | }
168 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/AS7341_Spectrometer_MQTT/arduino_secrets.h:
--------------------------------------------------------------------------------
1 | #define SECRET_SSID "" // your network SSID (name)
2 | #define SECRET_PASS "" // your network password
3 | #define SECRET_MQTT_USER "public"
4 | #define SECRET_MQTT_PASS "public"
5 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/AS7341_Spectrometer_MQTT_JSON/AS7341_Spectrometer_MQTT_JSON.ino:
--------------------------------------------------------------------------------
1 | /*
2 | MQTT Client
3 | Sends a JSON string of spectrometer readings to public.cloud.shiftr.io
4 | Uses AS7341 spectrometer.
5 |
6 | Works with MKR1010, MKR1000, Nano 33 IoT
7 | Uses the following libraries:
8 | http://librarymanager/All#WiFi101 // use this for MKR1000
9 | http://librarymanager/All#WiFiNINA // use this for MKR1010, Nano 33 IoT
10 | http://librarymanager/All#ArduinoMqttClient
11 | http://librarymanager/All#Adafruit_AS7341 (for the sensor)
12 |
13 | created 13 Jun 2021
14 | modified 4 Nov 2024
15 | by Tom Igoe
16 | */
17 | // include required libraries and config files
18 | //#include // for MKR1000 modules
19 | #include // for MKR1010 modules and Nano 33 IoT modules
20 | #include
21 | // I2C and light sensor libraries:
22 | #include
23 | #include
24 | #include "arduino_secrets.h"
25 |
26 | // instance of the sensor library:
27 | Adafruit_AS7341 as7341;
28 | // array to hold the raw readings:
29 | uint16_t readings[12];
30 | String readingString = "";
31 |
32 | // properties of the object (the channels of the sensor):
33 | // added 0 for channels 4 and 5, which are not used
34 | int wavelengths[] = { 415, 445, 480, 515, 0, 0, 555, 590, 630, 680, 910 };
35 |
36 | // last time the client sent a message, in ms:
37 | long lastTimeSent = 0;
38 | // message sending interval:
39 | int interval = 120 * 1000;
40 |
41 | // initialize WiFi connection using SSL
42 | // (use WIFiClient and port number 1883 for unencrypted connections):
43 | WiFiClient wifi;
44 | // WiFiClient wifi;
45 | MqttClient mqttClient(wifi);
46 | // details for MQTT client:
47 | const char broker[] = "public.cloud.shiftr.io";
48 | const int port = 1883;
49 | // you should use more unique names than this:
50 | const char topic[] = "spectrometer";
51 | String clientID = "spectrometerClient";
52 |
53 | void setup() {
54 | Serial.begin(9600); // initialize serial communication
55 | // if serial monitor is not open, wait 3 seconds:
56 | if (!Serial) delay(3000);
57 |
58 | // set the credentials for the MQTT client:
59 | mqttClient.setId(clientID);
60 | mqttClient.setUsernamePassword(SECRET_MQTT_USER, SECRET_MQTT_PASS);
61 | // initialize the sensor:
62 | if (!as7341.begin()) {
63 | Serial.println("Sensor not found, check wiring");
64 | while (true)
65 | ;
66 | }
67 | // set integration time:
68 | as7341.setATIME(35);
69 | as7341.setASTEP(10000);
70 | as7341.setGain(AS7341_GAIN_256X);
71 | // start a new reading cycle:
72 | as7341.startReading();
73 | // connect to WiFi:
74 | connectToNetwork();
75 | }
76 |
77 | void loop() {
78 | //if you disconnected from the network, reconnect:
79 | if (WiFi.status() != WL_CONNECTED) {
80 | connectToNetwork();
81 | }
82 |
83 | // if not connected to the broker, try to connect:
84 | if (!mqttClient.connected()) {
85 | connectToBroker();
86 | }
87 | // once every interval, send a message:
88 | if (millis() - lastTimeSent > interval) {
89 | // if you got a good read, send it:
90 | if (readSensor()) {
91 | mqttClient.beginMessage(topic);
92 | mqttClient.print(readingString);
93 | // send the message:
94 | mqttClient.endMessage();
95 | Serial.println(readingString);
96 | // timestamp this message:
97 | lastTimeSent = millis();
98 | }
99 | }
100 | }
101 |
102 | boolean readSensor() {
103 | // clear the reading string:
104 | readingString = "";
105 | // check to see if readings are done
106 | // checkReadingProgress() is non-blocking:
107 |
108 | float result = as7341.checkReadingProgress();
109 | // if there is a reading, do corrections and parse it
110 | // into readingString:
111 | if (result) {
112 | // clear readingString:
113 | readingString = "{";
114 | // correction factors:
115 | // TODO: This is crude, and could be better with matrix math.
116 | // from https://ams.com/documents/20143/36005/AS7341_AN000633_1-00.pdf/fc552673-9800-8d60-372d-fc67cf075740
117 | // fig 10
118 |
119 | // Added in 0 for 4 and 5 since those channels are not used below.
120 | float corrections[] = { 3.20, 3.00, 2.07, 1.30, 0, 0, 1.07, 0.93, 0.78, 0.71 };
121 |
122 | // get the readings:
123 | as7341.getAllChannels(readings);
124 | // loop over the readings and print them out:
125 | for (int r = 0; r < 9; r++) {
126 | // skip 4 and 5 as they are repeats:
127 | if (r == 4 || r == 5) continue;
128 | float thisReading = as7341.toBasicCounts(readings[r]);
129 | // if this is one of the channels with a correction, apply it:
130 | thisReading = thisReading * corrections[r];
131 | // format: "frequency":value,
132 | readingString += "\"" + String(wavelengths[r]);
133 | readingString += "\":" + String(thisReading);
134 | readingString += ",";
135 | }
136 | readingString += "\"clear\":";
137 | readingString += String(as7341.toBasicCounts(readings[10]));
138 | readingString += ",\"NIR\":";
139 | readingString += String(as7341.toBasicCounts(readings[11]));
140 | // add flicker detection:
141 | readingString += ",\"flicker\":";
142 | int flickerHz = as7341.detectFlickerHz();
143 | readingString += String(flickerHz);
144 | // add in JSON decorations:
145 | readingString += "}";
146 | // start a new reading cycle:
147 | as7341.startReading();
148 | }
149 | return result;
150 | }
151 |
152 | void connectToNetwork() {
153 | // try to connect to the network:
154 | while (WiFi.status() != WL_CONNECTED) {
155 | Serial.println("Attempting to connect to: " + String(SECRET_SSID));
156 | //Connect to WPA / WPA2 network:
157 | WiFi.begin(SECRET_SSID, SECRET_PASS);
158 | delay(2000);
159 | }
160 | Serial.println("connected.");
161 | IPAddress ip = WiFi.localIP();
162 | Serial.print(ip);
163 | Serial.print(" Signal Strength: ");
164 | Serial.println(WiFi.RSSI());
165 | }
166 |
167 | boolean connectToBroker() {
168 | // if the MQTT client is not connected:
169 | if (!mqttClient.connect(broker, port)) {
170 | // print out the error message:
171 | Serial.print("MOTT connection failed. Error no: ");
172 | Serial.println(mqttClient.connectError());
173 | // return that you're not connected:
174 | return false;
175 | }
176 | // once you're connected, you can proceed:
177 | mqttClient.subscribe(topic);
178 | // return that you're connected:
179 | return true;
180 | }
181 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/AS7341_Spectrometer_serial/AS7341_Spectrometer_serial.ino:
--------------------------------------------------------------------------------
1 | /*
2 | AS7341 sensor readings. Reads and prints the results
3 | as a CSV string. This version uses a non-blocking
4 | approach, using the startReading() and checkReadingProgress()
5 | functions. It's about 2 seconds between readings.
6 | Details for this sensor can be found on the AMS product page here:
7 | https://ams-osram.com/products/sensor-solutions/ambient-light-color-spectral-proximity-sensors/ams-as7341-11-channel-spectral-color-sensor
8 | In particular, the product data sheet, user guide, and
9 | Application Note AS7341 AN000633, "Spectral Sensor Calibration Methods"
10 | are of most use.
11 |
12 | Library:
13 | http://librarymanager/All#Adafruit_AS7341
14 |
15 | created 18 Jun 2021
16 | modified 16 Feb 2025
17 | by Tom Igoe
18 | */
19 |
20 | #include
21 | // instance of the sensor library:
22 |
23 | Adafruit_AS7341 as7341;
24 | // array to hold the raw readings:
25 | uint16_t readings[12];
26 | float channels[12];
27 | // header string for CSV:
28 | String headers = "415nm,445nm,480nm,515nm,555nm,590nm,630nm,680nm,Clear,NIR";
29 | // correction factors: these corrections are from Application Note AS7341 AN000633, "Spectral Sensor Calibration Methods"
30 | // fig. 10. These are for channels F1 through F8.
31 | // TODO: This math needs to be corrected.
32 | // values 4 and 5 are 0 because they are not used (see datasheet)
33 | float corrections[] = { 3.20, 3.00, 2.07, 1.30, 0.0, 0.0, 1.07, 0.93, 0.78, 0.71 };
34 |
35 | void setup() {
36 | // init serial, wait 3 secs for serial monitor to open:
37 | Serial.begin(9600);
38 | // if the serial port's not open, wait 3 seconds:
39 | if (!Serial) delay(3000);
40 |
41 | if (!as7341.begin()) {
42 | Serial.println("Sensor not found, check wiring");
43 | while (true)
44 | ;
45 | }
46 |
47 | // set integration time:
48 | as7341.setATIME(100);
49 | as7341.setASTEP(999);
50 | as7341.setGain(AS7341_GAIN_256X);
51 | // print column headers:
52 | Serial.println("setup");
53 | Serial.println(headers);
54 | // start a new reading cycle:
55 | as7341.startReading();
56 | }
57 |
58 | void loop() {
59 | if (!as7341.checkReadingProgress()) return;
60 |
61 | // if the current reading is complete:
62 | if(readSensor()) {
63 | // print the results:
64 | for (int r = 0; r < 12; r++) {
65 | // skip readings 4 and 5 as they are repeats:
66 | if (r == 4 || r == 5) continue;
67 | Serial.print(channels[r]);
68 | if (r < 11) Serial.print(",");
69 | }
70 | Serial.println();
71 | // start a new reading cycle:
72 | as7341.startReading();
73 | }
74 | }
75 |
76 | bool readSensor() {
77 | // take the current reading, do corrections,
78 | // and put the result into readingString:
79 | // get the readings:
80 | if (!as7341.readAllChannels(readings)) return false;
81 | // there are 12 elements in the readAllChannels array,
82 | // but elements 4 and 5 are not used. So channel number is different
83 | // than array index:
84 | int channelNum = 0;
85 | // loop over the readings and put them in the channels array:
86 | for (int r = 0; r < 12; r++) {
87 | // skip readings 4 and 5 as they are repeats:
88 | if (r == 4 || r == 5) continue;
89 | channels[r] = as7341.toBasicCounts(readings[r]);
90 | if (r < 10) {
91 | channels[r] = channels[r] * corrections[r];
92 | }
93 | }
94 | return true;
95 | }
96 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/AS7341_fritzing.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tigoe/LightProjects/b5bf913694539feaa8a481247025691f5d7d595a/spectrometers/AS7341/AS7341_fritzing.fzz
--------------------------------------------------------------------------------
/spectrometers/AS7341/MatrixCalculationTest/MatrixCalculationTest.ino:
--------------------------------------------------------------------------------
1 | #include
2 | // use the BasicLinearAlgebra namespace.
3 | // You might need to add BLA:: before all the Matrix declarations
4 | using namespace BLA;
5 |
6 | // XYZ correction matrix from the spreadsheet:
7 | Matrix<3, 10> corrMatrix = { 0.39814, 1.29540, 0.36956, 0.10902, 0.71942, 1.78180, 1.10110, -0.03991, -0.27597, -0.02347,
8 | 0.01396, 0.16748, 0.23538, 1.42750, 1.88670, 1.14200, 0.46497, -0.02702, -0.24468, -0.01993,
9 | 1.95010, 6.45490, 2.78010, 0.18501, 0.15325, 0.09539, 0.10563, 0.08866, -0.61140, -0.00938 };
10 |
11 | // sensor readings:
12 | Matrix<10> readings = {
13 | 0.04411,
14 | 0.11519,
15 | 0.14046,
16 | 0.14953,
17 | 0.38247,
18 | 0.41821,
19 | 0.44645,
20 | 0.11951,
21 | 0.66134,
22 | 0.07856
23 | };
24 | Matrix<10> corrections = { 3.20, 3.00, 2.07, 1.30, 1.07, 0.93, 0.78, 0.71, 0, 0 };
25 |
26 | Matrix<10> offsets = {
27 | 0.00197,
28 | 0.00725,
29 | 0.00319,
30 | 0.00131,
31 | 0.00147,
32 | 0.00186,
33 | 0.00176,
34 | 0.00522,
35 | 0.00300,
36 | 0.00100,
37 | };
38 |
39 | Matrix<10> correctionFactors = { 1.02811, 1.03149, 1.03142, 1.03125, 1.03390, 1.03445, 1.03508, 1.03359, 1.23384, 1.26942 };
40 |
41 |
42 | void setup() {
43 | // init serial, wait 3 secs for serial monitor to open:
44 | Serial.begin(9600);
45 | // if the serial port's not open, wait 3 seconds:
46 | if (!Serial) delay(3000);
47 | calculate();
48 | }
49 |
50 |
51 | void loop() {
52 | }
53 |
54 | bool calculate() {
55 | // corrected sensor values:
56 | Matrix<10> correctedValues;
57 | // normalized sensor values:
58 | Matrix<10> normalizedValues;
59 | // XYZ readings:
60 | Matrix<3> XYZ;
61 | // zero out the corrected and normalized matrices:
62 | correctedValues.Fill(0);
63 | normalizedValues.Fill(0);
64 | // max reading for the normalization:
65 | float maxReading = 0.0;
66 |
67 | // loop over the readings and print them out:
68 | for (int r = 0; r < 10; r++) {
69 | // skip 4 and 5 as they are repeats:
70 | //if (r == 4 || r == 5) continue;
71 |
72 | // if this is one of the channels with a correction, apply it:
73 | // if (r < 10) {
74 | correctedValues(r) = correctionFactors(r) * (readings(r) - offsets(r));
75 | // thisReading = thisReading * corrections[r];
76 | maxReading = max(correctedValues(r), maxReading);
77 | }
78 |
79 | for (int r = 0; r < 10; r++) {
80 | // skip 4 and 5 as they are repeats:
81 | // if (r == 4 || r == 5) continue;
82 | normalizedValues(r) = correctedValues(r) / maxReading;
83 | // print to 5 decimal places:
84 | Serial.print(correctedValues(r), 5);
85 | if (r < 9) Serial.print(",");
86 | }
87 | Serial.println();
88 | Serial.println();
89 | XYZ = corrMatrix * correctedValues;
90 | for (int i = 0; i < 3; i++) {
91 | Serial.println(XYZ(i), 5);
92 | }
93 | }
--------------------------------------------------------------------------------
/spectrometers/AS7341/spectrograph_chartjs_ble/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Spectrometer BLE
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/spectrograph_chartjs_mqttjs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | MQTT Client
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/spectrograph_chartjs_p5.webserial/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Document
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/spectrograph_chartjs_p5.webserial/sketch.js:
--------------------------------------------------------------------------------
1 | /*
2 | Spectrograph
3 | Written for serial input from AS7341, but could be adapted
4 | for other spectrometers, and for other transport mechanisms.
5 |
6 | Assumes a set of comma-separated values.
7 |
8 | uses p5.js and chart.js and p5.webSerial.
9 | For more on chart.js, see https://www.chartjs.org/
10 |
11 | created 9 July 2021
12 | modified 22 May 2022
13 | by Tom Igoe
14 | */
15 |
16 | // Serial port elements:
17 | const serial = new p5.WebSerial();
18 | let portButton;
19 |
20 | // fill in wavelengths of your spectrometer here:
21 | let wavelengths = [415, 445, 480, 515, 555, 590, 630, 680, 910, 'clear'];
22 | let numBands = 10;
23 |
24 | // array to put the readings from the sensors into:
25 | let readings = new Array();
26 | // div for the timestamp:
27 | let timestampDiv;
28 |
29 | // instance of the chart:
30 | let chart;
31 |
32 | // data object for the chart:
33 | const data = {
34 | labels: wavelengths,
35 | datasets: [{
36 | label: 'AS7341 Spectrometer',
37 | data: new Array(),
38 | backgroundColor: new Array()
39 | }]
40 | };
41 |
42 | const config = {
43 | type: 'bar',
44 | data: data,
45 | options: {
46 | responsive: true,
47 | plugins: {
48 | // this gets rid of the box by the data label:
49 | legend: {
50 | labels: {
51 | boxWidth: 0
52 | }
53 | }
54 | },
55 | // change the animation from one reading to the next:
56 | animation: {
57 | duration: 200 // in ms
58 | }
59 | }
60 | };
61 |
62 | function setup() {
63 | // set up the canvas:
64 | createCanvas(800, 600);
65 |
66 | // instantiate the chart:
67 | chart = new Chart(
68 | this, // context: this sketch
69 | config // config from the global variables
70 | );
71 | // push each wavelength onto the chart's
72 | // background color and borderColor:
73 | for (w of wavelengths) {
74 | // if the band name in the visible spectrum, calculate its color:
75 | if (w > 400 && w < 700) {
76 | chart.data.datasets[0].backgroundColor.push(wavelengthToColor(w));
77 | }
78 | else {
79 | if (w > 900) { // for near IR, use deep red:
80 | chart.data.datasets[0].backgroundColor.push('#770000');
81 | } else { // for 'clear', use black as its color:
82 | chart.data.datasets[0].backgroundColor.push('#000000');
83 | }
84 | }
85 | }
86 | // make a text div for the timestamp of each reading:
87 | timestampDiv = createDiv('last reading at: ');
88 | timestampDiv.position(windowWidth - 300, 10);
89 | // check to see if serial is available:
90 | if (!navigator.serial) {
91 | alert("WebSerial is not supported.");
92 | }
93 | // if serial is available, add connect/disconnect listeners:
94 | navigator.serial.addEventListener("connect", portConnect);
95 | navigator.serial.addEventListener("disconnect", portDisconnect);
96 | // check for any ports that are available:
97 | serial.getPorts();
98 | // if there's no port chosen, choose one:
99 | serial.on("noport", makePortButton);
100 | // open whatever port is available:
101 | serial.on("portavailable", openPort);
102 | // handle serial errors:
103 | serial.on("requesterror", portError);
104 | // handle any incoming serial data:
105 | serial.on("data", serialRead);
106 | }
107 |
108 | function draw() {
109 | // update the chart:
110 | chart.update();
111 | }
112 |
113 | // takes wavelength in nm and returns an rgba value
114 | // adapted from https://scienceprimer.com/javascript-code-convert-light-wavelength-color
115 | function wavelengthToColor(wavelength) {
116 | var r, g, b, alpha, colorSpace,
117 | wl = wavelength,
118 | gamma = 1;
119 | // UV to indigo:
120 | if (wl >= 380 && wl < 440) {
121 | R = -1 * (wl - 440) / (440 - 380);
122 | G = 0;
123 | B = 1;
124 | // indigo to blue:
125 | } else if (wl >= 440 && wl < 490) {
126 | R = 0;
127 | G = (wl - 440) / (490 - 440);
128 | B = 1;
129 | // blue to green:
130 | } else if (wl >= 490 && wl < 510) {
131 | R = 0;
132 | G = 1;
133 | B = -1 * (wl - 510) / (510 - 490);
134 | // green to yellow:
135 | } else if (wl >= 510 && wl < 580) {
136 | R = (wl - 510) / (580 - 510);
137 | G = 1;
138 | B = 0;
139 | // yellow to orange:
140 | } else if (wl >= 580 && wl < 645) {
141 | R = 1;
142 | G = -1 * (wl - 645) / (645 - 580);
143 | B = 0.0;
144 | // orange to red:
145 | } else if (wl >= 645 && wl <= 780) {
146 | R = 1;
147 | G = 0;
148 | B = 0;
149 | // IR:
150 | } else {
151 | R = 0;
152 | G = 0;
153 | B = 0;
154 | }
155 |
156 | // intensity is lower at the edges of the visible spectrum.
157 | if (wl > 780 || wl < 380) {
158 | alpha = 0;
159 | } else if (wl > 700) {
160 | alpha = (780 - wl) / (780 - 700);
161 | } else if (wl < 420) {
162 | alpha = (wl - 380) / (420 - 380);
163 | } else {
164 | alpha = 1;
165 | }
166 | // combine it all:
167 | colorSpace = "rgba(" + (R * 100) + "%," + (G * 100) + "%," + (B * 100) + "%, " + alpha + ")";
168 | return colorSpace;
169 | }
170 |
171 | ////////////////////
172 | // if there's no port selected,
173 | // make a port select button appear:
174 | function makePortButton() {
175 | // create and position a port chooser button:
176 | portButton = createButton('choose port');
177 | portButton.position(10, 10);
178 | // give the port button a mousepressed handler:
179 | portButton.mousePressed(choosePort);
180 | }
181 |
182 | // make the port selector window appear:
183 | function choosePort() {
184 | serial.requestPort();
185 | }
186 |
187 | // open the selected port, and make the port
188 | // button invisible:
189 | function openPort() {
190 | serial.open();
191 | console.log("port open")
192 | // hide the port button once a port is chosen:
193 | if (portButton) portButton.hide();
194 | }
195 |
196 | // read any incoming data as a string
197 | // (assumes a newline at the end of it):
198 | function serialRead() {
199 | // convert the input to a text string:
200 | let inString = serial.readLine();
201 | // use the serial data:
202 | let readings = float(split(inString, ','));
203 | // if you have all the readings, put them in the chart:
204 | if (readings.length >= numBands) {
205 | chart.data.datasets[0].data = readings;
206 | // update the timestamp:
207 | timestampDiv.html('last reading at: ' + new Date().toLocaleString());
208 | }
209 | }
210 |
211 | // pop up an alert if there's a port error:
212 | function portError(err) {
213 | alert("Serial port error: " + err);
214 | }
215 |
216 | // try to connect if a new serial port
217 | // gets added (i.e. plugged in via USB):
218 | function portConnect() {
219 | console.log("port connected");
220 | serial.getPorts();
221 | }
222 |
223 | // if a port is disconnected:
224 | function portDisconnect() {
225 | serial.close();
226 | console.log("port disconnected");
227 | }
--------------------------------------------------------------------------------
/spectrometers/AS7341/spectrograph_chartjs_serial/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Document
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/spectrometers/AS7341/spectrograph_chartjs_serial/sketch.js:
--------------------------------------------------------------------------------
1 | /*
2 | Spectrograph
3 | Written for serial input from AS7341, but could be adapted
4 | for other spectrometers, and for other transport mechanisms.
5 |
6 | Assumes a set of comma-separated values.
7 |
8 | uses p5.js and p5.serialport and chart.js. Requires p5.serialcontrol app
9 | to be open as well.
10 | For more on chart.js, see https://www.chartjs.org/
11 |
12 | created 9 July 2021
13 | modified 1 Oct 2021
14 | by Tom Igoe
15 | */
16 |
17 | // variable to hold an instance of the serialport library:
18 | let serial;
19 | // HTML Select option object:
20 | let portSelector;
21 | let timestampDiv;
22 | // fill in wavelengths of your spectrometer here:
23 | let wavelengths = [415, 445, 480, 515, 555, 590, 630, 680, 910, 'clear'];
24 | let numBands = 10;
25 |
26 | // array to put the readings from the sensors into:
27 | let readings = new Array();
28 |
29 | // instance of the chart:
30 | let chart;
31 |
32 | // data object for the chart:
33 | const data = {
34 | labels: wavelengths,
35 | datasets: [{
36 | label: 'AS7341 Spectrometer',
37 | data: new Array(),
38 | backgroundColor: new Array()
39 | }]
40 | };
41 |
42 | const config = {
43 | type: 'bar',
44 | data: data,
45 | options: {
46 | responsive: true,
47 | plugins: {
48 | // this gets rid of the box by the data label:
49 | legend: {
50 | labels: {
51 | boxWidth: 0
52 | }
53 | }
54 | },
55 | // change the animation from one reading to the next:
56 | animation: {
57 | duration: 200 // in ms
58 | }
59 | }
60 | };
61 |
62 | function setup() {
63 | // set up the canvas:
64 | createCanvas(800, 600);
65 |
66 | // new instance of the serialport library:
67 | serial = new p5.SerialPort();
68 | // callback function for serialport:
69 | serial.on('list', printList);
70 | serial.on('data', serialEvent);
71 | // list the serial ports:
72 | serial.list();
73 |
74 | // instantiate the chart:
75 | chart = new Chart(
76 | this, // context: this sketch
77 | config // config from the global variables
78 | );
79 | // push each wavelength onto the chart's
80 | // background color and borderColor:
81 | for (w of wavelengths) {
82 | // if the band name in the visible spectrum, calculate its color:
83 | if (w > 400 && w < 700) {
84 | chart.data.datasets[0].backgroundColor.push(wavelengthToColor(w));
85 | }
86 | else {
87 | if (w > 900) { // for near IR, use deep red:
88 | chart.data.datasets[0].backgroundColor.push('#770000');
89 | } else { // for 'clear', use black as its color:
90 | chart.data.datasets[0].backgroundColor.push('#000000');
91 | }
92 | }
93 | }
94 | // make a text div for the timestamp of each reading:
95 | timestampDiv = createDiv('last reading at: ');
96 | timestampDiv.position(windowWidth-300, 10);
97 | }
98 |
99 | function draw() {
100 | // update the chart:
101 | chart.update();
102 | }
103 |
104 | // make a serial port selector object:
105 | function printList(portList) {
106 | // create a select object:
107 | portSelector = createSelect();
108 | portSelector.position(10, 10);
109 | // portList is an array of serial port names
110 | for (var i = 0; i < portList.length; i++) {
111 | // add this port name to the select object:
112 | portSelector.option(portList[i]);
113 | }
114 | // set an event listener for when the port is changed:
115 | portSelector.changed(mySelectEvent);
116 | }
117 |
118 | function mySelectEvent() {
119 | let item = portSelector.value();
120 | // give it an extra property for hiding later:
121 | portSelector.visible = true;
122 | // if there's a port open, close it:
123 | if (serial.serialport != null) {
124 | serial.close();
125 | }
126 | // open the new port:
127 | serial.open(item);
128 | }
129 |
130 | function keyPressed() {
131 | // if port selector is visible, hide it, else show it:
132 | if (portSelector) {
133 | if (portSelector.visible) {
134 | portSelector.hide();
135 | portSelector.visible = false;
136 | } else {
137 | portSelector.show();
138 | portSelector.visible = true;
139 | }
140 | }
141 | }
142 |
143 | // if new serial data comes in, put it in the readings array:
144 | function serialEvent() {
145 | let inData = serial.readLine();
146 | let readings = float(split(inData, ','));
147 | // if you have all the readings, put them in the chart:
148 | if (readings.length >= numBands) {
149 | chart.data.datasets[0].data = readings;
150 | // update the timestamp:
151 | timestampDiv.html('last reading at: ' + new Date().toLocaleString());
152 | }
153 | }
154 |
155 | // takes wavelength in nm and returns an rgba value
156 | // adapted from https://scienceprimer.com/javascript-code-convert-light-wavelength-color
157 | function wavelengthToColor(wavelength) {
158 | var r, g, b, alpha, colorSpace,
159 | wl = wavelength,
160 | gamma = 1;
161 | // UV to indigo:
162 | if (wl >= 380 && wl < 440) {
163 | R = -1 * (wl - 440) / (440 - 380);
164 | G = 0;
165 | B = 1;
166 | // indigo to blue:
167 | } else if (wl >= 440 && wl < 490) {
168 | R = 0;
169 | G = (wl - 440) / (490 - 440);
170 | B = 1;
171 | // blue to green:
172 | } else if (wl >= 490 && wl < 510) {
173 | R = 0;
174 | G = 1;
175 | B = -1 * (wl - 510) / (510 - 490);
176 | // green to yellow:
177 | } else if (wl >= 510 && wl < 580) {
178 | R = (wl - 510) / (580 - 510);
179 | G = 1;
180 | B = 0;
181 | // yellow to orange:
182 | } else if (wl >= 580 && wl < 645) {
183 | R = 1;
184 | G = -1 * (wl - 645) / (645 - 580);
185 | B = 0.0;
186 | // orange to red:
187 | } else if (wl >= 645 && wl <= 780) {
188 | R = 1;
189 | G = 0;
190 | B = 0;
191 | // IR:
192 | } else {
193 | R = 0;
194 | G = 0;
195 | B = 0;
196 | }
197 |
198 | // intensity is lower at the edges of the visible spectrum.
199 | if (wl > 780 || wl < 380) {
200 | alpha = 0;
201 | } else if (wl > 700) {
202 | alpha = (780 - wl) / (780 - 700);
203 | } else if (wl < 420) {
204 | alpha = (wl - 380) / (420 - 380);
205 | } else {
206 | alpha = 1;
207 | }
208 | // combine it all:
209 | colorSpace = "rgba(" + (R * 100) + "%," + (G * 100) + "%," + (B * 100) + "%, " + alpha + ")";
210 | return colorSpace;
211 | }
--------------------------------------------------------------------------------
/spectrometers/AS7341/spectrograph_chartjs_web-serial/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Document
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/spectrometers/melanopic-edi-readings.md:
--------------------------------------------------------------------------------
1 | # Getting A Reading of Melanopic EDI From A Handheld Spectrometer
2 |
3 | Here's how to calculate melanopic equivalent daylight illuminance from a spectral reading using [Luox](https://luox.app/). To make this work, you will need a spectrometer that can deliver a spectral power distribution in normalized (relative) values. We used a Sekonic C-7000.
4 |
5 | First, make sure your meter is set to measure illuminance in lux, and to give you the spectral power distribution (SPD) in normalized values (a range from 0 to 1). Next, export the data from your meter, and look for the lux value and the SPD readings. Here's an [example file from the C-7000](SPD_001_02°_5791K.csv). You'll notice it gives you a lot of extra data. Take note of the illuminance reading (line 8 in that file, 1400lx). You'll also notice that there are two sets of spectral readings: the ones from lines 47-127 give readings in 5-nanometer ranges, and the ones in lines 129-529 give readings in 1-nanometer ranges. Whatever ranges your meter gives, you want the higher resolution ones. In this case, you would take lines 125-529 and delete the rest of the lines in the file. Then change the first column of each line so that it contains only the wavelength number. In this case, you'd change ``Spectral Data 780[nm]`` to ``780``, and do the same with all the other lines. Note that the readings in the second column are all between 0 and 1.
6 |
7 | Once you've got your file with just the spectral readings, upload them to the [Luox upload page](https://luox.app/upload). When prompted by Luox, respond that you want to "Use all rows and columns". On the next screen, you'll be asked to describe your data, with several optional pull-down boxes, like so:
8 | ````
9 | My data contains [absolute/relative] spectra with wavelength in nm. Label my measurements as
10 | [Observation 1].
11 | Each measurement column contains
12 | [irradiances/radiances]
13 | in W per m².
14 | ````
15 |
16 | Fill in the appropriate values for each option. Using the sample file, the answers are:
17 | ````
18 | My data contains
19 | relative spectra with wavelength in nm. Label my measurements as Observation 1. I have separately measured
20 | illuminance for Observation 1 at 1400 (lx).
21 | ````
22 |
23 | Finally, scroll down and Luox will give you all the melanopic readings (in this case, Melanopic EDI (lx) = 1263.0338) and let you download the full data as a file.
24 |
--------------------------------------------------------------------------------