With the goal of creating a useful implement for an older person in mind, we looked into the life of our team member Jeff. As a blinds installation professional, Jeff often has to demonstrate to his clients how his products affect the quality of light passing through them. We designed a portable and adjustable light source to help him simulate different lighting conditions in relation to his window blind samples.

Our initial meeting documentation can be found here.

Our prototype documentation can be found here.

What we built

Our final product is a portable light source that replicates a range of weather and lighting conditions through a control panel on its top surface. Two knobs control color temperature and intensity, while three buttons provide sunny, cloudy, and sunrise/sunset lighting presets. These presets can also be customized by holding the button down for more than two seconds, adjusting the values, and pressing any button to save. Additionally, the light source comes with a carrier box that doubles as a stand. It can be used to transport the light source and adapter, and flipped over to act as a stand for the light source to be placed atop.

Overall Photo:

An overall image of the final product.

Basic Operation:

Demonstrating the knob and button interaction.

Details / Highlights:

Light source and adapter in carrier.

Light source (off) on stand.

Light source (on) on stand.

Close-up of diffused LED panel.

Knobs and their corresponding icons (left: color temperature, right: intensity).

Buttons and their corresponding icons (left: sunny, middle: cloudy, right: sunrise/sunset).

Usage:

Turning the knobs.

Pressing the buttons.

Reading the LCD display.

Editing the cloudy preset values.

Narrative:

Jeff has a sales pitch today at a client’s house. He places the lighting simulator and its adaptor into its carrier and packs it into his car. He commutes to his client’s house and brings the carrier inside along with his other equipment. As he is going through the window blind choices with his client, his client gets confused about some of the options. To give his client a better visual, Jeff unpacks the light and places it on top of its carrier. He plugs in the adaptor and the device turns on. He pressed the sunny button and holds the opaque blind sample in front to show that no light passes through. He then hold up the semi-opaque blind sample to show the difference in light quality. After Jeff fiddles with the light settings to give his client a realistic image of how light would pass through each of the blinds, Jeff’s client is able to make a confident decision on which blinds to purchase.

How We Got Here

In our initial planning, we split the tasks into three main stages: programming/hardware, fabrication, and assembly. Our proposed project timeline can be seen below:

Proposed project timeline.

Based on the feedback we received on our prototype, we began by researching options for a brighter LED module in order to create a light source closer to natural sunlight.

After some research, we identified several important criteria we wanted our light source to achieve.

Since we intended to simulate lighting conditions through a window, the light source would have to achieve color qualities within a large portion of those ranges, which can extend from 1000 K to 10000K, which is basically a range from a deep red sunset to a deep blue sky. We determined that such a lighting simulator would be well-balanced if it encompassed the middle range, 3000K to 6000K, which would give options from orange-ish for dawn and dusk times to a white that’s tinted slightly blue for a general daylight color.

Secondly, we aimed for a product that could reproduce the intensities of sunlight and artificial lights to some degree. Sunlight intensity at Earth’s surface has a value of about 100,000 lux, and while a window is not likely to reach such intensities often, these can be important factors in replicating the performance of the blinds had they been demoed in those lighting conditions. Also, the high intensity also offers a way to simulate bright artificial lights, such as security lights, as well. Jeff mentioned that blinds may be installed with blocking such lights in mind, so a light capable of bright white light like that of bright sunlight could also approximate an artificial light to some extent.

A third factor that we thought was worth considering was the light source’s ability to simulate the quality of sunlight, specifically its full spectrum of light. A measure of a light’s quality, or closeness to the full spectrum, is its CRI value, so if we wanted a light that simulated lighting conditions, then it would make sense to have a light to approximate the natural light source as much as possible.

Thus, we settled on a light with the following specifications:

Color Range: 2700K – 6500K

Intensity: Max intensity is 3500 lumens at a color temperature of 4100K (close to white). A quick calculation gives around 110,000 to 120,000 lux. This is of course at its peak intensity, which occurs at a color temperature of 4100 K, but this intensity is mostly just necessary for direct sunlight, which is a close temperature of around 5000K, so the light should still be able to get close to the necessary intensity.

CRI value: rated to at least 95 CRI on a scale of 100.

