Category Archives: Для дома

Будильник своими руками: Част 2 – Пищалка

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

// библиотека для работы I²C
#include <Wire.h>
#include <DHT.h>
#include <QuadDisplay.h>
// библиотека для работы с часами реального времени
#include "TroykaRTC.h"

// EEPROM — энергонезависимая память
// библиотека для записи и считывания информации с EEPROM
#include <EEPROM.h>

// PIN Для подключения Quad Display От Amperka
#define DISPLAY_PIN 7
// Кнопка
#define KEY_PIN 8
// Количество вариатнтов отображения информации:
// Часы, Дата, будильник, День недели, Температура, влажность
#define MODE_NUM 6
// задержка для функции loop
#define FREQ 200
// PIN для зуммера
#define BUZ_PIN 11
// PIN для датчика влажности и температуры
#define DHT_PIN 6
// Тип датчика влажности и температуры
#define DHT_TYPE DHT22

// количество циклов для долгого нажатия
#define EDIT_COUNTER 7

#define LONG_PRESS 2
#define SHORT_PRESS 1

// создаём объект для работы с часами реального времени
RTC clock;

// Объект для работы с датчиком температцры и влажности
DHT dht(DHT_PIN,DHT_TYPE);


byte longpress = 0;
byte shortpress = 0;

// Тип Текущей отображаемой информации
byte mode = 0;
// Режим редактирования
byte edit = 0;
// Как долго зажата кнопка
byte key_counter = 0;
// Текущее значение для текущего режима отображения
int cur_val;
// Время срабатывания будильника
int alarm_time = -1;
// Время срабатывания Snooze-а
int snooze_time = -1;
// Отображение точки по центру
uint8_t dot = 0x4;

void setup()
{
  pinMode(KEY_PIN,INPUT);
  pinMode(BUZ_PIN,OUTPUT);

  Serial.begin(9600);
  
  // инициализация часов
  clock.begin();
  // метод установки времени и даты в модуль вручную
  // clock.set(10,25,45,27,07,2005,THURSDAY);
  // метод установки времени и даты автоматически при компиляции
  //clock.set(__TIMESTAMP__);
}

void loop()
{
  // Считтываем было ли короткое нажатие или длинное
  byte KeyPressed = GetKeyPressed();

  // Запомнаем текущее значение для режима редактирования
  byte cedit = edit;
  // изменяем значения глобальных переменных в зависимости от нажатия
  AdjustGlobalVars(KeyPressed);
  
  // Если время для срабатывания будильника
    if ((snooze_time != -1) && (IsAlarmTime()))
    {
      // Короткое нажатие - snooze
      if (KeyPressed == 1)
      {
        // Сбрасываем на отображение часов
        mode = 0;
        // Прибавляем к будильнику 1 минуту
        snooze_time = snooze_time + 1;

        // проверяем не вышли ли мы за границы 60 минут
        if (snooze_time % 100 > 59)
          snooze_time += (100 - 60);
        // Проверяем не вышли ли мы за границы 24 часа
        if (snooze_time / 100 > 23)
          snooze_time -= 24;       
      }
      // Если долгое нажатие - отключить будильник
      else if (KeyPressed == 2)
      {
        // Сбрасываем все переменные
        snooze_time = alarm_time;
        mode = -1;
        edit = 0;
      }
      AlarmSound();
    }

  // Если режим редактирования
  if (edit > 0)
    EditCurrentMode(KeyPressed);
  else {
    // Если мы вышли из режима редактирования
    if (cedit != edit)
    {
      // то меняем текущее значение
      SetCurrentMode();
    }
    // Отображаем текущее значение
    DisplayCurrentMode();
  }

  // спим
  delay(FREQ);
}

// Не пора ли будильнику звонить
bool IsAlarmTime()
{
  uint8_t hour = clock.getHour();
  uint8_t minute = clock.getMinute();

  if ((snooze_time / 100 == hour) && (snooze_time %100 == minute))
  {
    return 1;
  }

  return 0;
}

// Было длинное или короткое нажатие?
byte GetKeyPressed()
{
  byte result = 0;
  if (digitalRead(KEY_PIN) == HIGH)
  {
    key_counter++; 
    if (key_counter > EDIT_COUNTER)
    {
      result = 2;
      key_counter = 0;      
    }    
  }
  else
  {

    if(key_counter > 0)
    {
      result = 1;
    }
    key_counter = 0;   
  }

  return result;
}

// Изменяем глобальные переменные в зависимости от нажатия
void AdjustGlobalVars(byte KeyPressed)
{
  if (KeyPressed == LONG_PRESS)
  {
    LongPressSound();
        
    // Редактируем либо первую либо вторую пару чисел
    edit++;
    if (edit > 2)
      edit = 0;
  }      
  else if(KeyPressed == SHORT_PRESS)
  {
    ShortPressSound();
    
    // В режиме отображения короткое нажатие меняет отображаемую информацию
    if (edit == 0)
      mode++;
    if (mode > MODE_NUM)
      mode = 0;
  }

}

void AlarmSound()
{
  tone(BUZ_PIN, 3500,40);
  tone(BUZ_PIN, 4500, 40);
  delay(100);
  tone(BUZ_PIN, 4500, 40);
}

void LongPressSound()
{
    tone(BUZ_PIN, 4500, 40);    
    delay(100);      
    tone(BUZ_PIN, 4500, 40);    
}

void ShortPressSound()
{
  tone(BUZ_PIN, 3500, 40);    
}

// Что именно мы редактируем
void EditCurrentMode(byte pressed)
{
  clock.read();

  // совпадает с тем что отображаем
  if (mode == 0)
    EditHourMinute(pressed);
  else if (mode == 1)
    EditDate(pressed);    
  else if (mode == 2)
    EditAlarm(pressed);
  else if (mode == 3)
    EditDay(pressed);
  
}

// Редактирование законченоо - пора поменять текущие значения
void SetCurrentMode()
{
  // Меняем время
  if (mode == 0)
  {
    uint8_t hour = cur_val / 100;
    uint8_t minute = cur_val % 100;
    clock.set(hour, minute, 0, clock.getDay(), clock.getMonth(),
              clock.getYear(), clock.getDOW());
  }
  // Меняем дату
  else if (mode == 1)
  {
    uint8_t day = cur_val / 100;
    uint8_t month = cur_val % 100;
    clock.set(clock.getHour(), clock.getMinute(),
              clock.getSecond(), day, month, clock.getYear(), 
              clock.getDOW());

    mode = 3;
    edit = 1;
  }
  // Устанавливаем будильник
  else if (mode == 2)
  {
    alarm_time = cur_val;
    snooze_time = cur_val;
    mode = 0;
  }
  // Меняем день недели
  else if (mode == 3)
  {
    clock.set(clock.getHour(), clock.getMinute(),
              clock.getSecond(), clock.getDay(), clock.getMonth(),
              clock.getYear(), cur_val);    
  }
}

