Tag Archives: ws2812

Управляемая 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 – решил попробовать свой билд на нём:

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