Overall, the LED strip we got generally satisfied all our criteria. The only catch was that they were rather expensive. A link to the store page is provided below.

BC Series High CRI LED Multirow Hybrid Color Temperature LED Flexible Strip – Pack: 1 pcs

Fabrication Details

In terms of fabrication, the first step was to make adjustments to the design of the outer casing. Jeff had requested that the form be elongated horizontally to cover more area. In addition, he asked for a stand to elevate the light in order to create extra space at the bottom for when he stretches the blind sample. The outer casing and stand were designed in tandem to fit together. With the element of the light, adaptor, and stand in mind, we decided to make the stand double as a carrying case that can transport the other two items. We thought this could be particularly useful for Jeff since he commutes to his clients’ homes by car.

Redesigning aspects through sketching. Jeff had also mentioned that the light should be stable atop the stand, so we decided to add bumpers to the bottom face of the light that perfectly fit into cutouts on the top face of the stand.

After the designs were finalized, we constructed the CAD model for the outer casing in Fusion360 and sent it for 3D printing through the Stratasys printer.

Screenshot of the outer casing CAD model in Fusion 360.

We based the dimensions of the stand on the size of both the light and its adaptor. We input these dimensions into makeabox.io which gave us the dxf file to laser cut the stand. Slots on either side were added in Adobe Illustrator for when Jeff chooses to use the stand as a carrying case. We also added the circle cutouts that would fit the bumpers. Beyond this, fabricating the stand was a fast process.

The finished stand / carrier. The laser cut pieces were then joined together using bondene.

As for the outer casing, the 3D print was placed in the parts wash and finished using bondo and spray paint. Bondo was applied and sanded down to create a smooth surface for spray painting. We chose to use matte grey spray paint because Jeff wanted a sleeker look.

The finished stand / carrier and outer casing. Icons can be seen beneath the hole for each knob / button.

We also laser cut and engraved small icons that indicate the function of each knob / button. From left to right, the icons represent the color temperature knob, the brightness / intensity knob, the sunny preset button, cloudy preset button, and sunrise / sunset preset button. The holes for the icons were designed into the CAD file.

Finally, we added the bumpers on the bottom face of the outer casing.

Bottom face of outer casing with bumpers attached.

Technical details

While the structure of the prototype code was largely retained by the final code, several revisions had to be made account for several factors.

The initial big change was switching from using a LED library to manage the LEDs to a more ‘manual’ direct control of the LED intensities via PWM on transistors that controlled the actual current flow to the LED panels, although this is greatly simplified by the analogWrite() function.

This also meant that the color temperature of the board would have to be guided through a completely different method. The research on the LEDs here offered some helpful information on how the colors blended between the two LED circuits on the board, one for warm LEDs and one for cold LEDs, to produce the color temperatures we desired. One of the graphs on the sight indicated a close to linear relationship between intensity and color for both LED circuits given the other one was fully on, so I reasoned that the intensity of either circuit would cause an additive change in the temperature given the other was fully on. Without a way to test, we couldn’t be entirely sure, but the visual results suggested that the observed LED color temperatures were fairly close to the color temperatures we mapped them to.

Another change that was included fairly late in the game was the inclusion of a save system for the button presets, which resulted in some less than optimal code since it hadn’t been initially planned for.

On the electronics side of things, there were several notable changes to account for the high voltage LED panels and their 2 circuit design. We switched to using transistors and power regulators in the design to account for the 24V supply we switched to per the voltage requirements of the LEDs while allowing us to power the 5V Arduino Pro Mini. As pushing current through a resistor wastes a lot of the power that the LED will use and creates heating problems, a transistor allowed us to control the LED intensity as well in conjunction with PWM.

Before fully committing to the new design, we tested a few subsystems like transistors and power regulators on a solderless breadboard with the LED panel before upgrading to a soldered one for a more permanent design. This soldering requirement forced us to switch to the Arduino Pro Mini as we had been using the Arduino Uno for the prototype and testing many of the subsystems.

Soldering ended up taking a fair deal of time, but once it was done, all that was left was to install the wired up parts into the frame. We did not solder the power supply barrel jack immediately to the board however as we needed to mount it first, so we soldered the rest of the board before mounting the barrel jack, then soldered the barrel jack’s wires to the board.

