image

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

Краткое ТЗ:

1) Управление тремя группами освещения на кухне
2) Управление тремя группами освещения в комнате
3) Управление всеми источниками света одновременно
4) Разумная продолжительность автономной работы (от недели)
5) Совместимость с кодированием Livolo, SC2260, EV1527

Итого, дальше не нужно читать, если вы не любите Arduino, выключатели Livolo и китайские радиорозетки. Потому что первое — основа для пульта, а второе и третье — периферия.

Концепт


Логика управления представлялась мне следующей:

  1. Нажатие на «крутилку» переключает зоны группы освещения по кольцу (кухня — комната — все).
  2. Поворот ручки, в зависимости от направления вращения, последовательно включает или выключает освещение выбранной группы.
  3. Режим работы (выбранная группа) отображается ненавязчивой светодиодной индикацией.

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

В случае, если поворот ручки не приводит к желаемому результату, то комбинированное нажатие и поворот в обратную сторону позволяет пропустить команду. Затем команду можно повторить, как обычно.

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


Зачем так сложно? Затем, что кроме неловких протоколов, у меня еще и неловкие периферийные устройства. Например, радиоуправляемые выключатели света Livolo и радиореле, у которых одна и та же команда на включение и выключение вкупе с обычными радиорозетками, у которых команды на включение и выключение — раздельные.

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

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

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

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

Поэтому сразу после пробуждения поворот ручки начинает либо включать, либо выключать свет с нуля.

Первый подход


Визуальная концепция вида «коробка с крутилкой» требовала, как можно догадаться, двух вещей: коробки и крутилки. В первой версии роль коробки играл тонкий пауэрбанк, использование которого решало сразу две проблемы: у меня был и корпус, и схема зарядки аккумулятора, причем уже с разъемом. Сам аккумулятор, понятно, пришлось заменить на более компактный, иначе начинка уже не помещалась.

image

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

image

Управляющая часть стала результатом эксперимента с ATmega328P и закономерным продолжением сюжетной линии, задаваемой уже имеющейся домашней автоматикой (на тех же Arduino и примитивных радиопротоколах).

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

image

image

image

Эксперимент оказался успешным, и сконфигурированный под среду Arduino контроллер вполне успешно замигал светодиодом, после того, как проглотил классический Blink. Ну а затем по принципу «дорисуйте сову», я добавил к получившейся плате энкодер (с кнопкой), три светодиода и обычный передатчик с амплитудной модуляцией на несущей 433,92 МГц.

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

image

image

Второй подход


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

А вот когда появился 3D-принтер, то пообещал себе однажды сделать тот самый оригинальный корпус и таким образом закрыть вопрос с пультом.

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

image

Зато имея полную свободу действий, я отказался от хилых и невнятных китайских аккумуляторов, и взял источником питания старый добрый 18650. И, как несложно видеть, именно его размеры во многом определяют габариты всего корпуса.

Сам же корпус я стал делать модульным, состоящим из множества частей, что позволяло перепечатывать лишь отдельные (ошибочные или не слишком оптимальные) элементы, а не все изделие целиком.

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



У меня в загашнике как раз лежал еще один приемник, который я сразу же пустил в дело.


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

Что потребуется для повторения


Железка


1) Контроллер ATmega328P — 1 шт. (у меня в корпусе TQFP, но можно любой)
2) Резистор 10 кОм — 5 шт. (4 на подавление дребезга энкодера, 1 — на контроллер)
3) Резистор 100 Ом — 3 шт.
4) Керамические конденсаторы 0,1 мкФ — 4 шт. (на контроллер и подавление дребезга энкодера)
5) Нажимной энкодер (валкодер) — 1 шт. (у меня PEC12-4220F-S0024)
6) Светодиоды — 3 шт. (диаметром 3 мм)
7) Плата зарядки литиевого аккумулятора — 1 шт. (из попавшегося под руку пауэрбанка, по идее, подойдет любая с автоматическим включением под нагрузкой)
8) Приемник беспроводной зарядки Qi — 1 шт.
9) Передатчик с амплитудной модуляцией на 433 МГц — 1 шт. (вроде такого)
10) Немного стеклотекстолита для платы энкодера
11) 3D-принтер
12) Подходящий пластик (я печатал PLA)
13) Винты M4x30 — 4 шт.

Вообще, количество компонентов можно уменьшить. К примеру, в совсем минимальном варианте контроллер вообще не требует обвязки, хотя я решил последовать совету Ника Гаммона и не пожалел пару конденсаторов и резистор.

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

