Светильник декоративный бытовой СДБ-З «Евлампия»





Честно говоря, уже не помню, с чего все началось. Предположим, мне снова стало скучно и захотелось какого-то света хоть в конце тоннеля, хоть в начале. Понемногу желание оформилось в ночник, который включается взмахом руки. И впоследствии трансформировалось в декоративную лампу с регулируемой яркостью, различными оттенками свечения и всякими дополнительными функциями. Именно, индикацией температуры за бортом и управлением освещением в комнате.

И все это получилось из самой дешевой интерьерной лампы из ИКЕА, пары метров светодиодной ленты, Arduino и небольшой кучки модулей и компонентов.



Знакомьтесь: Евлампия

Хочется какого-то казенного языка вроде «Светильник СДБ-З „Евлампия“ предназначен для использования в декоративных целях внутри жилых помещений», но дальше надо будет писать о строгом следовании инструкции, каковая отсутствует. Поэтому буду проще, тем более, что это основной принцип управления Евлампией.

.


Итак, что она умеет:

1) Работать ночником. Свет включается и выключается горизонтальным взмахом руки над лампой.

2) Работать лампой фонового света. Яркость регулируется вертикальным движением руки над лампой.

3) Показывать температуру на улице. Точнее — визуализировать диапазон температур. Каждому диапазону — свой оттенок. Данные получает от метеодатчика по радио.

4) Работать декоративной лампой с ручным изменением оттенка. Оттенок меняется вертикальным движением руки над лампой.

5) Работать декоративной лампой с циклическим изменением оттенка.

6) Управлять светом в комнате*. Удержание руки на определенной высоте в режиме обычной лампы переключает фоновый и верхний свет. А если накрыть лампу ладонью, то спустя три секунды выключается вообще весь верхний свет в квартире и фоновый — в комнате.

* Управляет выключателями Livolo и радиорозетками с чипами SC/PT2260/2262 и подобными с фиксированным кодом.



Как вы уже могли догадаться, управляется лампа горизонтальными и вертикальными движениями. При этом горизонтальный взмах или управляет ночником, или переключает по кругу режимы (лампа, термометр, ручная радуга, автоматическая радуга).

Объясняется это тем, что мне хотелось как-то уйти от всяких пультов, выключателей и прочих вайфаев. Чтобы, знаете, свет и оттенки зависели от, так сказать, ловкости рук. Рука, разумеется, упомянута условно — подойдет любое препятствие. Но так как у людей обычно руки, а не препятствия, в дальнейшем — рука.

С учетом этой специфики выбрал и датчик — ИК-дальномер Sharp, по поводу которого одни личности пишут, что целевое устройство — автоматический писсуар, а другие интересуются, что это за писсуар такой, если у некоторых датчиков семейства максимальная рабочая дистанция около 40 см. С другой же стороны я точно знаю, что эти датчики используются в роботах-пылесосах для предотвращения столкновений с препятствиями.

Не суть.

Рабочая дистанция выбранного датчика — 80 см (хотя по факту, кажется, гораздо больше), причем напряжение на выходе тем выше, чем ближе объект. Но по мере приближения препятствия есть порог, после которого напряжение пойдет вниз, несмотря на то, что препятствие становится все ближе. Неприятно, но не смертельно, тем более, что в моем случае это около 4 сантиметров, которые можно компенсировать «заглублением» датчика в конструкцию.

После выбора датчика я внезапно понял, что просто света мне мало. Захотелось и радугу, и много других плюшек — раз уж все равно в лампе будет стоять контроллер, ресурсы которого почти не используются. Так в списке комплектующих появились светодиодная RGB-лента вместо простой, приемник и передатчик 433 МГц, а также транзисторная сборка для управления лентой. Последняя показалась мне более привлекательной, чем просто несколько полевых транзисторов, не спрашивайте почему.

Чтобы не оставалось неясных моментов. Приемник нужен для получения данных о температуре и внешних команд — ведь глупо было бы упускать возможность включать и выключать лампу с пульта фонового освещения. Передатчик же необходим для управления внешним светом: фоновым на базе обычных радиоуправляемых розеток и верхним через радиоуправляемые выключатели Livolo.

Комплектующие