// Редактирование времени
void EditHourMinute(byte pressed)
{
  // clock.set(10,25,45,27,07,2005,THURSDAY);
  int hour = int(cur_val / 100);
  int minute = int(cur_val % 100);
   
  // Если редактируем первую пару при коротком нажатии прибавляем час
  if ((pressed==1) && (edit == 1))
    hour = hour +1;
  // Если вторую - прибавляем минуту
  else if ((pressed==1) && (edit == 2))
    minute = minute +1;

  // Проверка выхода за границы
  if (hour > 23)
    hour = 0;
  if (minute > 59)
    minute = 0;
    
  // Обновляем переменую из которой будет считано новое время
  cur_val = hour * 100 + minute;

  // Эфект моргания
  static bool show;
  show = !show;
  if (show)
  {
    // При редактировании первой пары отображаем только минуты
    if (edit == 1)
      displayLastTwo(DISPLAY_PIN, minute);
    // При редактировании второй пары отображаем только часы
    if (edit == 2)
      displayFirstTwo(DISPLAY_PIN, hour);
  }
  else
  {
    // Отображаем целиком
    DisplayHourMinute(cur_val);
  }
}

// Изменение даты аналонично идменению времени
void EditDate(byte pressed)
{
  uint8_t day = int(cur_val / 100);
  uint8_t month = int(cur_val % 100);
  
  static bool blnk;
  blnk = !blnk;

  if ((pressed==1) && (edit == 1))
    day = day +1;
  else if ((pressed==1) && (edit == 2))
    month = month +1;

  if (day > 31)
    day = 1;
  if (month > 12)
    month = 1;

  cur_val = day*100 + month; 

  if (blnk)
  {
    if (edit == 1)
      displayLastTwo(DISPLAY_PIN, month);
    if (edit == 2)
      displayFirstTwo(DISPLAY_PIN, day);
  }
  else
  {
    displayInt(DISPLAY_PIN, cur_val, true, 0x0);
  }
}

// Изменение дня недели
void EditDay(byte pressed)
{
  if (pressed == 1)
    cur_val++;

  if (cur_val > 6)
    cur_val = 0;
    
  static bool show;
  show = !show;
  
  if (show)
  {
    DisplayDay(cur_val, true);
  }
  else
  {
    DisplayDay(cur_val, false);
  }
}

// Изменение будильника
void EditAlarm(byte pressed)
{
  uint8_t minute = cur_val % 100;
  uint8_t hour = cur_val / 100;

  if ((pressed==1) && (edit == 1))
    hour = hour +1;
  else if ((pressed==1) && (edit == 2))
    minute = minute +1;

  if (hour > 23)
    hour = 0;

  if (minute > 59)
    minute = 0;

  cur_val = hour * 100 + minute;
  
  static bool blnk;
  blnk = !blnk;
  
  if (blnk)
  {
    if (edit == 1)
      displayLastTwo(DISPLAY_PIN, minute);
    if (edit == 2)
      displayFirstTwo(DISPLAY_PIN, hour);
  }
  else
  {
    displayInt(DISPLAY_PIN, cur_val, true, 0x0);
  }
}

// Отображаем текущую выбраную информацию
void DisplayCurrentMode()
{
  if (mode == MODE_NUM)
    mode = 0;
    
  //displayClear(DISPLAY_PIN);

  // запрашиваем данные с часов
  clock.read();

  if (mode == 0)
    DisplayHourMinute(-1);
  else if (mode == 1)
    DisplayDate();
  else if (mode == 2)
    DisplayAlarm();
  else if (mode == 3)
    DisplayDay(-1, false);
  else if (mode == 4)
    DisplayTemp();
  else if (mode == 5)
    DisplayHum();
  else
    DisplayHourMinute(-1);

}

// Отображаем влажность
void DisplayHum()
{
  float h = dht.readHumidity();
  displayFloat(DISPLAY_PIN, h, 2, true);
}

// Отображаем температуру
void DisplayTemp()
{
  float t = dht.readTemperature();
  displayTemperatureC(DISPLAY_PIN, round(t), false);
}

// Отображаем время
void DisplayHourMinute(int cv)
{
  int hm = cv;
  if (cv == -1)
  {
    hm = clock.getHour() * 100 + clock.getMinute();  
    cur_val = hm;
  }
  
  uint8_t dot = 0x4;
  uint8_t sec = clock.getSecond();
  
  if (sec % 2)
    dot = 0x0;
  
  displayInt(DISPLAY_PIN, hm,true,dot);
}

// ОТображаем дату
void DisplayDate()
{
  int dm = clock.getDay()*100 + clock.getMonth();

  cur_val = dm;
  displayInt(DISPLAY_PIN, dm, true, 0x4);
}

// Отображаем будильник
void DisplayAlarm()
{
  cur_val = alarm_time;
  if (alarm_time != -1)
    displayInt(DISPLAY_PIN, alarm_time, true, 0x4);
  else
    displayDigits(DISPLAY_PIN, QD_MINUS, QD_MINUS,
                                QD_MINUS, QD_MINUS);
}

// Отображаем день недели
void DisplayDay(int cv, bool minus)
{
  
  uint8_t dow = cv;
  if (cv == -1)
  {
    dow = clock.getDOW();
    cur_val = dow;
  }
  uint8_t first = QD_NONE;
  if (minus)
    first = QD_MINUS;
  if (dow == 0)
    displayDigits(DISPLAY_PIN, first, QD_S, QD_U, QD_n);
  else if (dow == 1)
    displayDigits(DISPLAY_PIN, first, QD_H, QD_O, QD_n);
  else if (dow == 2)
    displayDigits(DISPLAY_PIN, first, QD_t, QD_U, QD_E);
  else if (dow == 3)
    displayDigits(DISPLAY_PIN, first, QD_H, QD_E, QD_d);
  else if (dow == 4)
    displayDigits(DISPLAY_PIN, first, QD_t, QD_h, QD_u);
  else if (dow == 5)
    displayDigits(DISPLAY_PIN, first, QD_F, QD_r, QD_I);
  else if (dow == 6)
    displayDigits(DISPLAY_PIN, first, QD_S, QD_a, QD_t);
  else 
  {    
    displayDigits(DISPLAY_PIN, QD_o, QD_o, QD_o, QD_o);
  }
    
}

И пример работы

Теперь ситуация исправлена и пришло время двигаться дальше. Единственное пока, я это делал, то наблюдал странные артефакты, когда пытался задать дату, и для отладки приобрёл ещё один 4 значный дисплей, так как простое логирование с использованием Serial мне не помогало.

20160411_23201620160412_204444

Этот дисплей немного меньше и имеет регулируему яркость.

Из минусов – требует два пина для управления. Возможно мигрирую проект на него, так как текущий индикатор довольно яркий, особенно если оставлять его включенным ночью. Соответственно если добавить датчик освещённости, то можно будем менять яркость индикатора в зависимости от освещённости помещения, но посмотрим.

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

20160417_191527 20160417_191535

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

В процессе реализации решил, что пора переписать свой говно код с использованием классов, а то эти жуткие IF-ы и нагромождение функций выглядят как-то не очень. К тому же следующий шаг – это добавить световую индикацию и сделать возможность управлять будильником с телефона через Bluetooth, но с этим мне ещё предстоит разобраться – следите за обновлениями.

Update: Обновил коментарии в скетче.

Update2: Две функции используемы в скетче displayFirstTwo() и displayLastTwo() это функции, которые я добавил в библиотеку для работы с QuadDisplay от амперки.