Testing the code and electrical components on a mini LED panel before our actual module arrived in the mail.

Integrating the LED module.

The new LED module is much brighter; closer to natural sunlight than the LED strip used in our prototype.

Wiring up the pro mini.

Moving from breadboard to protoboard.

Making progress in soldering the electrical components

Once all the connections were soldered, we began assembling everything together.

All components ready to be assembled.

We started by securing the LCD screen by hot glueing it in place, then mounting the knobs and buttons. We then glued the protoboard to the back face of the casing to prevent shifting during transport.

LCD screen, knobs and buttons mounted. Protoboard in place.

Finally, we created a cardboard backing for the LED module and glued that into place. The last step was glueing the frosted acrylic panel in front.

The LED module glued in place on cardboard backing.

In the end, we weren’t able to quite follow our timeline due to the time taken to find and confirm the purchase of the LED panel as well as planning certain milestones for classes that were during Thanksgiving break, but we were able to catch up with some work done on the Sunday (lots of soldering) and Monday (final assembly) before the final critique.

Conclusions and Lessons Learned
Findings from the final critique

A common point of critique was the stand for the device, which some mentioned could easily look dirty or show signs of wear due to its very smooth acrylic material. One of the reviewers remarked that the stand could be made of “wood or another opaque surface” and that both the device stand and our acrylic ‘diffuser’ panel that goes in front of the LED panel could be made of “a material that can take more of a beating.” While the stand had admittedly been designed with the device in mind, it certainly didn’t receive close to the attention from us that the device received, and its the thin, smooth acrylic material turned out to be a poor choice.

One reviewer remarked that we should’ve taken a “closer study of Jeff’s pitch” while another posed the question of “how will Jeff carry this into [the] customer’s home”, probably concerning the handles on the stand not being ideal. Since we hadn’t seen Jeff do a sales pitch, we recognized that this was an unfortunate oversight in our development process, as a number of the other points of criticisms can be drawn from a lack of awareness of its day to day use.

Another point of contention was our choice of placement of the controls, which could affect the sales pitch. While the controls are on the top in our design for ease of accessibility according to Jeff’s desire to put it up against a window, a reviewer said “controls might be better on back”, which would hide them from the client. Both options certainly have advantages, although Jeff seemed pleased enough with the interface as it was.

The 2 adjustable variables of temperature and intensity give the user a lot of direct control and a simpler UI to deal with, but one reviewer did point out that “lighting could be more simulation-oriented”, which is perhaps more user friendly for those less inclined to the control scheme using somewhat technical variables and prefer something they can think of, like a cloudy morning in April (weather, time, season settings vs. color temperature, light intensity settings). If we had looked for and found simple mathematical models that combine all these inputs into something like color temperature and intensity, we recognize that this may have been feasible, although this would ultimately have been a design decision we’d have had to make with Jeff after agreeing initially upon a design fairly similar to our prototype.

Despite a number criticisms, many of the reviewers also had praise for the visual aspect of the design, especially that of the main device (a bit less enthusiasm for the stand). Adjectives like “sleek”, “beautiful”, “well-designed”, “professional”, “fit together”, and “product-like” were used to describe the visual design, and what’s more, Jeff wrote that “I will use this light box daily”. What more could we ask for?

Major takeaways

One of the biggest takeaways we got from working with an older person is that everyone is unique in ways that are often overlooked by stereotypes. We went into the initial meeting with generalizations about the hobbies that an older person might enjoy, or the way that they live their life. However, we were surprised to find that many of these assumptions did not line up. This seemed to be the case for many other groups as well. In our case, Jeff is very active and had no troubles with any physical aspects of his life. He is still highly engaged in his professional life and is very technologically adept. Him and his wife are very social and spend little time in their home. This went against many of our initial assumptions of what we were designing as we expected to create something more closely related to physical needs caused by aging as well as something that would remain at his home. Just as not all young people are addicted to social media and pop culture, older people are also unique in their habits and lifestyles, making it all the more important to research the individual’s particular wants and needs.