Альтернативно можно использовать готовую плату Arduino, вроде Pro Mini, но в этом случае я не могу гарантировать низкий уровень потребления энергии, и вам придется поколдовать над ним самостоятельно. Заодно придется подправить и корпус.

Схема:



Для справки распиновка ATmega328p в корпусе TQFP-32 от Hobby Electronics:



Для своего энкодера я нарисовал небольшую плату:







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

Для корпуса важно, чтобы высота платы с деталями, исключая энкодер, была не более (или не сильно более) 5 мм.

Если под руками не готовая плата Arduino, то для того, чтобы все заработало, в контроллер ATmega328P нужно для начала записать загрузчик Arduino.
Для этого необходимо, во-первых, добавить в среду Arduino описание контроллера. Для этого идем на официальный сайт Arduino и скачиваем оттуда подходящий для установленной у вас версии среды архив описаний (для 1.6, для 1.5, для 1.0).

Содержимое архива следует извлечь в папку hardware папки среды Arduino. В дальнейшем я описываю происходящее на примере среды 1.0.3, которой пока пользуюсь.

Когда описания скопированы, следует запустить Arduino и загрузить скетч программатора в Arduino, которая будет использоваться в качестве этого самого программатора. Скетч находится в меню Файл — Примеры — ArduinoISP.

image

Разумеется, следует выбрать свою плату и порт. Я выбираю Mega, потому что у меня она и есть:

image

После загрузки скетча программатора необходимо переключиться на целевую плату. Т.е. в нашем случае — ATmega328 с частотой 8 МГц и внутренним задающим генератором. Она будет в списке плат, если описания, о которых говорил выше, скопированы правильно:

image

Теперь нужно соединить линии MISO, MOSI и SCK платы-программатора и платы с будущей Arduino, а также подключить RESET, GND и VCC. Плюс питания лучше именно в последнюю очередь.

Исходя из приведенной выше инфографики и описания Arduino Mega, вырисовывается следующая картина:

SPI — Arduino Mega — ATmega328p

MISO — 50 — 16
MOSI — 51 — 15
SCK — 52 — 17
SS (RESET) — 53 — 29

Физическое подключение на ваш вкус, я применил исключительно варварский метод — обычные макетные провода прямо в отверстия платы, без пайки и изоляции:

image

Если все готово — записываем загрузчик. Сначала убеждаемся, что выбран правильный программатор (Сервис — Программатор — Arduino as ISP):

image

Потом делаем Сервис — Записать загрузчик:

image

После этого на выходе — минималистическая плата Arduino, для загрузки скетчей в которую можно использовать адаптер USB-Serial или полноценную плату Arduino с таким адаптером на борту. В первом случае нужно соединить крест-накрест RX и TX, и не забыть подключить общую землю. Во втором случае дополнительно необходимо замкнуть на землю RESET Arduino, которая используется в качестве адаптера.

Если у вас, как и у меня, нет цепи автоматического сброса контроллера перед загрузкой скетча, то вариантов два: либо дернуть его сброс, либо просто включить его питание, когда среда Arduino напишет о начале загрузки.

Корпус


Корпус, как я и говорил — модульный. Это значит, что на скрытую от глаз внутреннюю часть можно пустить тот пластик, который залежался и больше никуда не годится. В него можно одеть электронику:

image

Обращаю внимание на тот факт, что корпус специфичный и рассчитан на то, чтобы в него поместился мой вариант начинки.

Ротор я предлагаю делать прозрачным, чтобы он рассеивал свет индикаторов. Для большей увесистости внутрь ротора можно вложить гайку М16:

image

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

Я печатал ротор с заполнением 10%, остальные элементы — с заполнением 5%. Пластик — PLA. Установленная температура сопла на моем принтере — 200С на первых трех слоях, 185С — на последующих. К сожалению, не могу сказать, какая истинная температура сопла. Стол холодный.

Сборка простая.



Платы размещаются в пазах прочного корпуса, светодиоды — ножками в пазы нижней части прочного корпуса. Антенна передатчика выводится вниз, точно так же вниз выводится приемник беспроводной зарядки — чтобы он был ближе к этой самой зарядке.



Начинка фиксируется промежуточной пластиной, в паз которой проходит жгут проводов энкодера.

Энкодер фиксируется верхней пластиной, все вместе стягивается винтами M4x30, которые сами нарезают себе резьбу в пластике.

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