Будильник своими руками: Часть 1 – Время, дата и день недели

Сейчас у всех есть телефоны и традиционные часы-будильники уже не так популярны, однако производители таких девайсов не сдаются и продолжают придумывать фишки, которые бы позволили им оставаться на плаву. Например, светобудильник, который синхронизируется с IPhone-ом:

20160305_220218Фактически это будильник + док станция. Я решил что мне нужен наподобие, только для Android-а… с беспроводной зарядкой… какой-нить весёленькой индикацией событий в телефоне… Ну и добавлю ещё каких-нить свистелок-перделок.

Первое, что нужно сделать – это конечно сами часы. Купил индикатор и часы реального времени (RTC) с батарейкой cr1220.

20160305_160942 20160305_160952

разместил это дело на макетке и подключил к Arduino

20160305_221732нашёл на сайте Амперки примеры для работы с часами реального времени и индикатором. Скачал библиотеки, необходимые для работы с ними и сделал простой скетч, который просто установил бы текущее время. Получилось примерно вот так:

20160305_222041Следующим шагом добавил кнопку, для изменения отображаемой информации и зуммер для индикации успешного нажатия. Для добавления Зуммера в свой проект не требуются дополнительной библиотеки – можно воспользоваться встроенной функцией tone.

Кнопку пока сделал как переключатель между временем, датой и днём недели. То есть по умолчанию “будильник” показывает часы и минуты, нажимаешь на кнопку – показывает дату, нажимаешь ещё раз – показывает день недели и так по кругу.

Вот как это выглядит и работает:

Ниже приведён полный исходный текст программы.

// библиотека для работы I²C
#include <Wire.h>
#include <QuadDisplay.h>
// библиотека для работы с часами реального времени
#include "TroykaRTC.h"

// EEPROM — энергонезависимая память
// библиотека для записи и считывания информации с EEPROM
#include <EEPROM.h>

// размер массива для времени с учётом завершающего нуля
#define DISPLAY_PIN 7 // Ножка QuadDisplay-я
#define KEY_PIN 8  // Ножка для кнопки
#define MODE_NUM 3  // Количество вариантов отображения инфы
#define FREQ 250  // Задержка в основном цикле
#define BUZ_PIN 11  // Ножка для зуммера

// создаём объект для работы с часами реального времени
RTC clock;

unsigned short mode = 0;

uint8_t dot = 0x4;

void setup()
{
  pinMode(KEY_PIN,INPUT);
  pinMode(BUZ_PIN,OUTPUT);

  Serial.begin(9600);
 
  // инициализация часов
  clock.begin();
  // метод установки времени и даты в модуль вручную
  // clock.set(10,25,45,27,07,2005,THURSDAY);
  // метод установки времени и даты автоматически при компиляции
  //clock.set(__TIMESTAMP__);
}

void loop()
{
  // Проверяем нажата ли кнопка
  if (digitalRead(KEY_PIN) == HIGH)
  {
    // Меняем индекс отображаемой инфорамции
    mode++;
    // Издаем короткий звук для подтверждения что нажатие кнопки считано
    tone(BUZ_PIN, 3500, 40);
  }

  // Проверяем не вышел ли наш индекс за границы
  if (mode == MODE_NUM)
    mode = 0;  // Если да то меняем на начальный

  // запрашиваем данные с часов
  clock.read();

  if (mode == 0)
    DisplayHourMinute(); // Отображаем Часы и Минуты
  else if (mode == 2)
    DisplayDate();  // Отображаем день и месяц
  else
    DisplayDay();   // Отображаем день недели

  delay(FREQ);
}

void DisplayHourMinute()
{
  int hm = clock.getHour() * 100 + clock.getMinute();  
  uint8_t dot = 0x4;  // Нам нужна точка между второй и третьей цифрами
  uint8_t sec = clock.getSecond();
  if (sec % 2) // Для эффекта мигания отображаем точку только
    dot = 0x0;  // на нечётной секунде
 
  displayInt(DISPLAY_PIN, hm,true,dot);
}

void DisplayDate()
{
  int dm = clock.getDay()*100 + clock.getMonth();
  displayInt(DISPLAY_PIN, dm, true, 0x4);
}

// Ниже я просто отображаю константы заданные в .h файле
// для часов, чтобы получились три буквы дня недели...
// ну или что-то похожее
void DisplayDay()
{
  uint8_t dow = clock.getDOW();
  if (dow == 7)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_S, QD_U, QD_n);
  else if (dow == 1)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_H, QD_O, QD_n);
  else if (dow == 2)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_t, QD_U, QD_E);
  else if (dow == 3)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_H, QD_E, QD_d);
  else if (dow == 4)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_t, QD_h, QD_u);
  else if (dow == 5)
    displayDigits(DISPLAY_PIN, QD_NONE, QD_F, QD_r, QD_I);
  else
    displayDigits(DISPLAY_PIN, QD_NONE, QD_S, QD_a, QD_t);
    
}

Ещё одна полезная функция – это датчик влажности и температуры на основе уже знакомого мне DHT22.

20160130_130724Добавить его в проект не составляет труда: требуется один дополнительный пин,

#define DHT_PIN 6

новый объект для работы с датчиком,

#define DHT_TYPE DHT22

DHT dht(DHT_PIN,DHT_TYPE);

две процедуры DisplayTemperature() и DisplayHumidity(), которые будут отображать температуру и влажность соответственно и будут работать аналогично DisplyaHourMinute()

void DisplayHumidity()
{
  float h = dht.readHumidity();
  displayFloat(DISPLAY_PIN, h, 2, true);
}

void DisplayTemperature()
{
  float t = dht.readTemperature();
  displayFloat(DISPLAY_PIN, t, 2, true);
}

и изменение количество вариантов отображаемой информации с 3х на 5, так как устройство теперь может показывать температуру и влажность.

#define MODE_NUM 5

Правда на половинной макетке, которую я использовал для прототипирования закончилось место, и я решил воспользоваться Troyka Shield-ом для прототипирования, для того, чтобы вынести часы реального времени и зуммер и освободить место для датчика влажности и температуры:

20160329_224355И хотя конечная цель проекта – это спарить Arduino с Android-ом, думаю что возможность устанавливать дату, время и будильник независимо от наличия телефона тоже пригодиться.

Добавлять ещё одну кнопку мне было лень, поэтому решил, что долгое нажатие на уже существующую и будет переводить “будильник” в режим установки времени и даты, а однакратные нажатия будут изменять значения, но это уже в следующей “серии”.

Монитор качества воздуха своими руками: Часть 2 – Сделай мне красиво

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

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

Для начала купил пластиковый корпус 100 на 100 на 40 с крышкой (взял на глаз). Оказалось, что Arduino Uno малость великоват,  для того, чтобы засунуть его в этот корпус вместе с батарейками (Кто бы мог подумать?), да и в целом ATmega328p-pu какая-то здоровая, поэтому решил искать варианты.

Первое что попалось на глаза – это ATTiny45, однако сходу перенести код не удалось, так как i2c тут реализован по-другому.

Следующим стал Arduino Nano. По сути тот же самый чип просто в другом форм факторе. Вот только Arduino IDE категорически отказывался заливать туда мои скетчи. Видимо через чур китайский