Если вкратце, то у меня была лампа из ИКЕА, отладочная платка Arduino Pro Mini, макетная плата, датчик расстояния Sharp, приемник и передатчик с амплитудной модуляцией на 433 МГц, светодиодная RGB-лента, банка из под пива, бутылка из под питьевой воды, коробка от зубного порошка, сетевой адаптер на 9В, пригоршня крепежа и немного проводов. Не то, чтобы этого было достаточно для возрождения орбитальной станции «Мир», но на Евлампию вполне хватило.

Итак (ссылки исключительно для примера):

— Лампа КВАРНЕ;

— ИК-датчики расстояния Sharp GP2Y0A21YK, и вот сразу даташит;

Приемник 433 МГц — супергетеродинный, потому что в регенеративных приемниках я разочаровался. То ли потому что они такие, то ли потому что я не могу нормально антенну сделать;

Передатчик 433 МГц субъективно — довольно приличный и, к тому же, уже с антенной;

Arduino Pro Mini;

— Транзисторная сборка ULN2003A;

— Светодиодная лента RGB — по вкусу, я купил в офлайне;

— Сетевой адаптер — тоже по вкусу, у меня валялись какие-то старые 9В (хотя на 12В, возможно, было бы лучше — лента ведь на 12В);

— Алюминиевая банка емкостью 0.5 л из-под любого напитка;

— Пластиковая бутылка с диаметром под внутренний диаметр алюминиевой банки;

— Банка зубного порошка диаметром под внутренний диаметр лампы. Похожа на такую (я купил в магазине у дома);

— Макетная плата, монтажные провода — по вкусу;

— Опционально: конденсаторы 10 мкФ, 0.1 мкФ, резистор 10 кОм.

Схема


Конденсаторы и подтягивающий резистор опциональны, и на схеме не приведены. Я их использовал в рамках компании по борьбе с ложными срабатываниями, но в итоге выяснилось, что фантомные потенциалы ни при чем.

. надеюсь, что более-менее правильно нарисовал.


. а так выглядит то, что получилось по схеме. Простите, что некрасиво. Уверен, в у вас точно будет лучше!


Обязательно обращайте внимание на питание ИК-датчика и приемника. Им нужно не более 5В, поэтому питаются они от стабилизатора Arduino, а не напрямую от блока питания.

Чудо из чудес

Сборка всего этого в единое целое оказалась занятием совершенно невероятным. Начать хотя бы с того, что кроме лампы из ИКЕА также понадобились пластиковая бутылка емкостью 0.5 л, алюминиевая банка той же емкости из-под любого напитка и пластиковая банка от зубного порошка. Все потому, что в голове возникла следующая концепция: RGB-лента клеится на алюминиевую банку. Получается некий аналог знаменитых китайских «кукурузин», который помещается внутрь плафона светильника.

. исходная лампа, заботливо разделенная на части


Банка здесь нужна чтобы служить каким-никаким теплоотводом и средством блюсти геометрию оформленного таким образом излучателя света. В банку же по замыслу создателя (т.е. меня) укладывается и плата управления. Но так как банка металлическая, то от КЗ защищаемся примитивно: закладываем внутрь предварительно обрезанную пластиковую бутылку, которая служит изолятором.

Отсюда закон Евлампии: внешний диаметр бутылки не должен превышать внутренний диаметр банки.

Что касается банки из-под зубного порошка, то она потребовалась, чтобы, во-первых, более-менее эстетично прикрыть от пыли и любопытных взглядов верх лампы. И заодно — удобно закрепить датчик и разместить приемник с передатчиком. Ведь внутри металлической банки у них мало шансов на продуктивную деятельность.

Другие чудеса по порядку:

  • купленных наугад пары метров светодиодной ленты хватило как раз, чтобы обернуть банку
  • банка с заботливо отрезанной верхушкой идеально встала в лампу
  • бутылка-изолятор прекрасно поместилась в банке
  • банка из-под зубного порошка по диаметру практически вписалась во внутренний диаметр плафона. «Практически» в данном случае означает, что есть незначительные нарушения геометрии крышки, которые отчасти вызваны тем, что корпус банки все же сжат, а частично тем, что в крышке банки пришлось сделать отверстие для ИК-датчика.

. примерно такая деформация


«Кукурузина» же крепится к плафону в нижней его точке штатным креплением патрона. Конечно, от этого крепления пришлось отломать «лишние» элементы, но факт остается фактом.

По моей версии процесс сборки таков:

1) Обрезаем верхушку алюминиевой банки и прорезаем в ее дне отверстие, соответствующее по диаметру отверстию в гайке крепления патрона лампы. Гайку приклеиваем к днищу банки — в дальнейшем вся она будет накручиваться на крепление патрона.

. вот так


2) Из крепления патрона удаляем собственно патрон и детали, мешающие продеванию в отверстие провода со штекером для питания контроллера.

.


3) Здесь я должен был бы аккуратно отрезать дно бутылки и обрезать ее носик так, чтобы с одной стороны этот носик, будучи помещенным вплотную к дну банки, исключал контакт контроллера с металлическими стенками, а с другой, чтобы бутылка не выступала за пределы банки по высоте. Но так как дважды ошибся, то в итоге положил на дно банки дно бутылки (с отверстием, разумеется, а сверху (чрезмерно отрезанным носиком вниз) — оставшуюся часть бутылки.

.


4) Наклеиваем светодиодную ленту вокруг банки с таким расчетом, чтобы создать достаточную плотность светодиодов и одновременно уложиться по длине (2 метра). Наклеиваем от дна банки, питающими проводами кверху, чтобы затем опустить их в банку, к контроллеру.

.


5) В чистой банке от зубного порошка проделываем два больших отверстия. В дне и в крышке, оба под датчик. Отверстия должны быть не менее диаметра датчика. В дне отверстие может быть почти любой формы, но оптимально прямоугольное, потому что рядом с ним нужно сделать пару отверстий поменьше — для шпилек крепления датчика. В крышке мне было удобно сделать круглое, так как оно было фактически размечено заводской штамповкой, и я аккуратно вырезал его обычным канцелярско-малярным ножом.

. сначала я просверлил два круглых отверстия под датчик, но когда выяснилось, что его нужно отодвинуть от края банки, то пришлось устроить настоящий «провал», иначе получал отражение от дна банки


Аккуратно рассверливаем отверстия датчика под M4 (если есть крепеж М3 — берите его, и ничего не сверлите) и насаживаем его на винты, фиксируя гайками.

.


Накручиваем на винты еще по гайке и продеваем их в отверстия дна банки, а сверху — еще по гайке. Такая конструкция позволяет механически регулировать положение датчика для точной подстройки базового уровня. Забегая вперед — базовый уровень это уровень при котором Евлампия считает, что ее накрыли рукой, и надо все выключить.

.


В банке потребуются также отверстия для проводов приемника и передатчика 433 МГц, которые туда также надо более-менее аккуратно уложить.

6) Подключаем к контроллеру светодиодную ленту, ИК-датчик, приемник и передатчик 433 МГц. Продеваем снизу в плафон провод питания и также подключаем к контроллеру.

. здесь уже частично видно прямоугольное отверстие в дне «зубной» банки


7) Укладываем контроллер в кукурузину, закрываем всю конструкцию банкой зубного порошка.

8) ?!!!

9) PROFIT!!!

Алгоритм

Здесь я постарался изобразить алгоритм ядра функций Евлампии, исключив радиоуправление. Также, этот алгоритм — часть бесконечно исполняемого цикла.



Код


Кроме кода потребуются две библиотеки:


Также потребуется сначала эмпирическим путем выяснить, а потом в секции переменных задать рабочий диапазон датчика, что зависит от того, как вы его разместите.

Это переменные top и bottom, отмечены комментариями «верх зоны регулировки» и «низ зоны регулировки».

В коде также остались отладочные строки — при необходимости можете включить.

p.s. пока Евлампия не получит температуру, в режиме ее отображения она последовательно мигает тремя цветами и переключается в следующий режим.