Код
В коде нужно задать команды на включение и выключение своих периферийных устройств. Опционально — поменять таймаут автовыключения.

Это все располагается в секции переменных.

// Код сна: http://donalmorrissey.blogspot.ru/2010/04/sleeping-arduino-part-5-wake-up-via.html
// Библиотека Livolo: http://forum.arduino.cc/index.php?action=dlattach;topic=153525.0;attach=108106


#include <avr/sleep.h>
#include <avr/power.h>
#include <livolo.h>

#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off) 
#define adc_enable() (ADCSRA |= (1<<ADEN)) // re-enable ADC


#define  txPin  7 //  пин передатчика
Livolo livolo(txPin);

#define PULSESHORT 450  
#define PULSELONG  1350
#define PULSESYNC  13950
#define encA 5
#define encB 6 // пины энкодера
#define buttonPin 2 // пин кнопки (прерывание)
#define roomLed 10 // пин индикатора комнаты
#define kitchenLed 9 // пин индикатора кухни
#define switchLed 3 // пин индикатора переключения освещения
#define switchLedTimeOut 150 // время свечения индикатора переключения освещения
#define switchTreshold 4 // количество срабатываний энкодера до переключения
#define offDelay 15000 // таймаут автовыключения
#define rLev 3 // количество уровней света комнаты
#define kLev 3 // количество уровней света кухни
#define glev 2 // количество уровней света всего
#define txPowerPin 8 // пин питания передатчика


#define kitchenBackLightOn1 12
#define kitchenBackLightOn2 34
#define kitchenMainLightOn 56

#define roomBackLightOn 12
#define roomMainLightOn1 34
#define roomMainLightOn2 56

#define mainLightOn 12


#define kitchenBackLightOff1 12
#define kitchenBackLightOff2 34
#define kitchenMainLightOff 56

#define roomBackLightOff 12
#define roomMainLighOtff1 34
#define roomMainLightOff2 56

#define mainLightOff 12

#define LivoloID 8500

volatile byte rotorMode = 0; // режим работы
byte currentMode = 0; // текущий режим работы
int curEncA, prevEncA, curButton, prevButton; // текущее значение энкодера, предыдущее
byte encCountPlus = 0; // счетчик энкодера
byte encCountMinus = 0; // счетчик энкодера
unsigned long offTimeOut = 0; // счетчик таймера автовыключения
unsigned long modeTimeOut = 0; // счетчик таймера короткого нажатия для переключения режима
unsigned long switchLedTime = 0; // счетчик выключения индикатора переключения освещения
unsigned long modeTime = 0; // длительность нажатия
unsigned int modeTreshold = 500; // пороговая длительность короткого нажатия (короче ххх мс)
unsigned int bounceTreshold = 200; // пороговая длительность дребезга контактов
byte rLevState = 0;
byte kLevState = 0;
byte gLevState = 0;
// флаги света k - кухня, r - комната, h - коридор, b - ванная
boolean kBackState = false;
boolean kBackState1 = false;
boolean kMainState = false;
boolean rBackState = false;
boolean rMainState = false;
boolean rMainState1 = false;
boolean hMainState = false;
boolean bMainState = false;
boolean afterSleep = false; // флаг выхода из режима сна
boolean modeTimeOutStart = false;
boolean switchLedOn = false;
boolean allOn = false; // флаг "все включено" для индикатора переключения
boolean allOff = false; // флаг "все выключено" для индикатора переключения


static void ookPulse(int on, int off) {
  digitalWrite(txPin, HIGH);
  delayMicroseconds(on);
  digitalWrite(txPin, LOW);
  delayMicroseconds(off);
}


static void rcSend(long remoteCode) {
  
 for (byte reSend = 0; reSend < 8; reSend++) {
  for(byte repeat=0; repeat<4; repeat++){

   for (byte i = 24; i>0; i--) { // transmit remoteID
    byte txPulse=bitRead(remoteCode, i-1); // read bits from remote ID
     // Serial.print(txPulse);
      switch (txPulse) {
    
        case 0:  // 00
          ookPulse(PULSESHORT,PULSELONG);
          //ookPulse(PULSESHORT,PULSELONG);
          break;
      
        case 1:  // 11
          ookPulse(PULSELONG,PULSESHORT);
          //ookPulse(PULSELONG,PULSESHORT);
          break;
      } // switch
    } // for loop
    ookPulse(PULSESHORT,PULSESYNC); // S(ync)
  //  Serial.println();
  } // repeat
 }
delay(150);
}