20160206_123021На просторах интернета был найден лёгкий способ бросить треску тебе в ебало мразь пример решения похожей проблемы. Собственно так я и сделал – поменял секцию Nano в файле boards.txt на нечто вроде этого:

nano.name=Arduino Nano

nano.upload.tool=avrdude
#nano.upload.protocol=arduino
nano.upload.using=arduino:arduinoisp

nano.bootloader.tool=avrdude
nano.bootloader.unlock_bits=0x3F
nano.bootloader.lock_bits=0x0F

nano.build.f_cpu=16000000L
nano.build.board=AVR_NANO
nano.build.core=arduino
nano.build.variant=eightanaloginputs

вариант подключения напрямую через USB так и не заработал, выдавая невнятное “did not find any USB device ‘usb'”, так что пришлось программировать Nano используя Arduino Uno в качестве программатора.

20160211_210204Отлично теперь всё работает, но тут я вспомнил, что у меня есть другой “девайс” со странным названием SEM0010 на ATmega328, который к тому же имеет макетную область. И о чудо – это практически Arduino Nano c ATmega328p-au и теми же 32мя контактами.

20160205_202226Единственное неудобство – это то что у него нет USB, поэтому его также пришлось перепрошивать используя Arduino Uno  в качестве программатора используя те же самые ножки и ICSP header на самом устройстве. Для проверки как обычно лучше всего походит пример blink:

20160227_165127Всё работает, так что залил свой скетч и стал припаивать датчики:

20160206_210002Заодно приобрёл и приделал к датчику пыли небольшой вентилятор, дабы тот протаскивал воздух через сенсор.

Вырезав в корпусе отверстия под датчик влажности и температуры на основе DHT22 и заодно окно под дисплей, стал впихивать всё это хозяйство в корпус.

20160206_20075020160206_222804

И вуаля – оно работает. Оказалось, правда, что я забыл сделать выключатель, поэтому пришлось воспользоваться зажимом типа “крокодил”. Позднее вставил маленький выключатель

20160211_212052В результате получился небольшой переносной девайс, который показывает температуру и влажность воздуха и имеет встроенный датчик пыли, через который небольшой вентилятор протягивает воздух. Выглядит вот так:

20160211_221021Однако в коде обнаружилась бага – как не пытался я дымить на устройство и совать его в пыльные места, так и не получилось, чтобы он показывал что-то отличное от нуля намекая, что воздух в Москве кристально чистый.

Заново подключил к Arduino Uno, сняв заднюю крышку:

20160227_171928Попробовал пример от производителя Waveshare, но он показывает какие-то фиксированные значения, и прыгает от “идеальное качества воздуха” до “сильно загрязнённое” – ему веры нет. К тому же, посмотрев в код, я увидел, что там значения домнажаются на какие-то ничем не обоснованные константы, так что ну его нафиг.

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

Последняя попытка привела меня на ещё один сайт с подобным проектом, где тоже есть непонятные мне домножения. Посмотрев ещё раз в Datasheet, я решил сделать очень просто. Выводить значение считанное с датчика не в вольтах, а в миливольтах, и больше не вычитать минимальное значения напряжения при отсутствии пыли, дабы увидеть если вообще какое-то изменение при “накуривании” сенсора. Заодно, руководствуясь графиком из Datasheet-а:

graphдобавил просто сообщение о качестве воздуха при разных значениях выдаваемого напряжения, вроде:

if (volt < 2500)
    return 0;
  else if (volt < 3600)
    return 1;
  else
    return 2;

и буду соответственно выводить на дисплей примерно так:

  if (bucket == 0)
    display.println("Perfect air");
  else if (bucket == 1)
    display.println("Avergae air");
  else if (bucket == 2)
    display.println("Light polution");
  else if (bucket == 3)
    display.println("Moderate pollution");
  else if (bucket == 4)
    display.println("Heavy pollution");
  else if (bucket == 5)
    display.println("Serious pollution");

20160227_213401Как будут интересные результаты – напишу финальный пост.

Монитор качества воздуха своими руками: Часть 1 – Начало

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

Для начала я купил датчик пыли на основе sharp GP2Y1010AU0F прменяется для обнаружения PM2.5 частиц (менее 2.5 микрон) и сигаретного дыма. Принцип действия довольно простой: фототранзистор распознает свет, отражённый от частиц, который посылает инфракрасный светодиод.

20160130_130529Для подключения к Arduino не требуется ничего особенного: чёрный провод земля, красный 5 вольт, жёлтый на цифровой Pin 7, для управления (вкл/выкл) тем самым инфракрасным светодиодом, и синий на один из аналоговых пинов, например A0, для чтения показаний датчика.

Для того, чтобы считать данные с датчика, согласно DataSheet-у нужно включить инфракрасный светодиод, подождать 280 микросекунд, и затем померить выходное напряжение датчика.

#define DUST_ILED 7
#define DUST_PIN 0
#define NO_DUST_VOLT 0.9

void setup() {
  pinMode(DUST_ILED, OUTPUT);
  digitalWrite(DUST_ILED, LOW);
 
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(DUST_ILED, HIGH);
  delayMicroseconds(280);

  int outVolt = analogRead(DUST_PIN);

  digitalWrite(DUST_ILED, LOW);

  float calcVoltage = outVolt * 5.0 / 1024.0 - NO_DUST_VOLT;
  if (calcVoltage < 0) {
    calcVoltage = 0;
  }

  delay(1000);
}

Здесь мы делим значение, которое считали с датчика на 1024 – это максимальное значение, которое может считать Arduino Uno на аналоговых пинах и умножаем на 5 – напряжение, которое мы подали на датчик.

Согласно DataSheet-у типичное напряжение, которое показывает датчик при отсутствии пыли – это 0.9 вольт, так что просто вычтем это значение и будем считать, что наличие пыли начинается при напряжении > 0.9 вольт.

Второе, что нужно сделать – это отображать считанные значения. Простейший вариант – это напечатать в консоль, но когда ходишь по городу, то лучше иметь какой-нить маленький дисплей и я купил  0.96″ OLED дисплей с интерфейсом i2c, которому требуется 2 аналоговых пина (SDA на A4, а SCL на A5) плюс напряжение и земля.

20160130_174819У AdaFruit есть отличная, но “тяжёлая” библиотека для работы с подобными дисплеями. Прежде чем ею можно воспользоваться нужно поменять пару вещей в зависимости от модели дисплея. В моём случае это i2c адрес, который нужно выставить в 0x3C . Так же может понадобиться другая AdaFruit библиотека – AdaFruit GFX.

#include <Adafruit_SSD1306.h>

#define DUST_ILED 7
#define DUST_PIN 0
#define NO_DUST_VOLT 0.9

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