Все, что нажито непосильным трудом
<code class="cpp">// beta 3 - откалиброванный диапазон значений расстояния для пропорциональной регулировки яркости и оттенка // beta 4 - добавлена световая индикация возможности переключения внешнего света, управление лампой пультом ДУ // beta 4 - сжаты входные значения температуры для фиксированных цветовых температур // beta 4 - настроены цветовые температуры: /* -35 - -25 Синий 0 -25 - -15 Бирюзовый 15 -15 - 0 Светло-бирюзовый 50 0 - 15 Светло-зеленый 85 15 - 20 100 Зеленый 20 - 25 Желто-зеленый 170 25 - 30 Желто-красный 235 30+ Малиновый 254    // КРАСНЫЙ:      if (colorV < 100) {     targetR = colorV*0.05;   } else {     targetR = 5+(colorV-100)*1.29;   }         // ЗЕЛЕНЫЙ        if (colorV < 75) { // 0 - 180     targetG = 2.4*colorV;   }   if (colorV > 75 && colorV < 100) { // 180 - 254    targetG = 180 + (colorV-75)*2.96;   }        if (colorV > 100 && colorV < 150) { // 254     targetG = 254;   }      if (colorV > 150 && colorV < 200) { // 254 - 154    targetG =  254 - (colorV-150)*2;   }      if (colorV > 200) { // 154 - 0     targetG = 154 - (colorV-200)*2.85;   }      // СИНИЙ      if (colorV < 100) {     targetB = 254 - colorV*2.54;   } else {     targetB = colorV*0.05;   }   */  #include <RCSwitch.h> //   управление розетками SC2260/2262 http://code.google.com/p/rc-switch/ #include <livolo.h> // управление выключателями Livolo  #define  txPin  8 // пин передатчика  Livolo livolo(8); // объект Livolo RCSwitch mySwitch = RCSwitch(); // объект RC-Switch  int weatherData = 0; unsigned long dimmerDelay, timerStart, timerStop, timerSwitchStart, timerSwitchStop, timerRange; // таймеры int prevRange, nowRange, tempRange, switchRange, vectorRange, initRange, rangeFinder, tempC; byte nightLightLevel, rainbow, delta, redB, greenB, blueB, targetL, targetR, targetG, targetB, lastValueRGB; byte lightMode = 0; // режим светильника (0 - ночник, 1 - светильник, 2 - термометр, 3 - радуга ручная, 4 - радуга автоматическая) byte lastMode = 1; // режим для первого включения всегда светильник byte valueRGB = 0; // яркость или оттенок (нормализованное значение nowRange)  boolean rainbowUp, followMeLight; // направление радуги boolean timing = false; // признак работающего таймера (рука над лампой) boolean switchLock = false; // признак запрета на переключение режимов до снятия руки с лампы boolean tempLock = false; // признак запрета на индикацию температуры до получения нового значения boolean backLight = false; // признак работающего фонового света boolean mainLight = false; // признак работающего верхнего света boolean nightLight = false; // признак режима ночника boolean tempRcvd = false; // признак полученной температуры   #define nightLightLimit 65 // яркость ночника #define rangePin A0 // датчик препятствия #define redPin 9 // красный #define greenPin 10 // зеленый #define bluePin 11 // синий #define bottom 630 // низ зоны регулировки #define top 135 // верх зоны регулировки #define delta 20 // допустимое отклонение при определении дистанции #define shortDelay 350 // задержка для переключения режимов #define longDelay 3000 #define timeOutRange 100 // интервал между вычислением дистанции нужен так как при "скольжении" руки дистанция меняется, а значит меняется яркость или цвет #define rainbowStep 75 // интервал смены оттенка радуги #define dimmerStep 35 // скорость приглушения ночника  void setup() {    //  Serial.begin(9600);   pinMode(rangePin, INPUT);   pinMode(redPin, OUTPUT);   pinMode(greenPin, OUTPUT);   pinMode(bluePin, OUTPUT);     mySwitch.enableTransmit(txPin); // разрешена передача   mySwitch.enableReceive(0);  // разрешен прием (прерывание 0 -> пин 2)     rgbLight(0, 0, 0); // светильник включается выключенным      lastMode = 1; // первое включение всегда в режиме света   timerRange = millis(); // старт таймера на периодическое считывание дистанции    nowRange = getRange(); // первое вычисление дистанции  }  void loop() {     // ПЕРИОДИЧЕСКОЕ ВЫЧИСЛЕНИЕ ДИСТАНЦИИ    if ((millis() - timerRange) > timeOutRange) { // вычисление дистанции раз в timeOutRange   prevRange = nowRange; // память предыдущего значения дистанции   nowRange = getRange(); // текущее значение дистанции   timerRange = millis(); // сброс таймера интервала вычисления дистанции  // Serial.print("Range: "); // Serial.println(nowRange);     }  // ПРОВЕЛИ РУКОЙ -- запуск таймеров и переключение режимов (если допустимо), задержали руку - сервисные функции (если допустимо)  if ((nowRange < bottom + delta) && (nowRange > top)) { // если дистанция больше порога и меньше верхней границы   if (nowRange > bottom - delta) { // выключение света раньше "дна" как индикатор для удержания руки при полном выключении (2*delta)    valueRGB = 0;  } else {  valueRGB = 254 - map(nowRange, top, bottom, 0, 254); // приведение текущего значения к диапазону 0 - 254 и "переворот"       } // Serial.print("valueRGB: "); // Serial.println(valueRGB);    // Цветовая подсказка для переключения фонового и верхнего света     if (lightMode == 1) { // если режим светильника      if (valueRGB > 70 && valueRGB < 110) { // если рука в середине нижней половины      followMeLight = true; // флаг блокировки регулировки света белого свечения, разрешен только оттенок индикатора      lastValueRGB = valueRGB; // память последнего значения яркости      rgbLight(0, 255, 0); // зеленый оттенок как индикатор возможности переключения "ближнего" света      // Serial.println("Set Green to 255");    } else {            if (valueRGB > 175 && valueRGB < 215) { // если рука в середине верхней половины              followMeLight = true;              lastValueRGB = valueRGB; // память последнего значения яркости              rgbLight(0, 0, 255); // синий оттенок как индикатор возможности переключения "дальнего" света              // Serial.println("Set Blue to 255");            } else {                    if (valueRGB > 235) { // если рука у верхней границы                      followMeLight = true;                         lastValueRGB = valueRGB; // память последнего значения яркости                      rgbLight(valueRGB, 0, 0); // красный оттенок как индикатор предела регулировки яркости                      // Serial.println("Set RED");                                         } else {                            lastValueRGB = valueRGB;                            followMeLight = false;                                 }                  }           }  }    // И так далее     if (timing == false) { // если таймер не работает     timerStart = millis(); // запуск таймера на удержание руки     timerSwitchStart = millis(); // запуск таймера на взмах руки     timing = true; // признак работающего таймера   }    if (timing == true) { // если запущен таймер коротких и длинных задержек (рука над лампой)   // СБРОС ТАЙМЕРОВ ЕСЛИ РУКА НАД ЛАМПОЙ ДВИГАЕТСЯ      tempRange = nowRange - prevRange; // вычисление смещения руки при длительном удержании над лампой        if (abs(tempRange) > delta) { // если смещение больше допустимого       timerStart = millis(); // перезапуск таймера длинной задержки     }          if ((millis() - timerStart) > longDelay) { // если руку задержали над лампой на время переключения режимов  // ВКЛЮЧЕНИЕ СВЕТИЛЬНИКА       if (lightMode == 0 && switchLock == false) { // если был режим ночника - переключение в последний активный режим       lightMode = lastMode;       switchLock = true; // переключение блокируется до отвода руки от лампы      }    // СЕРВИСНЫЕ ФУНКЦИИ (включение и выключение дополнительного освещения в режиме обычного светильника)       if (lightMode == 1 && switchLock == false) { // если режим света  // Переключение фонового света         if ((nowRange > (bottom/2)) && (nowRange < (bottom - 2*delta))) { // если руку задержали в нижней половине, но выше нижнего порога +4*delta  //        // Serial.print("Backlight mode (nowRange): "); //        // Serial.println(nowRange);          //        // Serial.print("backLight:"); //        // Serial.println(backLight);                    if (backLight == false) { // если фоновый свет выключен                        // включаем фоновый свет            mySwitch.send(863029, 24);  // фоновый свет                       backLight = true;            switchLock = true; //           // Serial.println("Backlight ON");  //        // Serial.print("backLight:"); //        // Serial.println(backLight);                      } else { // если фоновый свет включен                        // выключаем фоновый свет            mySwitch.send(863028, 24);  // фоновый свет                       backLight = false;            switchLock = true;    //           // Serial.println("Backlight OFF");          //        // Serial.print("backLight:"); //        // Serial.println(backLight);                      }        }         // Переключение верхнего света                if ((nowRange < (bottom/2)) && (nowRange > top)) { // если руку задержали в верхней половине, но ниже верхнего порога  /*         // Serial.print("Main light mode (nowRange): ");         // Serial.println(nowRange);                  // Serial.print("mainLight:");         // Serial.println(mainLight); */           if (mainLight == false) { // если фоновый свет выключен                        // включаем основной свет             livolo.sendButton(8500, 0); // весь верхний свет                        livolo.sendButton(8500, 96); // весь верхний свет                                  mainLight = true;            switchLock = true;             /*         // Serial.println("Main light ON");          // Serial.print("mainLight:");         // Serial.println(mainLight);  */                    } else { // если фоновый свет включен                        // выключаем основной свет             livolo.sendButton(8500, 0); // весь верхний свет                        livolo.sendButton(8500, 96); // весь верхний свет              mainLight = false;            switchLock = true;            /*         // Serial.println("Main light OFF");                     // Serial.print("mainLight:");         // Serial.println(mainLight); */           }        }        }  // ВЫКЛЮЧЕНИЕ светильника и переход в режим ночника      if (nowRange > (bottom - delta)) { // если рука ниже порога (2*delta)        // Serial.print("Full off mode (nowRange): ");   // Serial.println(nowRange);            if (lightMode > 0 && switchLock == false) { // если не в режиме ночника                blinkLight(150);         rgbLight(0, 0, 0); // выключение света         livolo.sendButton(8500, 106); // весь верхний свет         mySwitch.send(863028, 24);  // фоновый свет         mainLight = false;         backLight = false;         lastMode = lightMode; // память последнего режима         lightMode = 0; // режим ночника         switchLock = true; // блокировка переключений до следующего взмаха        }      }      } // > longDelay   } // timing = true      // РЕГУЛИРОВКА яркости светильника      if (lightMode == 1) {     if (followMeLight == false) { // флаг блокировки регулировки света белого свечения, разрешен только оттенок индикатора      // Serial.println("Brightness control");       rgbLight(valueRGB, valueRGB, valueRGB);     }   }  // РЕГУЛИРОВКА ОТТЕНКА   if (lightMode == 3) {    if (valueRGB > 0) { // регулировка оттенка      colorRGB(valueRGB);     } else {       rgbLight(0, 0, 0);  // приглушение света для индикации режима выключения    }  }     } // nowRange > bottom    // ЕСЛИ НАД ЛАМПОЙ НЕТ руки, но она была  if ((nowRange < (top + delta))) {      if (timing == true) { // если провели рукой и включились таймеры     if (lightMode == 1) {      rgbLight(lastValueRGB, lastValueRGB, lastValueRGB); // восстановление оттенка и яркости после индикации возможности переключения света      followMeLight = false;    }    unsigned long  millistart = millis() - timerSwitchStart;        if (millistart < shortDelay) { // если рукой просто провели  // ВКЛЮЧЕНИЕ и выключение ночника        if (lightMode == 0 && switchLock == false) { // если режим ночника         if (nightLight == false) { // если ночник выключен          nightLightLevel = 0;         nightLight = true; // ночник включен       } else {                nightLightLevel = nightLightLimit;        nightLight = false; // ночник выключен       }         dimmerDelay = millis(); // запуск таймера интервала увеличения яркости      }  // ПЕРЕКЛЮЧЕНИЕ режимов            if (lightMode > 0) { // если режим светильника                lightMode = lightMode + 1; // переключение режима        blinkLight(150);                if (lightMode == 4) { // если режим радуги          dimmerDelay = millis(); // запуск таймера интервала изменения яркости           if (nowRange < 255) { // установка стартового оттенка радуги             rainbow = nowRange;             rainbowUp = true;           } else {             rainbow = 254;             rainbowUp = false; // направление радуги           }        }                if (lightMode == 3) { // если режим оттенка - сразу включить оттенок          colorRGB(valueRGB);        }                if (lightMode == 2) {          if (tempRcvd == true) {          colorRGB(tempC); // инициализация и восстановление индикации температуры при смене режима          } else {            noTemp();          }        }         if (lightMode > 4) { // зацикливание переключения режимов          lightMode = 1;        }     }           } // < longDelay   } // timing = true   timing = false;   switchLock = false; // сброс блокировки на переключение режимов по длительному удержанию руки над лампой } // nowRange < bottom + delta   if (nightLight == true && nightLightLevel < nightLightLimit) { // если ночник включен, а яркость меньше установленной      if ((millis() - dimmerDelay) > dimmerStep) { // регулировка яркости с интервалом dimmerStep мс     nightLightLevel++;     rgbLight(nightLightLevel, nightLightLevel, nightLightLevel);     dimmerDelay = millis(); // запуск таймера интервала увеличения яркости   } }  if (nightLight == false && nightLightLevel > 0) { // если ночник включен, а яркость больше нуля      if ((millis() - dimmerDelay) > dimmerStep) {     nightLightLevel--;     rgbLight(nightLightLevel, nightLightLevel, nightLightLevel);     dimmerDelay = millis(); // запуск таймера интервала увеличения яркости   } }  // ИНДИКАЦИЯ температуры      if (lightMode == 2) {    if (valueRGB > 0) { // если рука на лампе и пора выключаться        if (tempRcvd == true) {          if (tempLock == false) { // если не заблокирована смена оттенка температуры            colorRGB(tempC); // инициализация и восстановление индикации температуры при смене режима           tempLock = true; // блокировка смены оттенка до следующего значения температуры                   } else {            noTemp();          }     }    } else {      rgbLight(0, 0, 0);      tempLock = false;    }       }     // РАДУГА     if (lightMode == 4) {    if (valueRGB > 0) { // если рука на лампе и пора выключаться   if ((millis() - dimmerDelay) > rainbowStep) {       // Serial.print("Rainbow value: ");  // Serial.println(rainbow);           if (rainbowUp == true) {       if (rainbow < 254) {         rainbow++;       } else {         rainbowUp = false;       }     }      if (rainbowUp == false) {       if (rainbow > 1) {         rainbow--;       } else {         rainbowUp = true;       }     }              colorRGB(rainbow);     dimmerDelay = millis();   }       } else {      rgbLight(0, 0, 0);    }      }  if (mySwitch.available()) { // прием данных и команд  int value = mySwitch.getReceivedValue();  if (value != 0) {      // ВКЛЮЧЕНИЕ ВМЕСТЕ С ФОНОМ      if (lightMode == 0) {      if (mySwitch.getReceivedValue() == 863029) {      lightMode = 1;      rgbLight(254, 254, 254);      switchLock = true;     }   }      // ВЫКЛЮЧЕНИЕ ВМЕСТЕ С ФОНОМ        if (mySwitch.getReceivedValue() == 863028) {         rgbLight(0, 0, 0); // выключение света         mainLight = false; // отключение памяти состояния света при выключении светильника         backLight = false;         if (lightMode == 0) {           lastMode = 1;         } else {           lastMode = lightMode; // память последнего режима         }         lightMode = 0; // режим ночника         switchLock = true; // блокировка переключений до следующего взмаха     }    if (mySwitch.getReceivedValue()/100000 == 161) {          weatherData = mySwitch.getReceivedValue() - 16100000; 	if (weatherData < 10000) { // фильтр от влажности     	  if (weatherData > 1000) { // минусовая температура                          if (weatherData > 1250) { // температура ниже -25С 	      tempC = 0;             }              if (weatherData < 1250 && weatherData > 1150) { // температура -25С -- -15C 	      tempC = 15;             }              if (weatherData < 1150) { // температура -15C -- 0C 	      tempC = 50;             }                                   }  else { // плюсовая температура               if (weatherData > 300) { // если температура выше +30С 	        tempC = 254;               }                              if (weatherData > 250 && weatherData < 300) { // если температура +25C -- +30С 	        tempC = 235;               }                if (weatherData > 200 && weatherData < 250) { // если температура +25C -- +30С 	        tempC = 170;               }                if (weatherData > 150 && weatherData < 200) { // если температура +15C -- +25С 	        tempC = 100;               }                              if (weatherData < 150) { // если температура ниже +15 	        tempC = 85;               }                              	  }              tempRcvd = true;       tempLock = false; // получена температура для отображения       // tempC = tempC*0.363; // трансляция температуры в шкалу от 0 до 255       // Serial.print("TempC: ");       // Serial.println(tempC);   }  }  mySwitch.resetAvailable();   } }    } // loop                 void rgbLight(byte redL, byte greenL, byte blueL) {      analogWrite(redPin, redL);   analogWrite(greenPin, greenL);   analogWrite(bluePin, blueL); }  void colorRGB(int colorV) {     // КРАСНЫЙ:      if (colorV < 100) {     targetR = colorV*0.05;   } else {     targetR = 5+(colorV-100)*1.29;   }         // ЗЕЛЕНЫЙ        if (colorV < 75) { // 0 - 180     targetG = 2.4*colorV;   }   if (colorV > 75 && colorV < 100) { // 180 - 254    targetG = 180 + (colorV-75)*2.96;   }        if (colorV > 100 && colorV < 150) { // 254     targetG = 254;   }      if (colorV > 150 && colorV < 200) { // 254 - 154    targetG =  254 - (colorV-150)*2;   }      if (colorV > 200) { // 154 - 0     targetG = 154 - (colorV-200)*2.85;   }      // СИНИЙ      if (colorV < 100) {     targetB = 254 - colorV*2.54;   } else {     targetB = colorV*0.05;   }    rgbLight(targetR, targetG, targetB);  }  void blinkLight(byte spark) {                for (byte i = 0; i < 3; i++) {             rgbLight(0, 0, 0);             delay(75);             rgbLight(spark, spark, spark);             delay(75);           }              }  unsigned int getRange(){   byte i;   unsigned int rangeFinder = 0;      for (i = 0; i < 100; i++) {     rangeFinder = rangeFinder + analogRead(rangePin);   }      rangeFinder = rangeFinder/100;   return rangeFinder;    }  // СВЕТОФОР БЕЗ ТЕМПЕРАТУРЫ  void noTemp() {   rgbLight(254, 0, 0); // Мигаем разными цветами   delay(500);   rgbLight(0, 254, 0);   delay(500);   rgbLight(0, 0, 254);   delay(500);   blinkLight(150); // Мигаем белым   lightMode = 3; // Переключаемся в следующий режим }  </code>


