├── .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 | ![Figure 1. Breadboard view of Squeeze bulb lamp circuit](SqueezeBulb_Lamp_circuit_bb.png) 29 | 30 | _Figure 1. Breadboard view of Squeeze bulb lamp circuit_ 31 | 32 | 33 | ![Figure 2. Schematic view of Squeeze bulb lamp circuit](SqueezeBulb_Lamp_circuit_schem.png) 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 |

3 |


4 | Low-voltage DC Lamps
5 | LED Strip Control
6 | Addressable LEDs
7 |   NeoPixel Library Quickstart
8 |   Making Electronic Candles
9 | Fading
10 | Chromaticity
11 | Color Spaces
12 | Spectrometers
13 | Light Rendering Indices
14 | Inventory
15 | Pattern Making
16 | Telephone Dimmer for Philips Hue 17 |
18 | Tom's other light-related repos
19 | tigoe.github.io
20 |

21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {% seo %} 8 | 9 | 10 | 11 | 14 | 15 | 16 |
17 |
18 |

{{ site.title | default: site.github.repository_name }}

19 |

{{ site.description | default: site.github.project_tagline }} 20 |

21 | {% include nav.html %} 22 | 23 | 30 | 31 | {% if site.github.is_project_page %} 32 |

This project is maintained by {{ site.github.owner_name }}

33 | {% endif %} 34 | 35 | {% if site.github.is_user_page %} 36 | 39 | {% endif %} 40 |
41 | 42 |
43 | {{ content }} 44 |
45 | 46 | 49 |
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 | ![Screenshot of the Arduino IDE](img/arduino-ide.png) 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 | ![Screenshot of the Arduino IDE Boards Manager](img/arduino-ide-boards-manager.png) 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 | ![Screenshot of the Arduino IDE with Examples open](img/arduino-ide-examples-menu.png) 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 | ![Figure 1. Archway of light in the entrance of ITP's 370 Jay St space. A red string of lights frames the entrance way to a lobby area.](img/itp-arch-red.jpg) 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 | ![Figure 2. Nano 33 IoT and light sensor on a breadboard, positioned on a windowsill, overlooking a skyline of buildings in downtown Brooklyn. Sunlight streams in through the window, and the sensor is in partial shadow from the window frame.](img/nano33-light-sensor-east.jpg) 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 | ![Figure 3. Nano 33 IoT and light sensor on a breadboard, positioned on a windowsill, overlooking a sunlit brick wall. the wall is painted a pale yellow.](img/nano33-light-sensor-west.jpg) 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 | ![Figure 4. Nano 33 IoT and light sensor mounted on a breadboard. The Nano 33 IoT straddles the center of the breadboard. Its second physical pin, Vout (second from top left) is connected to the voltage bus on the left side of the breadboard. Its fourteenth physical pin, Ground, (second from bottom left) is connected to the ground bus on the left side. Table 1 lists the pin connections between the two components.](img/nano33_light-sensor_bb.png) 27 | 28 | _Figure 4. Nano 33 IoT and light sensor, breadboard view._ 29 | 30 | ![Figure 5. Nano 33 IoT and light sensor, schematic view. Table 1 lists the pin connections between the two components.](img/nano33_light-sensor_schem.png) 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 | --------------------------------------------------------------------------------