void setup() {
  pinMode(DUST_ILED, OUTPUT);
  digitalWrite(DUST_ILED, LOW);
  // put your setup code here, to run once:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
  display.display();
  

  
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(DUST_ILED, HIGH);
  delayMicroseconds(280);

  int outVolt = analogRead(DUST_PIN);

  digitalWrite(DUST_ILED, LOW);

  float calcVoltage = outVolt * 5.0 / 1024.0 - NO_DUST_VOLT;
  if (calcVoltage < 0) {
    calcVoltage = 0;
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("Dust: ");
  display.println(calcVoltage);  
  display.display();
  delay(1000);
}

Теперь необходимо добавить датчики температуры и влажности для полноты информации. Например подойдёт DHT22 – это два датчика в одном.

20160130_130724У него 4 ножки, но одна (третья, согласно DataSheet-у) не используется, так что просто нужно подключить 5 вольт, землю и один из цифровых пинов, например 2й. Так же есть библиотека для работы с этим датчиком, так что считывание показаний можно легко добавить в предыдущий скетч:

#include <Adafruit_SSD1306.h>
#include <DHT.h>

#define DHT_TYPE DHT22
#define DHT_PIN 2

#define DUST_ILED 7
#define DUST_PIN 0
#define NO_DUST_VOLT 0.9

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

DHT dht(DHT_PIN,DHT_TYPE);

void setup() {
  pinMode(DUST_ILED, OUTPUT);
  digitalWrite(DUST_ILED, LOW);
  // put your setup code here, to run once:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();
  display.display();
  

  
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(DUST_ILED, HIGH);
  delayMicroseconds(280);

  int outVolt = analogRead(DUST_PIN);

  digitalWrite(DUST_ILED, LOW);

  float calcVoltage = outVolt * 5.0 / 1024.0 - NO_DUST_VOLT;
  if (calcVoltage < 0) {
    calcVoltage = 0;
  }

  float h = dht.readHumidity();
  float t = dht.readTemperature();
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.print("H: ");
  display.print(h);
  display.print(" T: ");
  display.println(t);  
  display.println("");
  display.setTextSize(2);
  display.print("Dust: ");
  display.println(calcVoltage);  
  display.display();
  delay(1000);
}

просто читаем влажность и температуру с помощью библиотечных функций dht.readHumidity() и dht.readTemperature() и выводим их на экран:

20160130_21055820160130_221420

Ещё нужен источник питания, такой, чтобы можно было легко носить “прибор” с собой. Например подойдут пальчиковые (АА) батарейки, которые можно вставить в батарейный отсек и приделать к ним разъём с клеммой для лёгкого способа подключения к Arduino

20160130_130601Сам Arduino засунуть в корпус и приклеить батарейный отсек с помощью 2х стороннего скотча:

20160130_130621 20160130_130631

Что скажите? Полезный девайс?

Управляемая RGB лампочка: Часть 1 – NeoPixel-и

Сейчас куда не глянь везде всё умное. Умные розетки, кофеварки, телевизоры и конечно же лампочки. Одной из первых таких ламп я увидел Philips HUE, и загорелся идеей сделать свою управляемую RGB лампочку. У такой лампочки масса очевидных преимуществ: задание цвета под настроение, автоматическое включение/выключение, светобудильник, плюс неочевидные, как например светомузыка. Сказано сделано – начинаем копать.

Первое, что нужно решить – это каким будет источник света. Сейчас в моде светодиоды – они компактны и потребляют мало электричества, к тому же у меня завалялось 15 NeoPixel-ей от Adafruit на основе ws2812b.

20160117_185334Это 3 светодиода в одном плюс управляющая электроника. Особенность в том, что управляются они по одному проводу (вместе с напругой и землёй – три). Грубо говоря по проводу с данными вы передаёте сигнал, представляющий массив значений цветов. А дальше первый NeoPixel “откусывает” от этого массива первый элемент, выставляет его себе и передаёт остаток дальше и так далее по цепочке.

20160117_193644Второе – это какую беспроводную технологию использовать. WiFi мне кажется более универсальной и лучше масштабируемой в отличии от Bluetooth – это позволяет добавлять огромное количество таких ламп и заодно даёт возможность управления ими с помощью любого устройства, находящегося в этой же сети (да и за её пределами). Да и радиус действия у них гораздо больше. К тому же у меня валялось 3 модуля на основе esp8266.

20160117_190110Третье – это на чем реализовать управляющую логику. В наличии был Arduino Uno к нему и решил подключить NeoPixel-и, следуя инструкции на сайте Adafruit.

20160117_195326Однако Arduino Uno слишком большой, чтобы он мог поместиться в корпус стандартной лампочки. В интернете мне попалось видео, как уменьшить свои Arduino Uno проекты используя ATTiny45/85. К тому же на instructables есть guide, как заставить ATTiny работать с NeoPixel-ями. Не долго думая я купил ATTiny45 и перенёс пример с полноценной Arduino Uno на эту мелочь.

20160117_201131 1 20160117_204324

С другой стороны esp8266 – это очень мощная штука при малых размерах. Помимо WiFi этот SoC имеет ещё и GPIO. Я взял ESP-01 у которого есть два GPIO. К тому же Arduino IDE поддерживает это семейство начиная с версии 1.6.4 и у Adafruit даже есть гайд по настройке. Единственная проблема – это то, как заливать туда свой софт. Я купил USB-Serail converter который имеет выходной сигнал на 3.3 вольта, так как 5 вольт убьет esp8266 и сделал две кнопки на макетной плате, так как для заливки нового софта требуется перевести один из пинов в Low и затем RESET-нуть esp8266, более подробно о том как заливать свои программы можно прочитать тут:

20160117_193458 20160117_193920 1

Пусть вас не смущает наличие Arduino Uno на картинке справа – его я использовал исключительно, чтобы получить 3.3 вольта и запитать esp8266, а также чтобы получить 5 вольт для NeoPixel-ей.

20160118_205216Стандартный пример от Adafruit заработал и я бросился писать простенькую программку, которая бы коннектилась к моей домашней WiFi сети, поднимала сокет и ждала число, которое бы определяло цвет светодиодов. В интернете я наткнулся на хороший пример того, как общаться с esp8266 и на его основе с небольшими изменениями, используя Adafruit API для управление светодиодами, я добился чего хотел.

Для проверки накропал простенький скрипт на Perl-е, который бы и отправлял цвет для лампы:

use strict;

use IO::Socket::INET;

my $sock = new IO::Socket::INET(PeerAddr => '192.168.1.35',
                                PeerPort => '6969',
                    Proto => 'tcp') || die "Can't bind $@\n";

$sock->send(shift."\n");

И сама прошивка

#include <Adafruit_NeoPixel.h>
#include <ESP8266WiFi.h>

#define NEOPIXEL_DATA_PIN 0
#define PIXELS_NUM 7

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS_NUM,
                                            NEOPIXEL_DATA_PIN,
                                           NEO_RGB + NEO_KHZ800);

const char* ssid = "mynetworkssid";
const char* password = "mynetpass";
WiFiServer server(6969);                                           

void setup() {
  Serial.begin(115200);
  strip.begin();
  strip.show();
 
  WiFi.begin(ssid, password);

  server.begin();
  while(WiFi.status() != WL_CONNECTED)
  {
    delay(500);
  }
}

void loop() {

  WiFiClient client = server.available();  
  if (client)
  {
    Serial.println("client connected.");
    while(client.connected())
    {
      if (client.available())
      {      
        String command;
        char c = client.read();
        while(c != '\n')
        {
          command += c;
          c = client.read();
        }

        if (command != 0)
        {
          char** tmp;
          char colorcommand[10];
          command.toCharArray(colorcommand, sizeof(colorcommand));
          
          uint32_t iColor = strtoul(colorcommand, tmp, 10);
           SetStripColors(iColor);
        }
      }
    }
    Serial.println("Client disconnected.");
    client.stop();
  }
}