void switchLedToggle() {
  digitalWrite(switchLed, HIGH);
  switchLedTime = millis();
  switchLedOn = true;
}

void lightsUp(boolean lightsUpMode) {

// чтобы при включении после сна при "выключении" свет выключался с максимума
// а при "включении" - с минимума
  if (afterSleep == true) {
    if (lightsUpMode == false) {
      gLevState = 1;
      rLevState = 3;
      kLevState = 3;
    } else 
           {gLevState = 0;
            rLevState = 0;
            kLevState = 0;
           }
  afterSleep = false; // сброс признака "после сна"
  }
  
// Все освещение
  if (rotorMode == 2) {
   if (lightsUpMode == false){
    if (allOff == false) {
     switchLedToggle();
     if (digitalRead(buttonPin) == HIGH) {    
      // выключить все
      gLevState = 0;
      rLevState = 0;
      kLevState = 0;
      
      rcSend(kitchenBackLightOff1);
      rcSend(kitchenBackLightOff2);
      rcSend(roomBackLightOff);
      livolo.sendButton(LivoloID, mainLightOff);      
      
    }
   allOff = true;
   allOn = false;
   }
  }

    if (lightsUpMode == true){
      // включить все, что не включено
    if (allOn == false) {
     switchLedToggle();
     if (digitalRead(buttonPin) == HIGH) {
      gLevState = 1;
      rLevState = 3;
      kLevState = 3;
      
      rcSend(kitchenBackLightOn1);
      rcSend(kitchenBackLightOn2);
      rcSend(roomBackLightOn);
      
      livolo.sendButton(LivoloID, mainLightOff);      // сначала выключим все Livolo
      livolo.sendButton(LivoloID, kitchenMainLightOn); //#1 теперь включим
      livolo.sendButton(LivoloID, roomMainLightOn1);  // #2  
      livolo.sendButton(LivoloID, roomMainLightOn2); // #3     
      livolo.sendButton(LivoloID, mainLightOn); // #6
   
      }
      allOn = true;
      allOff = false;
     }
    }
   }

// Кухня
  if (rotorMode == 1) {

    if (lightsUpMode == false && kLevState > 0) {
      switchLedToggle();
     if (digitalRead(buttonPin) == HIGH) {
      if (kLevState == 3) {
        // выкл верх
       livolo.sendButton(LivoloID, kitchenMainLightOff); // #3     
      }

      if (kLevState == 2) {
        // выкл фон 2
      livolo.sendButton(LivoloID, kitchenBackLightOff2); // #6        
      }      

      if (kLevState == 1) {
        // выкл фон 1
      rcSend(kitchenBackLightOff1);
      
      }    
     }
      
      if (kLevState!=0) {
      kLevState--;}
       //   Serial.println(kLevState);
   }

    if (lightsUpMode == true && kLevState < 3) {
      switchLedToggle();
      kLevState++;
      if (digitalRead(buttonPin) == HIGH) {
      if (kLevState > 3) {kLevState = 3;}
       //  Serial.println(kLevState);
     
      if (kLevState == 1) {
        // вкл фон 1
      
      rcSend(kitchenBackLightOn1);

      }

      if (kLevState == 2) {
        // вкл фон 2
      livolo.sendButton(LivoloID, kitchenBackLightOn2); // #6
      }      

      if (kLevState == 3) {
        // вкл верх
      livolo.sendButton(LivoloID, kitchenMainLightOn); // #3     
      }    
     }
    }
  
  }

// Комната
  if (rotorMode == 0) {
   if (lightsUpMode == false && rLevState > 0) {
    switchLedToggle();
    if (digitalRead(buttonPin) == HIGH) {      
      if (rLevState == 3) {
        // выкл верх1
      livolo.sendButton(LivoloID, roomMainLighOtff1); //#1
      }

      if (rLevState == 2) {
        // выкл верх
      livolo.sendButton(LivoloID, roomMainLightOff2);  // #2  
      }      

      if (rLevState == 1) {
        // выкл фон
      rcSend(roomBackLightOff);
      }    
    }
      if (rLevState != 0) {
      rLevState--;
      }
    }

    if (lightsUpMode == true && rLevState < 3) {
     switchLedToggle();
     rLevState++;
     if (digitalRead(buttonPin) == HIGH) {      
      if (rLevState == 1) {
        // вкл фон
      rcSend(roomBackLightOn);
      }

      if (rLevState == 2) {
        // вкл верх
      livolo.sendButton(LivoloID, roomMainLightOn1); //#1
      }      

      if (rLevState == 3) {
        // вкл верх 1
      livolo.sendButton(LivoloID, roomMainLightOn2);  // #2  
      }    
     }
    }
  
  }  
  
}