Особенности

Что характерно, с лампой получилось, как обычно, несмотря на то, что я уже ученый. Иными словами, я ничего не собирал целиком, пока не проверил работу во всех режимах. Работает.

Тогда я уложил все в корпус и снова проверил работу во всех режимах. Работает.

Оставил на сутки на предмет неожиданностей. Неожиданностей не было. работает.

Поставил на прикроватную полочку. Лампа сошла с ума. Именно: если была в режиме ночника, стала непрерывно включаться и выключаться; если была в режиме светильника — постоянно сама переключала режимы. На свою беду я сопоставил эти проблемы с расположением провода питания и расположением лампы относительной батареи. Подумал, что это потенциальные источники помех, которые могли бы повлиять на работу датчика и микроконтроллера.

Дальше лампа то работала нормально, то не работала. Чтобы вернуть ее работоспособность я постоянно крутил ее и провод; купил сетевой фильтр-розетку APC, поставил конденсаторы 10 мкФ и 0.1 мкФ параллельно питанию датчика и подтянул вход контроллера, к которому подключен датчик к земле через резистор 10 кОм. Не помогало ничего, но смущало, что лампа сходила с ума в произвольные моменты времени. В конечном итоге я взял волю в кулак и разобрал конструкцию, чтобы посмотреть, что вообще творится у нее в душе.