void SetStripColors(uint32_t color)
{
  for (uint16_t i=0; i< PIXELS_NUM; i++)
  {
    strip.setPixelColor(i, color);
  }
  strip.show();
  delay(100);
}

Теперь дело за спецэфектами!

Ambilight своими руками: Часть 5 – Tuning

После того, как всё заработало в предыдущем посте, осталось сделать финальную настройку, чтобы светодиоды более точно передавали изображение на экране. Для начала посмотрим на картинку, на основе которой мы выставляем цвета, с помощью простого скрипта, который делает чтение буфера из EasyCap и сохраняет его в файл. Как обычно мой любимый vim screenshot.py:

import numpy as np
import cv2
import sys

cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print "Failed to initialize"
    exit(0)

ret, frame = cap.read()

if not ret:
    print "Failed to read frame"
    exit(0)

cv2.imwrite(sys.argv[1],frame)

Единственная новшество тут – это запись в файл с помощью функции cv2.imwrite, которая сама определяет формат сохранения в зависимости от расширения файла. То есть, если туда передать .jpg – то будет JPEG, а если .png то PNG.

sys.argv[1] – это чтение параметра из командной строки. Нулевой элемент – это имя скрипта. Таким образом, объединив эти две вещи, скрипт будет сохранять в формат файла в зависимости от параметра который мы передадим. Проверьте переключатель формата на устройстве конвертации из HDMI в аналоговый видеосигнал. У меня он установлен в NTSC поэтому делаю первый снимок с соответствующим именем:

sudo python screenshot.py /run/ntsc.jpg

ntsc_defНа снимке экрана видно, что слева и справа здоровенная чёрная рамка, поменьше снизу и маленькая сверху. Именно из-за этой рамки цвет светодиодов так сильно отличается. В дополнение к этому картинка обрезана слева и справа. Посмотрим, что будет если попробовать PAL. Переведите переключатель на конвертере в другое положение и повторите процедуру, чтобы получить второй снимок и решить, какой из режимов предпочтительнее.

sudo python screenshot.py /run/pal.jpeg

pal_defТут также здоровенная рамка, да ещё и картинка сильнее обрезана справа и снизу. Так что мой выбор очевиден – NTSC. Если у вас такая же ситуация, то переведите переключатель обратно.

Теперь нам нужно модифицировать наш скрипт управления лентой, чтобы он не учитывал чёрные пиксели рамки, а работал только с той частью, которая показывает картинку с устройства. Для этого нужно померить количество пикселей с каждой стороны в любом графическом редакторе, например mspaint-е. После этого заведём четыре переменные, которые и будут определять начало значимых для нас пикселей на картинке. Для этого откроем наш скрипт и добавим новые переменные. vim run.py:

import numpy as np
import time
import cv2
import sys
from neopixel import *

LED_PIN = 18
LED_HEIGHT = 15
LED_WIDTH = 25
LED_FREQ = 800000
LED_DMA = 5
LED_BRIGHTNESS = 255
LED_INVERT = False

xborderl = 23
xborderr = 21
ybordert = 2
yborderb = 9

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

strip = Adafruit_NeoPixel(LED_HEIGHT*2+LED_WIDTH*2,LED_PIN,LED_FREQ,\
                                LED_DMA,LED_INVERT,LED_BRIGHTNESS)
strip.begin()

cap = cv2.VideoCapture(0)

if (not cap.isOpened()):
    print "unable to initialize!"
    exit(0)

ret,frame1 = cap.read()
ret,frame2 = cap.read()

while(cap.isOpened()):
    ret,frame1 = cap.read(frame1)
    ret,frame2 = cap.read(frame2)
    img = frame2[ybordert:frame2.shape[0]-yborderb,\
                        xborderl:frame2.shape[1]-xborderr]

Последняя строчка определяет как раз границы, по-которым мы будем обрезать. Теперь нам нужно уже resize-ить не всю картинку, а только значимую область:

    rsz1 = cv2.resize(img, (LED_HEIGHT, LED_WIDTH))

а всё остальное остаётся по старому:

    for i in range(LED_WIDTH):
        BGR = rsz1[LED_WIDTH-i-1,0]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[0,i]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_WIDTH, color)
    for i in range(LED_WIDTH):
        BGR = rsz1[i,LED_HEIGHT-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[LED_WIDTH-1,LED_HEIGHT-i-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH*2, color)
    strip.show()

Пришло время попробовать. В youtube полно видео для тестирования ambilight. Мне понравилось вот это (не забудьте включить полный экран):

Вот как это выглядит у меня:

Так же нашёл прикольное видео с Laser show – решил попробовать свой билд на нём:

В общем видно, что ещё есть над чем работать. Задержка заметна, видно пропуски, да и ленту надо чуть переклеить. Думаю, что на новогодних каникулах разберусь

Ambilight своими руками: Часть 4 – всё вместе

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

Для этого можно модифицировать скрипт, который мы написали для тестирования.

cp test.py run.py
vim run.py

теперь помимо тех библиотек, которые мы уже подключили нам понадобится ещё time и neopixel.

import numpy as np
import time
import cv2
import sys
from neopixel import *

Далее нужно инициализировать библиотеку для работы со светодиодами, предварительно объявив несколько переменных, которые будут описывать нашу ленту и способ доступа к ней:

LED_PIN = 18
LED_HEIGHT = 15
LED_WIDTH = 25
LED_FREQ = 800000 
LED_DMA = 5
LED_BRIGHTNESS = 255
LED_INVERT = False

strip = Adafruit_NeoPixel(LED_HEIGHT*2+LED_WIDTH*2,LED_PIN,LED_FREQ,\
                                LED_DMA,LED_INVERT,LED_BRIGHTNESS)
strip.begin()

теперь инициализируем видеозахват используя OpenCV, этот код уже был написан в прошлый раз:

cap = cv2.VideoCapture(0)

if (not cap.isOpened()):
    print "unable to initialize!"
    exit(0)

создаем переменные с которыми будем работать и читаем кадр из устройства

ret,frame1 = cap.read()

теперь нам нужен цикл который будет все время вычитывать кадры и управлять лентой:

while(cap.isOpened()):
    ret,frame1 = cap.read(frame1)

EasyCap выдает картинку 720×480 пикселей на основе, которой нам надо выставлять цвет для светодиодов. Самый простой способ, который я вижу это просто сжать эту самую картинку до размера количества светодиодов, то есть из 720×480 должно получиться 25×15. К счастью в OpenCV это делается одной строчкой:

 rsz1 = cv2.resize(frame1, (LED_HEIGHT, LED_WIDTH))

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

    for i in range(LED_WIDTH):
        BGR = rsz1[LED_WIDTH-i-1,0]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i, color)

Тут следует сказать о двух особенностях.

  1. У меня лента начинается в нижнем левом углу телевизора и идёт по часовой стрелки если смотреть на экран. Соответственно мне нужны пиксели с левой стороны картинки то есть X=0, а Y=высота картинки – индекс, так как начало картинки находится в верхнем левом углу
  2. В Картинках, представленных как массивы numpy, сначала идут Y координаты, а потом X