We also got a chance to learn from Jeff how our device could play a role in his business and even the entire industry. While there are large studio setups that could do a better job than our device, they’re hardly portable. Our device is both light and compact, making it easy to transport from car to home, but the pragmatic aspect didn’t seem all that novel to us considering similar photography lights did exist, even if they generally didn’t aim for such high light intensities. So we had figured it would be simply a minor addition to the sales pitch to reinforce what Jeff was already telling the customer. But according to Jeff, the device’s role in demonstrations is part of the “sizzle” in a sales pitch that can give a salesman an edge, and, as per Jeff’s own words, “sizzle sells”.

Concluding thoughts

There were several lessons that we took away from this experience.

A variety of technical issues came up throughout the course of developing this device that will serve as helpful experience for the future to prevent mistakes.

Soldering was a challenge at times, especially on the rare occasion that one of the many pins we had to solder in for the Arduino Pro Mini we were using wasn’t connected well by the solder, which made debugging a real challenge, as determining what was a code bug vs. an electrical issue can be hard to see. Bulk soldering can also lead to forgetting about adding heat shrink until both ends of a wire is soldered on, and the potential risk isn’t worth the quicker soldering, since wrapping electrical tape around small wires isn’t fast by any means.

An unfortunate change in the final product from the prototype was switching of the encoder pins from pins 2,4 for one encoder to pins 2,3 and pins 3,5 for the other encoder to pins 4,5. While it appeared to be a seemingly innocuous and more organized change,it turns out that pins 2 and 3 are special interrupt pins on the Arduino Uno (for the prototype) and the Arduino Pro Mini that are especially good for use with encoders. As a result, an encoder output is read best if it has two or at least 1 of these interrupt pins attached, but in this case, 1 encoder got both of the interrupt pins when it would’ve been better for both to get 1. Nevertheless, the ‘faulty’ encoder still works very well if it’s turned slowly enough, so the problem was largely overlooked, as it was an uncommon issue during testing when we hardly turned it very fast.

A few design choices could’ve also potentially enhanced our device.

We hadn’t designed the housing to have a ledges for the LED panel to rest on, which lead to the use of the cardboard backing and hot glue. Additionally, the housing also had no access from the back, so it was effectively impossible to access the internal electronics once it was sealed in by the acrylic panel.

Perhaps embedding the controls in the back could’ve given it an even sleeker look, although the top access is arguably more accessible, but it would’ve been worth considering.

We really should’ve inquired more into how Jeff might use the device on a sales pitch to better customize the stand/carrier. Then we probably would’ve ended up with a sturdier and hopefully easier to carry device.

Overall, creating this device, the result of our extensive efforts, was an engaging and wonderful opportunity for improving skills, gaining experience and  enhancing Jeff’s life.

Technical details:

Code:

/*
  Final Project: Jeffrey's Light

  Description: This code is intended to serve as an interface
  between User Input (consisting of 2 encoders, 3 buttons and an LCD display)
  and configurations for approximate color and relative intensities
  for a high power, bicolor LED panel.

  NOTE: The pins chosen for the encoders are not optimal,
  as one encoder has no interrupt pins (pins 2 and 3), which makes it less effective
  While 2 is optimal, the limited number of interrupt pins makes giving
  each encoder 1 interrupt pin a better balance, therefore pins 2,4 -> Encoder 1 and
  3,5 -> Encoder 2 or something similar would work best.

  Input:
  Pins|Connection (relevant properties)

  2   |Encoder 1 (interrupt pin)
  3   |Encoder 1 (interrupt pin)
  4   |Encoder 2
  5   |Encoder 2
  7   |Button 1
  8   |Button 2
  9   |Button 3

  Output:
  Pins|Connections
  10  |LED Circuit 1 (PWM)
  11  |(PWM) LED Circuit 2 (PWM)
  A4/A5|LCD Screen (SDA/SCL)


  Resources Used:

  LCD Screen code contains snippets and references to code written by Robert Zacharias at Carnegie Mellon University, rzach@cmu.edu
   released by the author to the public domain, November 2018

  Some Button-related code snippits adapted from my Project 2 code

  Adapted code from public domain examples in Encoder Library by Paul Stoffregen
  https://www.pjrc.com/teensy/td_libs_Encoder.html

  Data on bicolor LED panel used in device from product website
  BC Series High CRI LED Multirow Hybrid Color Temperature LED Flexible Strip - Pack: 1 pcs

  Some data on the LEDs, most importantly regarding the balance of LED temperatures
  
Complexities of Bicolor LED Lights: An Extensive Color Analysis
*/ #include <Encoder.h> #include <LiquidCrystal_I2C.h> #include <EEPROM.h> //PINS assignments Encoder IntenKnob(2, 3); Encoder ColorKnob(4, 5); const int B1_PIN = 7; const int B2_PIN = 8; const int B3_PIN = 9; const int WARM_LEDS = 10; const int COLD_LEDS = 11; //Uses SDA/SCL pins, not shown LiquidCrystal_I2C screen(0x27, 16, 2); //Presets and State Constants// //State constants based on states set by buttons (effectively an enumeration) const byte CUSTOM = 0; const byte DAYLIGHT = 1; const byte CLOUDY = 2; const byte SUNSET = 3; //Temperature constants of LEDs in Kelvin const int MAX_TEMP = 6500; //Highest color temperature rating by manufacturer const int MIN_TEMP = 2700; //Lowest color temperature rating by manufacturer const int MID_TEMP = 4100; //LEDs reach peak brightness at this color temperature //Max - Min = 3800 K //LED Presets int DAY_TEMP = 6000; float DAY_INTEN = 0.86; int CLOUDY_TEMP = 6500; float CLOUD_INTEN = 0.02; int SUNSET_TEMP = 3000; float SUNSET_INTEN = 0.2; //Commented out struct below is present in device code but is not used /* struct saveData { int DAY_TEMPs; float DAY_INTENs; int CLOUDY_TEMPs; float CLOUD_INTENs; int SUNSET_TEMPs; float SUNSET_INTENs; }; */ //Variables// //I/O Constants// //Presets chosen to define the range of encoder values //50 intervals for intensity ->0.02 per encoder 'tick' //38 intervals for temperature -> 100 K per encoder 'tick' const int EncIntenRange = 50; const int EncColorRange = 38; //Constants that serve as delimeters for milliseconds it takes to recognize either //a press (50ms) for setting light to config preset or a hold (2000ms) to edit that preset const int bPressTime = 50; const int bEditTime = 2000; //Used for storing previous Intensity and Color values between loop calls int lastInten, lastColor = -1; //Keeps track of how long any of the three buttons have been held int buttonTimes[3]; //These store the output values to the PWM pins (index 0 is warm LEDs; index 1 is cold LEDs) int PWMs[2]; //Time Variables// //Used to keep track of elapsed time between loop calls unsigned long lastMillis; //A constant delay value (should be a constant variable) at the end of loop calls as it should reduce LCD flickering. Possibly unnecessary. int delayTime = 10; //State Variables// //The state of the device in non-editing mode (not relevant while editing) byte state = CUSTOM; //The state of the device during editing (not relevant when non-editing) byte EditState = CUSTOM; //Initialized to not an editable state //Determines if the device is in an editing state or not. bool isEditing = false; //A flag to determine whether to redraw the LCD screen or not bool dirtyLCD = true; //Initialization Constants// //Initialization Temperature and color (a soft white glow to indicate activity without being blinding) const int InitTemp = 5000; const float InitInt = 0.02; void setup() { //Initialize button pins pinMode(B1_PIN, INPUT_PULLUP); pinMode(B2_PIN, INPUT_PULLUP); pinMode(B3_PIN, INPUT_PULLUP); //Initialize LED (PWM) pins pinMode(WARM_LEDS, OUTPUT); pinMode(COLD_LEDS, OUTPUT); lastMillis = millis(); //Loads the 3 presets from EEPROM loadPreset(); //Initialize the screen with relevant commands screen.init(); screen.backlight(); screen.clear(); screen.home(); //Initial color and intensity is set in the relevant 'encoder objects'. IntenKnob.write(rotFromInten(InitInt)); ColorKnob.write(rotFromTemp(InitTemp)); } void loop() { //Reads the encoder values int intenvalue = IntenKnob.read(); int colorvalue = ColorKnob.read(); //Prevents the encoders from leaving the allowed range if (intenvalue > 0) { IntenKnob.write(0); intenvalue = 0; } if (colorvalue > 0) { ColorKnob.write(0); colorvalue = 0; } if (intenvalue < -EncIntenRange) { IntenKnob.write(-EncIntenRange); intenvalue = -EncIntenRange; } if (colorvalue < -EncColorRange) { ColorKnob.write(-EncColorRange); colorvalue = -EncColorRange; } //Record Encoder input (negative due to clockwise motion decreasing the encoder values) int cin = -colorvalue; int inn = -intenvalue; //This variable is largely irrelevant unless in edit mode, in which case inputs that make it //true will lead to the leaving of edit mode. bool stopEdit = false; //Response to changes between current and previous encoder positions if (cin != lastColor || inn != lastInten) { dirtyLCD = true; if (!isEditing)state = CUSTOM; //Only while editing does state changing to custom matter } //Set state or editing based on button input if (!digitalRead(B1_PIN)) { buttonTimes[0] += millis() - lastMillis; if (buttonTimes[0] > bPressTime) { if (state != DAYLIGHT)dirtyLCD = true; state = DAYLIGHT; if (buttonTimes[0] < bEditTime)stopEdit = true; } } else buttonTimes[0] = 0; if (!digitalRead(B2_PIN)) { buttonTimes[1] += millis() - lastMillis; if (buttonTimes[1] > bPressTime) { if (state != CLOUDY)dirtyLCD = true; state = CLOUDY; if (buttonTimes[1] < bEditTime)stopEdit = true; } } else buttonTimes[1] = 0; if (!digitalRead(B3_PIN)) { buttonTimes[2] += millis() - lastMillis; if (buttonTimes[2] > bPressTime) { if (state != SUNSET)dirtyLCD = true; state = SUNSET; if (buttonTimes[2] < bEditTime)stopEdit = true; } } else buttonTimes[2] = 0; //LastMillis is only used above, so we can set it again afterwards lastMillis = millis(); //If any buttons been held for long enough, then device enters Edit Mode if (buttonTimes[0] > bEditTime || buttonTimes[1] > bEditTime || buttonTimes[2] > bEditTime) { if (!isEditing)dirtyLCD = true; stopEdit = false; isEditing = true; EditState = state; } //Initializes the string so it can be used later to print to the LCD String stateStr; if (!isEditing) { switch (state) { case CUSTOM: stateStr = "CUSTOM"; break; //Daylight presets are converted to Encoder presets case DAYLIGHT: cin = (DAY_TEMP - MIN_TEMP) / ((MAX_TEMP - MIN_TEMP) / EncColorRange); inn = (int)(EncIntenRange * DAY_INTEN); stateStr = "SUNNY"; break; //Cloudy presets are converted to Encoder presets case CLOUDY: cin = (CLOUDY_TEMP - MIN_TEMP) / ((MAX_TEMP - MIN_TEMP) / EncColorRange); inn = (int)(EncIntenRange * CLOUD_INTEN); stateStr = "CLOUDY"; break; //Sunset presets are converted to Encoder presets case SUNSET: cin = (SUNSET_TEMP - MIN_TEMP) / ((MAX_TEMP - MIN_TEMP) / EncColorRange); inn = (int)(EncIntenRange * SUNSET_INTEN); stateStr = "SUNRISE/SET"; break; } //If state has been set to non-custom, then overwrite current encoder settings if (state != CUSTOM) { IntenKnob.write(-inn); ColorKnob.write(-cin); } } else { //From above, this is the case where the device is in Editing mode switch (EditState) { case DAYLIGHT: stateStr = "EDIT:SUNNY"; break; case CLOUDY: stateStr = "EDIT:CLOUDY"; break; case SUNSET: stateStr = "EDIT:SUNSET"; break; } } //Convert ending Encoder values to temperature(equivalent to color) and intensity int temperature = MIN_TEMP + (int)((long)(MAX_TEMP - MIN_TEMP) * cin / EncColorRange); float intensity = (float)inn / EncIntenRange; //Writes to the LED panel control transistors using a function of temperature and intensity setPWM(temperature, intensity); //Only redraw the LCD if dirtyLCD flag is true if (dirtyLCD) { screen.setCursor(0, 0); screen.print(" "); screen.setCursor(0, 0); screen.print(stateStr); screen.setCursor(0, 1); screen.print((String)temperature + " K"); screen.setCursor(7, 1); screen.print(" "); //Easier to clear part of screen this way than clear the entirety. screen.setCursor(7, 1); screen.print((String)((int)(intensity * 100))); screen.print("%"); dirtyLCD = false; } //In this case, a button has been pressed during editing, so editing will cancel and the preset will be saved if (isEditing && stopEdit) { savePreset(EditState, temperature, intensity); dirtyLCD = true; //Redraw LCD when canceling out of edit } //Store current values for next loop lastInten = inn; lastColor = cin; //Analog write PWM pins with values set by SetPWM() method analogWrite(WARM_LEDS, PWMs[0]); analogWrite(COLD_LEDS, PWMs[1]); //May be unnecessary. Although it may make the LCD less flickery, it may also make changes to LED values more choppy delay(delayTime); } //conversion from temperature to encoder value int rotFromTemp(int temp) { return -((temp - MIN_TEMP) / 100); } //conversion from intensity to encoder value int rotFromInten(float inten) { return -inten / 0.02; } //sets PWM values when called based on input temperature and intensity void setPWM(int temp, float inten) { //Branching cases depending on whether the temperature is on the cold or the warm side if (temp - MID_TEMP <= 0) { //Warm temperature is at maximum for temperatures above the midTemp PWMs[0] = (int)(255 * inten); //The cold temperature varies depending on how far it ranges from MAX to midTemp PWMs[1] = (int)(inten * 255 * (temp - MIN_TEMP) / (MID_TEMP - MIN_TEMP)); } else { //Cold temperature is at maximum for temperatures above the midTemp PWMs[1] = (int)(255 * inten); //The warm temperature varies depending on how far it ranges from MAX to midTemp PWMs[0] = (int)(inten * 255 * (MAX_TEMP - temp) / (MAX_TEMP - MID_TEMP)); } } //reads the EEPROM for corresponding values for the button presets void loadPreset() { int checkLoad = 0; int i = 0; EEPROM.get(i, checkLoad); if (checkLoad >= MIN_TEMP) { EEPROM.get(i, DAY_TEMP); i += sizeof(int); EEPROM.get(i, DAY_INTEN); i += sizeof(float); } i = sizeof(int) + sizeof(float); EEPROM.get(i, checkLoad); if (checkLoad >= MIN_TEMP) { EEPROM.get(i, CLOUDY_TEMP); i += sizeof(int); EEPROM.get(i, CLOUD_INTEN); i += sizeof(float); } i = 2 * (sizeof(int) + sizeof(float)); EEPROM.get(i, checkLoad); if (checkLoad >= MIN_TEMP) { EEPROM.get(i, SUNSET_TEMP); i += sizeof(int); EEPROM.get(i, SUNSET_INTEN); i += sizeof(float); } } //Method to save the presets for the buttons to EEPROM void savePreset(int preset, int temp, float inten) { int i = 0; switch (preset) { case DAYLIGHT: DAY_TEMP = temp; DAY_INTEN = inten; break; case CLOUDY: CLOUDY_TEMP = temp; CLOUD_INTEN = inten; i = sizeof(float) + sizeof(int); break; case SUNSET: SUNSET_TEMP = temp; SUNSET_INTEN = inten; i = 2 * (sizeof(float) + sizeof(int)); break; } EEPROM.put(i, temp); EEPROM.put(i + sizeof(int), inten); isEditing = false; }

 

Schematic:

Note that this schematic is the one present in the device with the aforementioned encoder pin issue: pin 2,3 ->Encoder 1 and pin 4,5 ->Encoder 2, where 1 encoder gets both interrupt pins while the other gets none. This is also noted in the device code and concluding thoughts, so anyone aiming to reproduce at least the encoder part of the design should take note of it in both their electrical and software components.

Design Files:

design-files.zip