void wakeUp() {
  detachInterrupt(0);
}

void setMode() {

  if (rotorMode >= 2 ) {
     rotorMode = 0;
  } else {
    rotorMode++;
  }

 offTimeOut = millis();

}

void ledBlink() {
 
  for (byte iLed = 0; iLed<3; iLed++) {
    digitalWrite(kitchenLed, HIGH);
    digitalWrite(roomLed, HIGH);
    delay(100);
    digitalWrite(kitchenLed, LOW);
    digitalWrite(roomLed, LOW);
    delay(100);    
 }

 
}

void setLed() {
 
   if (rotorMode == 0) { // Комната
    digitalWrite(roomLed, HIGH);
    digitalWrite(kitchenLed, LOW);
  }

  if (rotorMode == 1) { // Кухня
    digitalWrite(roomLed, LOW);
    digitalWrite(kitchenLed, HIGH);
  }

  if (rotorMode == 2) { // Комната и кухня
    digitalWrite(roomLed, HIGH);
    digitalWrite(kitchenLed, HIGH);
  } 
  
}



void enterSleep()
{
// ledBlink();
 afterSleep = true;
 digitalWrite(txPin, LOW); 
 digitalWrite(txPowerPin, LOW);
 digitalWrite(roomLed, LOW);
 digitalWrite(kitchenLed, LOW); 
 digitalWrite(switchLed, LOW); 
 pinMode(txPin, INPUT);
 pinMode(txPowerPin, INPUT);
 pinMode(roomLed, INPUT);
 pinMode(kitchenLed, INPUT); 
 pinMode(switchLed, INPUT); 

 attachInterrupt(0, wakeUp, LOW);

  adc_disable();

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  
  sleep_enable();
  
  sleep_mode();
  
 sleep_disable();
 power_all_enable();
 
 pinMode(txPin, OUTPUT);
 pinMode(txPowerPin, OUTPUT);
 pinMode(roomLed, OUTPUT);
 pinMode(kitchenLed, OUTPUT); 
 pinMode(switchLed, OUTPUT); 
 digitalWrite(txPin, LOW); 
 digitalWrite(txPowerPin, HIGH);
 
// ledBlink();
 setLed();
 offTimeOut = millis();
 allOn = false;
 allOff = false;

}

void setup() {
 // Serial.begin(115200);
 pinMode(txPin, OUTPUT);
 pinMode(txPowerPin, OUTPUT);
 pinMode(roomLed, OUTPUT);
 pinMode(kitchenLed, OUTPUT); 
 pinMode(switchLed, OUTPUT); 
 digitalWrite(txPin, LOW); 
 digitalWrite(txPowerPin, HIGH);
 digitalWrite(roomLed, LOW); 
 digitalWrite(kitchenLed, LOW);
 digitalWrite(switchLed, LOW);
//  pinMode(buttonPin, INPUT_PULLUP);
 pinMode(buttonPin, INPUT);
 pinMode(encA, INPUT);
 pinMode(encB, INPUT);
 prevEncA = digitalRead(encA);
 offTimeOut = millis();
 rotorMode = 0;
 setLed();
 prevButton = digitalRead(buttonPin);
}