Дальше нужно повторить всё тоже самое только для верха, правого бока и низа:

    for i in range(LED_HEIGHT):
        BGR = rsz1[0,i]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_WIDTH, color)
    for i in range(LED_WIDTH):
        BGR = rsz1[i,LED_HEIGHT-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT+LED_WIDTH, color)
    for i in range(LED_HEIGHT):
        BGR = rsz1[LED_WIDTH-1,LED_HEIGHT-i-1]
        color = Color(int(BGR[2]),int(BGR[1]),int(BGR[0]))
        strip.setPixelColor(i+LED_HEIGHT*2+LED_WIDTH, color)

Единственное, на что нужно обратить внимание – это то, что каждый раз вызывая setPixelColor вы должны сдвигать index светодиода для которого вы выставляете цвет. В первом цикле у нас 15 светодиодов, соответственно во втором цикле мы уже выставляем цвет для 15+i-го светодиода, а в 3ем цикле для 15+25+i-го и т.д..

Теперь, когда цвет для всех светодиодов задан осталось только отправить эти данные на ленту:

    strip.show()

Вот и всё – скрипт готов, пришло время запускать.

sudo python run.py

светодиоды зажгутся и если вы запустите кино или просто свернёте все окна то увидите, что некоторые (или все) светодиоды начнут неприятно моргать:

Первое, что можно сделать, чтобы избавиться от этого – это разогнать Raspberry Pi 2 используя стандартную утилиту raspi-config.

sudo raspi-config

выберите overclocking

raspiconfigзатем выберите preset pi2 – 1000MHz

raspiconfig-pi2presetПерезагрузитесь и попробуйте ещё раз. Должно стать лучше, но не идеально. Причина тому, видимо в том, что мы читаем только один кадр, в то время, как EasyCap предосталвяет buffer из двух. Чтобы проверить это можно воспользоваться всё тем же v4l2-ctl и опцией –all:

v4l2allв голову приходит только один фикс – читать два раза. Для этого сделаем небольшое изменение для нашего скрипта vim run.py на строчке 30:

ret,frame1 = cap.read()
ret,frame2 = cap.read()

и на строчках 34-37

    ret,frame1 = cap.read(frame1)
    ret,frame2 = cap.read(frame2)

    rsz1 = cv2.resize(frame2, (LED_WIDTH, LED_HEIGHT))

и попробуем ещё раз

sudo python ryn.py

Теперь почти хорошо, осталось прогнать тест и сделать последние доработки, но это уже в следующий раз.

Ambilight своими руками: Часть 3 – софтверная

Если вы прочитали мой предыдущий пост, все подключили и у вас тоже железо, что и у меня – не спешите радоваться, всё только начинается. Теперь нужно всё настроить.

Обновите ваш Raspbian

sudo apt-get update
sudo apt-get upgrade

Для начала убедитесь, что ваш EasyCap девайс работает. В этом вам помогут команды lsusb и dmesg | tail:

lsusb
dmesg | tail

вывод должен выглядеть примерно так:

lsusbdmesgТут видно, что появился новый USB девайс 1b71:3002, что это USBTV007 от fushicai. Дальше нам понадобятся утилиты Video4linux. С помощью v4l2-ctl –list-devices найдите свой EasyCap. В моем случае это просто /dev/video0.

v4l2ctllistОтлично, запомните (или запишите) результат v4l2-ctl – тот что выглядит как /dev/video0. Теперь используя, например, mplayer можно посмотреть картинку предварительно запустив Иксы:

startx
sudo apt-get -y install mplayer
mplayer tv:// -tv device=/dev/video0 -hardframedrop

вместо /dev/video0 может быть что-то другое – смотри вывод v4l2-ctl –list-devices.

Тут вас ждёт первая засада: чёрный или зелёный экран.

20151119_205807Проверьте кабели и побалуйтесь с переключателем PAL-NTSC:

20151103_220807Или же вы можете указать PAL или NTSC в командной строчке для mplayer

mplayer tv:// -tv device=/dev/video0:norm=NTSC -hardframedrop

20151119_210935Python+OpenCV

Cледующий шаг – это поставить и настроить Python+OpenCV. Python – это скриптовый язык программирования, подробнее о нём можете прочитать например в Wikipedia. Скорее всего Python уже установлен на Raspberry Pi, тем не менее имеет смысл проверить версию, так как я тестировал только с версией 2.7.3.

Далее вам нужно поставить две python библиотеки. Это numpy и OpenCV. Они так же доступны через apt-get.

sudo apt-get install python-numpy
sudo apt-get install python-opencv

Numpy – это библиотека для научных вычислений, а OpenCV – это библиотека машинного зрения. Для того, чтобы проверить, что всё установлено и работает как надо, можно написать простенький скрипт на Python-е. Я люблю vim, но вы можете использовать любой другой текстовый редактор, например nano test.py.

Импортируем библиотеки numpy, OpenCV и sys для последующего расширения и работы с системными функциями.

import numpy as np
import cv2
import sys

Эта часть инициализирует видеозахват для устройства /dev/video0. Если ваш EasyCap имеет другой номер – используйте его вместо 0. И проверяем, что инициализация прошла успешно.

cap = cv2.VideoCapture(0)

if (not cap.isOpened()):
  exit(0)

Читаем кадр из устройства и проверяем, что чтение прошло успешно.

ret,frame = cap.read()

if (not ret):
  exit(0)

И сохраняем в файл.

cv2.imwrite('/run/test.jpeg',frame)

Запускаем наш Python скрипт, чтобы посмотреть как оно работает:

sudo python test.py

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

sudo apt-get install fbi
sudo fbi /run/test.jpeg

20151119_211614

_rpi_ws281x

Следующий шаг – это начать работу со светодиодной лентой. На просторах интернета есть разные реализации. Наиболее часто попадающаяся это rpi_ws281x, в частности инструкия на сайте Adafruit ссылается на неё, однако мне так и не удалось её поставить и заставить работать отдельно на Raspberry Pi 2, в то время как на Raspberry Pi B+ всё работает. Спасибо ребятам из pimoroni, за их волшебный unicorn_hat, который решил все проблемы  и простому shell скрипту, который устанавливает все необходимое. Просто качаете и запускаете.

Сначала ставим саму библиотеку, как описано здесь:

sudo apt-get install build-essential python-dev git scons swig
git clone https://github.com/jgarff/rpi_ws281x.git
cd rpi_ws281x/
scons

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

wget get.pimoroni.com/unicornhat
chmod +x unicornhat
./unicornhat
sudo pip install unicornhat

Чтобы проверить работоспособность можно запустить один из exmaple-ов.

20151119_222410А в следующей серии вы узнаете как скрестить OpenCV и Adafruit_NeoPixel чтобы управлять светодиодами в зависимости от изображения на экране

 

Ambilight своими руками. Часть 2 – Железо

Итак, вы прочитали первую часть и понимаете какую боль вам придётся испытать и всё равно хотите попробовать? Очень хорошо, я попробую это облегчить.