Пока знакомился с богатым внутренним миром Евлампии, случайно повернул датчик и обнаружил, что хотя расстояние до ближайшего объекта было более 80 сантиметров, показания датчика изменились. И тут я многое понял. Причем, если вспомнить мое упоминание этого датчика в роботах, удивительно, что я сразу об этом не подумал.

Так вот, датчик по-разному реагирует на различные препятствия. Например, светлая стена или темная дверь, если судить по поведению робота-пылесоса — очень разные вещи. Ну а котик — вообще сродни черной дыре. Иными словами, когда я впервые запустил Евлампию в тестовом режиме, то задал границы рабочего диапазона датчика для определенного места — где, собственно, проверял. А вот на прикроватной полке все оказалось по-другому.

В общем, после того, как я изменил верхнюю границу диапазона, Евлампия стала совершенно послушной.

Вторая особенность — реализация радуги (северного сияния). Так как я совсем не математик, то просто прикинул, как именно нужно смешивать цвета, и построил график из соответствующих отрезков. Внимательный наблюдатель отметит, что большую часть времени лампа проводит в зеленом диапазоне. И для этого есть причина: и для отображения температуры, и для радуги используется один и тот же «цветогенератор».

А так как на мой взгляд в моем родном регионе важнее иметь представление о диапазоне от, скажем +5C до +25C, то и последствия соответствующие.

Источник: habrahabr.ru/post/243037/