void loop() {

if ((millis() - offTimeOut) > offDelay) {
     enterSleep();

 } else {  
 
 // выключение индикации переключения режимов  
 if (switchLedOn == true) {
   if ((millis() - switchLedTime) > switchLedTimeOut) {
     digitalWrite(switchLed, LOW);
     switchLedOn = false;
   }
 }

// сброс таймера автовыключения при нажатом энкодере
if (digitalRead(buttonPin) == LOW) {
  offTimeOut = millis();
}

// переключение режимов
curButton = digitalRead(buttonPin);
  if ((prevButton == HIGH) && (curButton == LOW)) {
    if (modeTimeOutStart == false) {
      modeTimeOut = millis();
      modeTimeOutStart = true;
    }
  } else {
        if (modeTimeOutStart == true) {
          modeTime = millis() - modeTimeOut;
          if ((modeTime < modeTreshold) && (modeTime > bounceTreshold)) {
            setMode();
            modeTimeOutStart = false;
            prevButton = digitalRead(buttonPin);
          } else {
              modeTimeOutStart = false;
              prevButton = digitalRead(buttonPin);
          }
        }
  }
   
// переключение индикации режимов 
if (currentMode != rotorMode) { // если текущий режим не совпадает с установленным, то устанавливаем текущий режим
  currentMode = rotorMode;

// и включаем соответствующую индикацию

  setLed();  
}

// подсчет импульсов
 curEncA = digitalRead(encA);
   if ((prevEncA == LOW) && (curEncA == HIGH)) {
     offTimeOut = millis();
     if (digitalRead(encB) == LOW) {
       encCountMinus++;
       encCountPlus = 0;
      // Serial.println("Encoder Minus");

       if (encCountMinus > switchTreshold) {
          encCountMinus = 0;
          lightsUp(false);
        }

     } else {
       encCountPlus++;
       encCountMinus = 0;
      // Serial.println("Encoder Plus");       

       if (encCountPlus > switchTreshold) {
          encCountPlus = 0;
          lightsUp(true);
        }
     }
   } 
   prevEncA = curEncA;
 }
}




Модель корпуса по ссылке.

Все.

P.S.: Я постарался ничего не забыть, но мог. Если так — прошу прощения и приложу все силы, чтобы верно ответить на наводящие вопросы и исправить ошибки.