Хочу сразу подчеркнуть, что если вы хотите и фильмы смотреть и управлять лентой с Raspberry Pi, то вы пришли не по адресу. Мой вариант годиться только для одного – фоновая подсветка для внешнего HDMI сигнала.

Итак вам понадобятся:

  • Raspberry Pi 2
  • Светодиодная лента на базе ws2812b
  • HDMI Splitter
  • HDMI2AV converter
  • EasyCap USB AV Grabber
  • блок питания для Raspberry Pi
  • блок питания для HDMI Splitter-а (Должен быть в комплекте)
  • 3x HDMI кабель
  • RCA-RCA кабель
  • Источник HDMI сигнала, например Nvidia Shield
  • паяльник, припой,

Блок питания для Raspberry Pi

Я не могу передать словами, насколько важен хороший источник питания для Raspberry Pi. Если вы видите вот такой значок в верхнем правом углу, значит вашему Raspberry Pi не хватает питалова.

20151103_221700

я взял SmartBuy с двумя USB на 2 и 1 ампера, проблемы с питанием пропали.

20151103_220456

Ещё хорошо себя показала внешняя батарея от Xiaomi 

20151103_220509

Video Grabber

Easy Cap USB Video Grabber – эта та самая вещь, которая доставит вам много боли, так как их существует великое множество (говорят всего 4), и невозможно определить какой именно девайс вы купите до покупки, так как они выглядят одинаково.

20151103_220604

Мне не повезло – я купил 4 штуки и все они оказались на базе чипа UTV007. К счастью, Raspbian от 2015-05-05 (последний доступный Debian Wheezy на официальном сайте Raspberrypi.org на момент написания) уже включает в себя поддержку драйвера для этого устройства.

20151103_221052

Светодиодная лента

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

На то, чтобы все припаять правильно и ничего не развалилось, пока я нёс это до телевизора ушло вечеров 5. В результате я добился чего хотел – надёжного соединения и без соплей. Правда из-за того, что я сначала отодрал защитный слой со светодиодной ленты, а потом паял, то на него налипла всякая хрень, которая мешает надёжному креплению ленты к телевизору. Появилось даже идея воспользоваться могучим клеем момент, но пока это выглядит вот так:

20151103_220826

Остальные компоненты

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

20151103_220749

Поскольку EasyCap работает с аналоговым видеосигналом, а на выходе у современных устройств как правило HDMI, то нужен конвертор HDMI2AV.

20151103_220756Его и EasyCap соединяем RCA-RCA кабелем соблюдая маркировку – желтый CVBS/Video в Желтый и т.п.

Подключение светодиодной ленты

Это малость запутанный момент. Можете нарыть разные варианты в интернете. Тот же Adafruit дает неплохое описание. Важно помнить две вещи:

  • У светодиодной ленты и Raspberry Pi должна быть общая земля
  • Для питания светодиодной ленты нужен мощный источник питания, около 60*количество светодиодов мА

У меня 80 светодиодов, то есть мне нужно 4.8 Ампера для светодиодной ленты. Я купил блок питания от какого-то ноутбука на 6 Ампер.

20151116_203458И о чудо – штекер от блока питания идеально подошёл к одному из разъёмов с клеммой, что у меня завалялся.

20151116_203544Соответственно берем минус и подключаем к земле ленты (чёрный провод) и к земле на Raspberry Pi 2 (например к pin 6), а плюс с разъёма подключаем на 5 вольт ленты (красный провод). Последнее, что остаётся сделать – это подключить data (зелёный провод) светодиодной ленты к pin 12.

20151116_212526Распиновку Raspberry Pi 2 можно посмотреть здесь.

pinout6-1220151116_211654

Результат

В результате получилось довольно миленько:

20151103_221509Что скажите? То как выглядит подсветка – норм?

Ambilight своими руками. Часть 1 – Боль

Полгода назад я открыл для себя мир боли и унижения волшебный мир микроконтроллеров и одноплатных компьютеров. Теперь у меня аж целых 6(7) Raspberry Pi, 3 Arduino и 2 девайса от ODROID. И, хотя большинство из них пойдёт в мой автомобильный проект или на подарки друзьям, один из них решено было пустить в какой-нить домашний проект.

20151103_220658 20151107_110215

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

Естественно, как и всё, что связано со “сделай сам” , оно не могло просто взять и заработать.

Сначала я купил “неправильную” RGB ленту. Которая работает шикарно, но на ней нельзя задать цвет для отдельного светодиода, а только для всей ленты целиком. Правда эта лента пригодилась в другом проекте, о котором обязательно напишу позже.
RGB лента 20151107_115237  20151107_115336

Осознав ошибку я начал искать “правильную” ленту с управлением каждым светодиодом отдельно (Так называемый бегущий огонь). Таких оказалось много разных и я взял первую попавшуюся – на базе ws2812, не подозревая, что могут быть какие-то проблемы.

ws2812

ws2812

Как оказалось, это тоже “неправильная” лента и проекты, которые существуют в интернете либо не поддерживают её, либо требуется дополнительно Arduino… или я просто не умею пользоваться google-ом.

  • Всемирно любимый Hyperion, например, при настройке не имеет поддержки ws2812 лент, но может управлять ей через Arduino (нафига?). С наскока разобраться не получилось, да и сама идея использовать связку RaspberryPi с Arduino мне как-то не нравится
  • ambi-tv вообще поддерживает только один тип лент: LPD8806, хотя имеет возможность расширения
  • “мертвый” boblight тоже может работать с такой лентой только через Arduino.

В дополнение ко всему этому, рабочие примеры в интернете были для Raspberry Pi A или B, а для Raspberry Pi 2 часто можно было встретить оговорку, что “не поддерживается” или “не работает”, что вселяло дополнительный оптимизм.

Ну да ладно, подумал я, говно вопрос – запилю всё сам. Тогда я ещё не знал, что помимо Software проблем меня ждут ещё и Hardware.

Не подходит для Raspberry Pi - недостаточно питалова

Зарядка от телефона Samsung 5v/2a

Как оказалось большинство (или все?) производителей телефонов кладут в комплекте с их устройством говённую зарядку, напряжение которой не хватает для Raspberry Pi, к тому же сам Raspberry Pi имеет ограничение на выходной ток на USB портах. О том что это может вызывать какие-то проблемы с подключенными устройствами я тоже узнал гораздо позже.

Кроме того это кажется очевидным, когда читаешь, но не очевидным, когда делаешь. Светодиодную ленту нужно СНАЧАЛА примерить к телевизору, ЗАТЕМ нарезать. ПОСЛЕ припаять, и ТОЛЬКО ПОТОМ приклеивать к телевизору.

В дополнение ко всему этому мой вариант расположения ленты за телевизором, отличается от классического – светить в стену. Вместо этого мои светодиоды светят в бок. Когда я это делал – мне казалось, что, поскольку край рамку у моего телевизора прозрачный, то возможность видеть подсвеченный светодиод придаст некую изюминку. На деле это отвлекает от картинки и не дает нужного эффекта. Короче НЕ НАДО ТАК ДЕЛАТЬ.
20151103_221509

О том можно ли сделать из Raspberry Pi фоновую подсветку для телевизора и как всё это работает – читайте в следующих частях.