В этом уроке мы узнаем как использовать Arduino IoT Cloud и Amazon Alexa для переключения каналов, регулировки громкости и включения или выключения любого телевизора.
Комплектующие
Урок для общего понимания как устроены определенные вещи по взаимодействию с телевизором и другими полезными устройствами для умного дома.
Детали, которые мы будем использовать в этом уроке перечислены ниже. Начнем с компонентов оборудования:
- Arduino Nano 33 IoT × 1
- Резистор 330 Ом × 1
- ИК-ресивер × 1
- ИК-передатчик × 1
- Перемычки
- Макет (универсальный) × 1
Программное обеспечение:
- Arduino IDE или Arduino Web Editor
- Arduino IoT Cloud
- Навык Arduino для Amazon Alexa
Облако Arduino IoT
Arduino IoT Cloud - это платформа, которая позволяет любому с легкостью создавать IoT-связанный объект.
IoT расшифровывается с английского как Internet of Things, что мы переводим как "интернет вещей".
Чтобы контролировать наш телевизор с помощью Alexa, мы также будем использовать официальный навык Arduino для Amazon Alexa.
Если вы новичок по работе с облаком Arduino IoT Cloud, мы советуем сначала взглянуть на некоторые проекты на нашем сайте, которые дадут вам представление о том, как и когда нон применяется.
Схема соединения
Соединяем все наши комплектующие согласно схеме ниже:
Как управлять телевизором
Самый простой способ управлять любым видом телевизора - это действовать так, как если бы мы были его собственным удаленным инфракрасным контроллером, т.е. пультом управления.
Для этого нам нужно будет прослушать сигналы, которые отправляет пульт, захватывать данные и имитировать их с помощью нашей платы Arduino.
Как только схема будет собрана (см. выше), мы загрузим вот этот скетч на нашу плату:
// Include the IRremote library header #include <IRremote.h> // Tell IRremote which Arduino pin is connected to the IR Receiver (TSOP4838) int recvPin = 11; IRrecv irrecv(recvPin); // Configure the Arduino void setup ( ) { Serial.begin(9600); // Status message will be sent to PC at 9600 baud irrecv.enableIRIn(); // Start the receiver } // Display IR code void ircode (decode_results *results) { // Panasonic has an Address if (results->decode_type == PANASONIC) { Serial.print(results->address, HEX); Serial.print(":"); } // Print Code Serial.print(results->value, HEX); } // Display encoding type void encoding (decode_results *results) { switch (results->decode_type) { default: case UNKNOWN: Serial.print("UNKNOWN"); break ; case NEC: Serial.print("NEC"); break ; case SONY: Serial.print("SONY"); break ; case RC5: Serial.print("RC5"); break ; case RC6: Serial.print("RC6"); break ; case DISH: Serial.print("DISH"); break ; case SHARP: Serial.print("SHARP"); break ; case JVC: Serial.print("JVC"); break ; case SANYO: Serial.print("SANYO"); break ; case MITSUBISHI: Serial.print("MITSUBISHI"); break ; case SAMSUNG: Serial.print("SAMSUNG"); break ; case LG: Serial.print("LG"); break ; case WHYNTER: Serial.print("WHYNTER"); break ; case AIWA_RC_T501: Serial.print("AIWA_RC_T501"); break ; case PANASONIC: Serial.print("PANASONIC"); break ; case DENON: Serial.print("Denon"); break ; } } // Dump out the decode_results structure. void dumpInfo (decode_results *results) { // Check if the buffer overflowed if (results->overflow) { Serial.println("IR code too long. Edit IRremoteInt.h and increase RAWBUF"); return; } // Show Encoding standard Serial.print("Encoding : "); encoding(results); Serial.println(""); // Show Code & length Serial.print("Code : "); ircode(results); Serial.print(" ("); Serial.print(results->bits, DEC); Serial.println(" bits)"); } // Dump out the decode_results structure. void dumpRaw (decode_results *results) { // Print Raw data Serial.print("Timing["); Serial.print(results->rawlen-1, DEC); Serial.println("]: "); for (int i = 1; i < results->rawlen; i++) { unsigned long x = results->rawbuf[i] * USECPERTICK; if (!(i & 1)) { // even Serial.print("-"); if (x < 1000) Serial.print(" ") ; if (x < 100) Serial.print(" ") ; Serial.print(x, DEC); } else { // odd Serial.print(" "); Serial.print("+"); if (x < 1000) Serial.print(" ") ; if (x < 100) Serial.print(" ") ; Serial.print(x, DEC); if (i < results->rawlen-1) Serial.print(", "); //',' not needed for last one } if (!(i % 8)) Serial.println(""); } Serial.println(""); // Newline } // Dump out the decode_results structure. void dumpCode (decode_results *results) { // Start declaration Serial.print("unsigned int "); // variable type Serial.print("rawData["); // array name Serial.print(results->rawlen - 1, DEC); // array size Serial.print("] = {"); // Start declaration // Dump data for (int i = 1; i < results->rawlen; i++) { Serial.print(results->rawbuf[i] * USECPERTICK, DEC); if ( i < results->rawlen-1 ) Serial.print(","); // ',' not needed on last one if (!(i & 1)) Serial.print(" "); } // End declaration Serial.print("};"); // // Comment Serial.print(" // "); encoding(results); Serial.print(" "); ircode(results); // Newline Serial.println(""); // Now dump "known" codes if (results->decode_type != UNKNOWN) { // Some protocols have an address if (results->decode_type == PANASONIC) { Serial.print("unsigned int addr = 0x"); Serial.print(results->address, HEX); Serial.println(";"); } // All protocols have data Serial.print("unsigned int data = 0x"); Serial.print(results->value, HEX); Serial.println(";"); } } // The repeating section of the code void loop ( ) { decode_results results; // Somewhere to store the results if (irrecv.decode(&results)) { // Grab an IR code dumpInfo(&results); // Output the results dumpRaw(&results); // Output the results in RAW format dumpCode(&results); // Output the results as source code Serial.println(""); // Blank line between entries irrecv.resume(); // Prepare for the next value } }
Скетч будет транслировать пакеты ИК-сигналов, генерируемых удаленным нажатиями кнопок, в массив целых чисел. Давайте нацелим пульт на построенный нами ИК-приемник Arduino и нажмем следующие кнопки:
- Включение/выключение
- Каналы
- Увеличить громкость
- Уменьшить громкость
- Отключение звука
- Канал плюс
- Канал минус
Мы увидим значения, поступающие через последовательный монитор, представленные как rawData. Давайте пока запишем их в текстовый файл и назначим каждому списку свое имя массива (chan1, chan2 и т.д.). Значения ниже приведены только для справки и генерируются пультом телевизора Samsung:
CHANNEL 1 unsigned int chan1[67] = {4450,4500, 550,1700, 500,1700, 550,1700, 550,550, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,1700, 550,1700, 500,600, 500,600, 550,550, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,600, 500,600, 550,550, 550,600, 500,600, 550,1700, 500,1700, 550,600, 500,1700, 550,1700, 550,1700, 500,1700, 550,1700, 500}; CHANNEL 2 unsigned int chan2[67] = {4500,4500, 550,1700, 500,1700, 550,1700, 550,550, 550,550, 550,600, 500,600, 550,600, 500,1700, 550,1700, 500,1700, 550,600, 500,600, 550,550, 550,600, 500,600, 550,1700, 500,600, 550,1700, 500,600, 550,550, 550,600, 500,600, 550,550, 550,650, 450,1700, 550,600, 500,1700, 550,1700, 500,1700, 550,1700, 550,1700, 500}; CHANNEL 3 unsigned int chan3[67] = {4500,4500, 500,1700, 550,1700, 550,1700, 500,600, 550,550, 550,600, 500,600, 550,550, 550,1700, 500,1700, 550,1700, 550,550, 550,600, 500,600, 550,550, 550,600, 500,600, 550,1700, 500,1700, 550,600, 550,550, 550,550, 550,600, 550,550, 550,1700, 500,600, 550,550, 550,1700, 550,1650, 550,1700, 550,1700, 500,1700, 600}; CHANNEL 4 unsigned int chan4[67] = {4450,4450, 550,1700, 550,1700, 500,1700, 550,600, 500,600, 550,550, 600,550, 500,600, 550,1700, 500,1700, 550,1700, 550,550, 550,600, 500,600, 550,550, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,1700, 550,1700, 500,600, 550,1700, 500,1700, 550,1700, 550,1700, 500}; CHANNEL 5 unsigned int chan5[67] = {4500,4500, 500,1700, 550,1700, 550,1700, 550,550, 550,550, 550,550, 600,550, 550,550, 550,1700, 550,1650, 550,1700, 550,550, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,600, 500,600, 550,1700, 500,600, 550,550, 550,600, 550,550, 550,550, 550,1700, 550,1700, 500,600, 550,1700, 500,1700, 550,1700, 550,1700, 500}; CHANNEL 6 unsigned int chan6[67] = {4500,4500, 550,1650, 550,1700, 550,1700, 500,600, 550,550, 550,600, 500,600, 500,600, 550,1700, 500,1700, 550,1700, 550,550, 600,550, 500,600, 550,550, 600,550, 550,550, 550,1700, 500,600, 550,1700, 500,600, 550,550, 550,600, 500,600, 550,1700, 500,600, 550,1700, 500,600, 550,1650, 600,1650, 550,1700, 550,1650, 600}; CHANNEL 7 unsigned int chan7[67] = {4500,4500, 550,1700, 500,1700, 550,1750, 500,550, 550,600, 500,650, 500,550, 550,550, 550,1750, 500,1700, 500,1700, 550,650, 450,650, 500,550, 550,600, 500,650, 500,550, 550,600, 500,1700, 550,1750, 500,600, 500,550, 550,600, 500,650, 500,1750, 450,1700, 550,600, 500,650, 500,1700, 500,1700, 550,1750, 500,1700, 500}; CHANNEL 8 unsigned int chan8[67] = {4450,4550, 500,1700, 550,1700, 550,1650, 550,600, 500,600, 550,550, 550,600, 500,600, 550,1700, 500,1700, 550,1700, 550,550, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,600, 500,1700, 550,1700, 500,600, 550,550, 550,650, 450,600, 550,550, 550,1700, 550,550, 550,600, 500,1700, 550,1700, 550,1700, 500,1700, 550}; CHANNEL 9 unsigned int chan9[67] = {4450,4500, 550,1700, 550,1700, 500,1700, 550,600, 500,600, 550,550, 550,600, 500,600, 550,1700, 500,1700, 550,1700, 500,600, 550,550, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,1700, 550,1700, 500,600, 550,550, 550,550, 550,600, 500,1700, 550,600, 500,600, 550,550, 550,1700, 550,1700, 500,1700, 550,1700, 550}; VOLUME UP unsigned int volUp[67] = {4500,4500, 550,1700, 500,1750, 500,1700, 550,600, 500,600, 500,600, 550,550, 550,600, 500,1700, 550,1700, 550,1700, 500,650, 450,600, 550,600, 500,650, 450,650, 500,1700, 500,1750, 500,1750, 500,550, 550,600, 500,650, 500,550, 550,600, 500,650, 500,600, 500,600, 500,1700, 550,1750, 450,1750, 500,1700, 550,1700, 500}; VOLUME DOWN unsigned int volDown[67] = {4450,4550, 500,1700, 550,1700, 550,1650, 550,600, 550,550, 550,600, 500,600, 500,600, 550,1700, 500,1700, 550,1700, 550,550, 550,600, 500,600, 550,550, 550,600, 500,1700, 550,1700, 550,600, 500,1700, 550,600, 500,600, 500,600, 550,550, 550,600, 500,650, 500,1700, 500,650, 500,1700, 500,1750, 500,1700, 550,1700, 500}; CHANNEL UP unsigned int chanUp[67] = {4500,4450, 550,1700, 550,1650, 550,1700, 550,550, 550,600, 550,550, 550,550, 600,550, 550,1650, 550,1700, 550,1650, 600,550, 550,550, 550,600, 500,600, 550,550, 550,550, 550,1700, 550,550, 600,550, 550,1650, 550,600, 550,550, 550,550, 550,1700, 550,550, 550,1700, 550,1700, 550,550, 550,1650, 600,1650, 550,1700, 550}; CHANNEL DOWN unsigned int chanDown[67] = {4500,4450, 600,1650, 550,1700, 550,1650, 550,600, 550,550, 550,550, 550,600, 500,600, 550,1700, 500,1700, 550,1700, 550,550, 550,600, 550,550, 550,550, 550,550, 600,550, 550,550, 550,600, 500,600, 550,1650, 600,550, 550,550, 550,550, 600,1650, 550,1700, 500,1700, 600,1650, 550,550, 600,1650, 550,1700, 500,1700, 550};
Arduino IoT Cloud
На главной странице Arduino IoT Cloud мы создадим новую вещь (Thing) и присвоим ей имя. Давайте назовем её TVRemoteController. Затем мы выберем плату, которую будем использовать. Для этого урока мы используем Arduino Nano 33 IoT, но если у вас есть другая совместимая плата, она тоже подойдет, просто имейте в виду, что распиновка и поведение ИК-библиотеки могут измениться.
После этого мы добавим одно свойство или объект (Property ) к нашей вещи (Thing), который будет представлять наш телевизор. В категории «Умный дом» (Smart Home) выберите ТВ (TV) в качестве типа объекта (свойства), установите для него «Чтение и запись» (Read & Write), а в разделе «Обновление» (Update) выберите «При изменении значения» (When the value changes):
Arduino Web Editor
Пришло время нажать на кнопку «Редактировать скетч» (EDIT SKETCH), которая откроет нам веб-редактор, где мы можем добавить некоторый код в скетч, автоматически сгенерированный IoT Cloud.
Первое, что мы должны включить, это библиотека IR Remote, автором которой является Кен Ширифф.
#include <IRremote.h>
Затем нам нужно настроить один двумерный массив для наших каналов и 6 массивов для необходимых нам команд. Если вы помните, в первой части мы собрали некоторые данные IR, которые теперь будем использовать для заполнения наших массивов.
const unsigned int chan[9][67] = { {chan1}, {chan2}, {chan3}, {chan4}, {chan5}, {chan6}, {chan7}, {chan8}, {chan9} }; const unsigned int volUp[67] = {...}; const unsigned int volDown[67] = {...}; const unsigned int chanUp[67] = {...}; const unsigned int chanDown[67] = {...}; const unsigned int onoff[67] = {...}; const unsigned int mute[67] = {...};
Затем давайте настроим ИК-библиотеку и требуемую частоту (для такого рода приложений она всегда будет 38 кГц):
IRsend irsend; const int freq = 38;
Нам также понадобится функция для отправки ИК-команд и мигания встроенного светодиода (на этом этапе в основном для целей отладки). Используемое значение задержки будет зависеть от марки и модели вашего телевизора, поэтому не стесняйтесь настраивать его, если все работает не так, как ожидалось (например, неправильные команды или команды не получены):
void sendIR(const unsigned int buf[]) { digitalWrite(LED_BUILTIN, HIGH); irsend.sendRaw(buf, 67, freq); delay(300); digitalWrite(LED_BUILTIN, LOW); }
Последний шаг - завершить сгенерированный обратный вызов onTvChange() с некоторым пользовательским кодом, чтобы отправлять ИК-команды, когда свойство TV изменяется с помощью команд Alexa. Например, если громкость увеличивается, мы должны фактически нажать кнопку увеличения громкости, если канал установлен на 7, мы должны отправить последовательность для кнопки канала 7 и так далее.
void onTvChange() { Serial.println("=================="); Serial.println("Switch:"+String(tv.getSwitch())); Serial.println("Volume:"+String(tv.getVolume())); Serial.println("Channel:"+String(tv.getChannel())); Serial.println("Mute:"+String(tv.getMute())); Serial.println("=================="); if (first){ prevSwitch = tv.getSwitch(); prevVolume = tv.getVolume(); prevChannel = tv.getChannel(); prevMute = tv.getMute(); first = false; return; } // Volume changed if (tv.getVolume() > prevVolume) { tv.setMute(false); prevMute = false; for (int k = prevVolume + 1 ; k<=tv.getVolume(); k++) { sendIR(volUp); Serial.println("Volume requested:"+String(tv.getVolume())+" Set:"+String(k)); } prevVolume = tv.getVolume(); } else if (tv.getVolume() < prevVolume) { tv.setMute(false); prevMute = false; for (int k = prevVolume - 1; k>=tv.getVolume(); k--) { sendIR(volDown); Serial.println("Volume changed:"+String(tv.getVolume())+" Set:"+String(k)); } prevVolume = tv.getVolume(); } // Mute changed if (tv.getMute() != prevMute && tv.getMute()) { prevMute = tv.getMute(); sendIR(mute); Serial.println("Mute changed:"+String(tv.getMute())); } else if (tv.getMute() != prevMute && !tv.getMute()) { prevMute = tv.getMute(); sendIR(mute); Serial.println("Mute changed:"+String(tv.getMute())); } // Channel changed if (tv.getChannel() != prevChannel) { int newChannel = tv.getChannel(); if (newChannel > 0 && newChannel < 10) { sendIR(chan[newChannel-1]); } else if (newChannel > 9) { if (newChannel > prevChannel) { for (int ch = prevChannel; ch < newChannel; ch++) { sendIR(chanUp); Serial.println("Chan requested:"+String(newChannel)+" Set:"+String(ch)); } } else if (newChannel < prevChannel) { for (int ch = prevChannel; ch > newChannel; ch--) { sendIR(chanDown); Serial.println("Chan requested:"+String(newChannel)+" Set:"+String(ch)); } } } prevChannel = newChannel; Serial.println("Channel changed:"+String(tv.getChannel())); } // On/Off changed if (tv.getSwitch() != prevSwitch) { prevSwitch = tv.getSwitch(); if (tv.getSwitch()) { sendIR(chan[6]); } else { sendIR(onoff); } Serial.println("Switch changed:"+String(tv.getSwitch())); }
Скачать необходимые файлы урока:
Amazon Alexa
Теперь нам понадобится приложение Amazon Alexa, которое можно загрузить из:
После установки войдите в свою учетную запись или создайте новую.
Давайте пройдемся по шагам, необходимым для установки навыка Arduino для Alexa и настройки его для доступа и управления телевизором. Следуйте последовательно изображениям ниже, чтобы увидеть все необходимые шаги:
Пришло время для голосового управления нашим телевизором, который будет реагировать на фразы, например:
- Алекса, включи звук на телевизоре (Alexa, turn the volume up on TV)
- Алекса, отключи звук на ТВ (Alexa, mute TV)
- Алекса, включи звук на ТВ (Alexa, unmute TV)
- Алекса, следующий канал на телевизоре (Alexa, next channel on TV)
На этом всё.