Комментарии (34)


  1. adson
    28.03.2018 06:19
    +1

    Выглядит отлично, только, кажется, скорость реакции низкая, большая задержка, если судить по видео.
    И ручка — напечатанная или купленная? Именно она и понравилась мне больше всего :-)


    1. spc Автор
      28.03.2018 09:04

      У задержки на видео две причины. Во-первых, так получилось, что одна из групп фонового света на кухне разбита на две части и управляется разными командами. Во-вторых, используемые радиопротоколы довольно медленные (по моим расчетам, на одну посылку-включение уходит порядка 0,7 секунды). Можно сделать в три-четыре раза быстрее, но снизится помехозащищенность, которая здесь обеспечивается самым варварским многократным повтором команды.

      Ручка целиком (ну, кроме вставленной в нее гайки) напечатана.


      1. adson
        28.03.2018 09:13

        а смотрится металлическим монолитом, круто


  1. Tachyon
    28.03.2018 06:45

    Спасибо за интересную статью. Напечатанный вами корпус удобен в плане хвата (не выскользнет и рука не вспотеет, если держать его долго), но удобен ли он в плане чистоты? Просто сам дома иногда протираю выключатели от жирных и грязных пятен, но выключатели ровные, а здесь — испещрённая поверхность удобная для хвата. Как будут обстоять дела с ней, ватная палочка бутыль спирта и медитативный процесс чистки? Не сочтите меня эстетом, но всё что иногда пачкается и должно содержаться в чистоте я предпочитаю белого цвета. Тем более в тёмной комнате найти белое будет быстрее (это я про пульт управления светом) НО это только лично моё мнение.


    1. adson
      28.03.2018 06:51

      Если смотреть с точки зрения практичности, на мой взгляд идеальная крутилка — у старых iPod'ов первых версий. Легко протирается, абсолютный минимализм деталей (для пользователя). Легко добавить функционал в виде подсветки (разного цвета), круговой шкалы и т.п. И, главное, понятна вообще любому пользователю


    1. spc Автор
      28.03.2018 09:11

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

      В основном внешний вид определяется тем, что за обилием деталей я пытался хоть как-то скрыть (или отвлечь внимание) от огрехов печати. А так как корпус модульный и легкосъемный, то при желании можно напечатать оболочку любого другого вида (условно гладкую, например), или просто снимать ее, чтобы помыть щеткой и водой с мылом.

      Вот, к примеру, один из промежуточных вариантов оболочки:


  1. sav13
    28.03.2018 07:55

    Плюс самоделкиным!
    Где меги 328 по дешевке покупали?


    1. spc Автор
      28.03.2018 09:35

      Покупал у Polida2008 на eBay, но это было давно и неправда, и 10 штук за $11. А сейчас я посмотрел на ценник и поразился — $18,5.


      1. rustavelli
        28.03.2018 18:31

        при том, что готовые pro mini стоили по $1.5


  1. dernuss
    28.03.2018 08:32

    Взмомать управление освещением вашему соседу не составит труда.


    1. spc Автор
      28.03.2018 09:18

      Знаете, если честно, я уже устал от таких комментариев. Разумеется, всем понятно, что это абсолютно открытый, абсолютно не защищенный от помех и абсолютно ненадежный протокол.

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

      Да и вообще, как-то это по-детски: прибежать, понажимать на кнопки, а потом что? Повесить на дверь табличку «зацени, как я мигаю твоей лампочкой»? Ну максимум — DOS-атака, пока в оставленном гипотетическим хулиганом брелке работают батарейки.


      1. dernuss
        28.03.2018 09:22

        а потом что?
        Счет за электричество, ну или пожар… тьфу, тьфу
        На самом деле наверное несложно дописать хоть какое нибудь кодирование.

        Я его использую уже лет пять

        Это все равно что сказать — Я перебегаю на красный свет уже 5 лет


        1. spc Автор
          28.03.2018 09:32

          На самом деле наверное несложно дописать хоть какое нибудь кодирование


          Да вы шутите, наверное. Когда я только-только хвастался автоматикой, мне говорили «а зачем вообще выключать свет, поставил энергосберегайки, и выгода от выключения будет минимальна по сравнению с удобством».

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

          Это все равно что сказать — Я перебегаю на красный свет уже 5 лет


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

          Скорее, здесь нужно говорить о некотором балансе цены, безопасности и удобства. То есть, как в том анекдоте: выбирайте любые два параметра.


          1. dernuss
            28.03.2018 10:19

            Кодирование можно использовать только если целиком заменить всю периферию и управляющий контроллер.

            Я конечно не спец в атмегах, но вот люди делают AES128
            habrahabr.ru/post/224383
            Да и вообще гугл выдаёт много ссылок
            По этому не понимаю зачем вам менять микроконтроллер.

            Обратите внимание, что те же радиопротоколы используются в многочисленных домашних сигнализациях, гаражных системах (да, самых дешевых, но все же

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


            1. spc Автор
              28.03.2018 10:59

              Я, наверное, не совсем ясно выразился. В моем случае контроллер следует за периферией. Какова периферия — таков и управляющий блок. Периферию, без ее полной замены, изменить невозможно. А в моей периферии никакого кодирования отродясь не было, и она вся — заводская, где код жестко прошит в местные контроллеры и не подлежит замене без разделывания всего и вся на биты, на что я неспособен.

              А уж если думать о надежной, безопасной и более-менее удобной системе, то надо переходить на готовые стандарты домашней (и не только) автоматики, а не городить собственный огород (тем более, что мне ой как далеко до того уровня, что демонстрируется по приведенной ссылке про AES).

              Поэтому замена периферии ведет к замене контроллера.

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


              1. dernuss
                28.03.2018 11:11

                А в моей периферии никакого кодирования отродясь не было, и она вся — заводская, где код жестко прошит в местные контроллеры и не подлежит замене без разделывания всего и вся на биты, на что я неспособен.

                А ну теперь понятно. Ну это жесть какая то. Я бы не стал использовать такое)

                тем более, что мне ой как далеко до того уровня, что демонстрируется по приведенной ссылке про AES

                Есть ссылки сильно по проще. Можно добавить как либу в ардуино.

                А уж если думать о надежной, безопасной и более-менее удобной системе, то надо переходить на готовые стандарты домашней (и не только) автоматики


                А таких стандартов вроде и нет, каждый что во что горазд. По крайней мере я не знаю таких стандартов беспроводных. Но если вы знаете такие стандарты, скажите мне. А то вдруг я буду делать что нибудь. А когда нибудь скорее всего и буду…

                Достаточно включить поблизости глушилку — и все беспроводные системы лапками кверху.

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


                1. spc Автор
                  28.03.2018 11:25

                  А таких стандартов вроде и нет, каждый что во что горазд


                  Ну это вы неправы. Раньше был очень популярен X10. Сейчас модно Zigbee, Z-Wave, Insteon. Мы же не говорим про китайцев, у которых на каждый выключатель свой апп, правда?


                  1. dernuss
                    28.03.2018 12:36
                    +1

                    Zigbee, Z-Wave это низкоуровневые протоколы по типу wifi, а дальше кто во что горазд.
                    Или я не прав?
                    То есть я не могу вот так просто взять и поуправлять покупным zigbee диммером сам? или могу?


                    1. spc Автор
                      28.03.2018 13:16
                      -1

                      А вот на этом месте мои познания заканчиваются — я просто не интересовался этими протоколами. С другой стороны, у openHAB есть связка с ZigBee, так что, в теории — можно порулить каким-нибудь покупным диммером.

                      Опять же, с openHAB я не знаком.


      1. vconst
        28.03.2018 12:06

        «зацени, как я мигаю твоей лампочкой»
        Уже было в Симпсонах в Теории большого взрыва.


  1. p_fox
    28.03.2018 08:43

    Насколько я понял, крутилка только делает вкл/выкл света? Никакого плавного регулирования?
    В таком случае мне непонятна логика управления. Разве не удобнее было сделать именно поворотом ручки выбирать источник света (возможно с индикацией диодами или цветом подсветки, чтобы знать что конкретно выбрано), а на нажатие сделать функцию вкл/выкл?


    1. spc Автор
      28.03.2018 09:27

      Да, все правильно, только включение и выключение. Возможно, у нас по-разному работает голова: мне проще выбрать одну из трех групп света нажатием и включать лампы в ней вращением, чем запомнить, какой лампе соответствует один из восьми (у меня пульт управляет восемью источниками света) светодиодов или один из восьми оттенков одного RGB-светодиода.

      Я здесь-то диоды выбирал по принципу «как в машине»: зеленый — ближний свет (кухня), синий — дальний (комната).

      Но вообще, у меня лишь один из вариантов интерфейса, который я посчитал для себя же наиболее удобным. Разумеется, нет проблем реализовать другую логику.


      1. rustavelli
        28.03.2018 18:34

        шкалу из 8ми секторов вокруг крутилки нарисовать. Повернул в нужный сектор и нажал.


  1. Crazy_Pit
    28.03.2018 09:12

    в какой программе вы рисуете модели для печати на 3д принтере?


    1. spc Автор
      28.03.2018 09:13

      Autodesk 123d Design, но они прекратили поддержку программы и заменили ее на Fusion 360, с которой я никак не освоюсь.


      1. Foreglance
        28.03.2018 09:31
        +1

        Рекомендую попробовать PTC Creo Elements/Direct Modeling Express
        Сам только ей пользуюсь (пару туториалов к ней делал, по англицки только).


        1. spc Автор
          28.03.2018 09:49

          Спасибо, буду иметь в виду!


  1. max1muz
    28.03.2018 16:29
    +1

    Сначала я подумал что это статья про управление освещением в крупном городском парке. ЦПКИО — Центральный парк культуры и отдыха)


  1. plm
    28.03.2018 20:24

    Прочитал, но что-то я упустил, видимо. Почему в двух местах — комнате и кухне — надо управлять светом централизованно? Кнопка «отбой», отключающая свет везде, была бы гораздо практичнее.


    1. spc Автор
      28.03.2018 22:48

      Централизованный — потому что я увлекся аббревиатурами. Вообще это означает, что пульт умеет включать и выключать свет во всей квартире, на случай, если мне лень встать и подойти к выключателю.

      Выключение света во всей квартире одновременно — это лишь одна, но не главная функция пульта.

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


      1. plm
        29.03.2018 10:20

        Так, значит остается только один случай — надо включить свет там, где нас нет. Во-первых — зачем это может понадобиться, а во-вторых — как вы обеспечиваете «ЦПКИО-2Д Ротор всегда под рукой».


        1. spc Автор
          29.03.2018 10:57

          У меня такое ощущение, что я — инопланетянин, и все мои мысли абсолютно чужды населению Земли )

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

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

          Опять же, открою секрет. «Д» в названии — это сокращение от «демонстратор». Вообще-то, в значительной степени Ротор — демонстратор интерфейса, нежели какое-то сверхполезное для всех гуманоидов одновременно устройство. Мне, к примеру, нравится им пользоваться, но это мне.


          1. plm
            29.03.2018 19:33

            Вот! С этого надо было начинать! Демонстратор — и все понятно. А вы его описывали как реальную бытовую вещь — дверной замок, к примеру.
            У технарей вообще мысли «чужды населению Земли», мы с вами видать разные технари. У меня у света на кухне всего два состояния — «полумрак» (или дежурное освещение) и «все включено», у вас зачем-то еще и промежуточные состояния используются (у нас принцип «света много не бывает»). Да и квартира у меня не настолько большая, чтоб «бегать к выключателям».
            А вообще многолетний опыт обучения пользователей сложного оборудования мне подсказывает, что такой интерфейс мало кому понравится. Но если вы любите свиные хрящики — почему бы нет?


            1. dernuss
              30.03.2018 06:07

              у вас зачем-то еще и промежуточные состояния используются (у нас принцип «света много не бывает

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