Arduino Home Controller с Alexa: умный дом голосовым управлением
Компоненты и материалы
| Arduino Yun | × | 1 |
| 4-канальный I2C релейный модуль (ControlEverything.com) | × | 1 |
| DHT11 — датчик температуры и влажности (4 пина) | × | 1 |
| Резистор 10 кОм | × | 2 |
| Зуммер SparkFun RedBot | × | 1 |
| Транзистор NPN (универсальный) | × | 1 |
| LM2596 Buck-конвертер | × | 1 |
| Amazon Echo Dot (Alexa) | × | 1 |
| Arduino Proto Shield | × | 1 |
| USB-вебкамера | × | 1 |
| Коробка для монтажа 200×155×80 мм | × | 1 |
Инструменты
- Паяльник
Приложения и сервисы
- Arduino IDE
- Amazon Alexa Skills Kit
- AWS Lambda
- PubNub Publish/Subscribe API
О проекте
Обзор
Запуск Alexa и Echo Dot открыл бесконечные возможности для умного дома голосом на естественном языке.
Этот проект — Arduino Home Controller, соединяющий датчики и устройства с интернетом для управления через Alexa Skill.
Можете установить мой скилл Arduino Home Controller (простой способ) или создать свой по этому руководству.
На данный момент управляется:
- Четыре света: комната, гараж, кухня, гостиная
- Датчик температуры и влажности
- Зуммер-аларма
- Веб-камера — фото безопасности на email
В демо используются 4 светодиода на макетке вместо ламп. Для удалённых модулей см. проект Arduino Home Controller Remote Modules.
Блок-схема
Четыре основных компонента (рисунок):
- Amazon Echo Dot — приём голосовых команд, отправка в AWS.
- AWS (Skill + Lambda) — интерпретация команд. Скилл: Arduino Home Controller.
- MQTT-брокер PubNub — приём сообщений в двух каналах (управление и чтение сенсоров). Аккаунт PubNub не нужен — можно использовать мой для демо.
- Home Controller на Arduino Yun — встроенный Ethernet, Linux (OpenWRT) для Python-скриптов. Управляет реле, читает DHT11, включает зуммер, делает фото веб-камерой.

Установка ПО (два варианта)
Вариант 1: Демо — используйте мой скилл
В Alexa App: Skills → поиск «Arduino» → «Arduino Home Controller» → Enable.

Или прямая ссылка в Amazon Skill Store → Enable.

Привяжите Amazon-аккаунт (email используется как ID для команд, спама нет).



Скилл установлен, привязан к аккаунту, подключён к моей Lambda-функции.
Вариант 2: Свой скилл (полное руководство)
1. Создание скилла
Регистрация на developer.amazon.com.
- Alexa → Alexa Skills Kit → Get Started.
- Add a New Skill.
- Custom Interaction Model, English US, Name: TestSkill, Invocation Name: Test Skill, Global fields — No. Save.

Появится Application ID (например amzn1.ask.skill.6e22e052-c32f-433c-8d39-dc94a77a4adb) — нужен для связи с Lambda.

1.4 Interaction Model
В Intent Schema вставьте JSON:
{
"intents": [
{ "slots": [ {"name": "light", "type": "States"}, {"name": "which", "type": "HabRooms"} ], "intent": "LightIntent" },
{ "slots": [ {"name": "alarm", "type": "States"} ], "intent": "AlarmIntent" },
{ "intent": "TakePhotoIntent" },
{ "intent": "GetTemperatureIntent" },
{ "intent": "GetHumidityIntent" },
{ "intent": "AboutIntent" },
{ "intent": "AMAZON.HelpIntent" },
{ "intent": "AMAZON.CancelIntent" },
{ "intent": "AMAZON.StopIntent" }
]
}
Custom Slot Types: States (on, off), HabRooms (room, kitchen, garage, living room).



Sample Utterances (модель разговора):
LightIntent Turn {which} light {light}
LightIntent Set {which} light {light}
AlarmIntent Turn alarm {alarm}
TakePhotoIntent Take a Photo
TakePhotoIntent Take Security Photo
GetTemperatureIntent Read Temperature
GetHumidityIntent Read Humidity
AboutIntent Tell me about
AboutIntent About

Перед Configuration нужно создать Lambda.
2. Создание Lambda-функции
AWS Console: aws.amazon.com/console

- Services → Lambda (левое меню).
- Functions → Create function.


2.3 Настройки:
- Author from Scratch
- Name: TestSkillLambda
- Runtime: Node.js 6.10
- Role: Create Custom Role → IAM Role: Lambda_Basic_Execution → Policy: Create New Role Policy → вставьте JSON политики (логи + iot:UpdateThingShadow) → Add → вернитесь, выберите lambda_basic_execution.


2.4 Функция создана, страница Designer.

2.5 Add triggers → Alexa Skills Kit → Configure triggers → Enable → Skill ID: вставьте Application ID из шага 1.3. Save.



2.6 Function code: Code entry type: Upload .zip file, Runtime: Node.js 6.10, загрузите data.zip. Откроется редактор — выберите index.js. В строке 23 замените Application ID на свой. Save.


var APP_ID = 'amzn1.ask.skill.6e22e052-c32f-433c-8d39-dc94a77a4adb';
ARN (Amazon Resource Name) функции — копируйте для следующего шага.

3. Возврат к скиллу — Configuration
- Endpoint: AWS Lambda ARN → вставьте ARN в Default. Скилл и Lambda связаны.

- Account Linking → скопируйте первые два Redirect URL для профиля безопасности.

4. Security Profile (Login with Amazon)
Developer Console → Apps & Services → Login with Amazon → Create a New Security Profile.

- Security Profile Name: TestSkillProfile, Description: Profile used by TestSkill, Consent Privacy Notice URL: ваш URL политики. Save.

- Появятся Client ID и Client Secret — нужны для завершения настройки скилла.

- Web Settings → Allowed Origins:
https://layla.amazon.comиhttps://pitangui.amazon.com. Allowed Return URLs: два URL из шага 3.2.

5. Завершение скилла
- Configuration → Account Linking: Allow users to create account: Yes. Authorization URL:
https://www.amazon.com/ap/oa/?redirect_url=ВАШ_REDIRECT_URL. Client ID: из 4.3. Scope: profile.

- Authorization Grant Type: Auth Code Grant. Access Token URI:
https://api.amazon.com/auth/o2/token. Client Secret: из 4.3. Privacy Policy URL: ваш. Save → Next.

- Global Fields: Category: Smart Home. Testing instructions. Available worldwide. Short/Full description. Example phrases. Keywords. Icons 108×108 и 512×512.



- Privacy & Compliance — заполните.

6. Установка TestSkill в Alexa App
- Alexa App → Skills → Your Skills → Dev Skills → карта TestSkill → Enable.

- Кликните карту → Enable → редирект на логин Amazon для привязки.


- Если логин успешен — скилл привязан к аккаунту.

- Готово. Тест: «Alexa, ask Test Skill» — услышите приветствие Arduino Home Controller.

Настройка Arduino Yun
Если первый раз — подготовьте по гайду Greg Baugues (Getting started with Arduino Yun).
Для фото и email нужен доп. софт. Подключите USB-вебкамеру к Yun.
Подключитесь по SSH:
ssh root@yourYunName.local
# пароль по умолчанию: arduino

Установка ПО для камеры
opkg update
opkg install kmod-video-uvc
opkg install python-openssl
opkg install fswebcam
Проверьте SD-карту: mount — ищите /dev/sda1 on /mnt/sda1 type vfat.

Тест камеры:
cd /mnt/sda1
fswebcam test.png
Должно появиться Opening /dev/video0... /dev/video0 opened. Проверьте фото на компьютере.
Установка отправки email
Скопируйте на SD AHC_SendEmail.py, поменяйте строки 11,12,13 (ваш Gmail и пароль), при другом провайдере — строку 36 (SMTP-сервер).


Вставьте SD в Yun, выполните:
cd /mnt/sda1
python /mnt/sda1/AHC_SendEmail.py /mnt/sda1/ test.png
Должно прийти письмо с фото.
Подключение железа к Arduino Yun
По схеме: DHT11 (температура/влажность), зуммер (аларма).
Загрузка скетча ArduinoHomeMQTT.ino
Скачайте libraries.zip с GitHub — распакуйте в libraries Arduino IDE. Включает:
- ArduinoJson — работа с JSON
- DHT_sensor_library — DHT11
- pubsubclient-master — MQTT (PubNub)
Arduino IDE 1.6.x → Board: Arduino Yun. Откройте ArduinoHomeMQTT.ino:
- Строка 16: ваш Amazon email (привязанный в скилле) — идентификатор контроллера.
- Строка 50: ваш UUID для PubNub (сгенерируйте на uuidgenerator.net).


Сохраните, загрузите. В Serial Monitor: подключение к PubNub, инициализация сенсора.

Тестирование команд из Alexa
Скилл Arduino Home Controller понимает:
Управление светом:
"Alexa, ask arduino home to turn room light on"
"Alexa, ask arduino home to turn room light off"
"Alexa, ask arduino home to turn kitchen light on"
"Alexa, ask arduino home to turn kitchen light off"
"Alexa, ask arduino home to turn garage light on"
"Alexa, ask arduino home to turn garage light off"
"Alexa, ask arduino home to turn living room light on"
"Alexa, ask arduino home to turn living room light off"
Аларма:
"Alexa, ask arduino home to turn alarm on"
"Alexa, ask arduino home to turn alarm off"
Температура и влажность:
"Alexa, ask arduino home to read temperature"
"Alexa, ask arduino home to read humidity"
Фото безопасности:
"Alexa, ask arduino home to take a photo"
О проекте и помощь:
"Alexa, ask arduino home about"
"Alexa, ask arduino home help"

Детали ПО
Все файлы на GitHub.
ArduinoHomeMQTT— ino + библиотеки для Yunlambda— Node.js Lambda для AWS (вариант 2)python— скрипт отправки email с фотоschematic— Fritzing-схема
Workflow:
- Echo Dot → голос → Skill.
- Skill вызывает Lambda (
index.js). - Lambda (Node.js + модули) на AWS.
- Lambda интерпретирует интенты → MQTT JSON в PubNub. Формат:
Топики:{ "topic": "Topic_xxxx", "command": "on/off", "id": "email" }Topic_room,Topic_kitchen,Topic_garage,Topic_livingroom,Topic_alarm,Topic_temperature,Topic_humidity,Topic_photo. Команды:on,off(для фото —take). - Канал
AHC_IOT_01— команды,AHC_IOT_02— ответы (температура/влажность). - Yun-скетч подписан на каналы, получает/шлёт сообщения.
- Через
Bridgeскетч общается с Linux OpenWRT (сеть, команды). - Email — через Python
AHC_SendEmail.py, фото —fswebcamв Linux.

Детали железа
Центр — Arduino Yun. Цифровые пины:
Pin 2 : DHT11
Pin 8 : Зуммер (через NPN транзистор)
Pin 9 : Свет комнаты
Pin 10 : Свет кухни
Pin 11 : Свет гаража
Pin 12 : Свет гостиной
Веб-камера — USB. Питание 12В DC → buck-конвертер → 5В. Сделал шилд на Proto Shield с разъёмами, интерфейсом сенсоров, зуммера, питания. Места для расширения.


Всё в пластиковой коробке: Yun, Proto Shield, реле, buck-конвертер. Аларма, веб-камера, датчик — снаружи. Соединения по схеме.




Спасибо за внимание! Проект в память об отце — R.I.P. Dad.
Код
ArduinoHomeMQTT.ino (Arduino)
// ArduinoHomeMQTT v2.1
// Copyright (C)2018 Jose Cruz. All right reserved
// web: https://sites.google.com/view/raeiot/home
#include <YunClient.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <Process.h>
// Ваш Amazon email, привязанный в Alexa App
#define MyID "YOUR AMAZON EMAIL LINKED IN ALEXA APP"
// DHT11
#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
// Аларма
#define alarm 8
// Реле света (активный LOW)
#define room_light 9
#define kitchen_light 10
#define garage_light 11
#define livingroom_light 12
// PubNub MQTT
int mqtt_server_port = 1883;
const char* mqtt_server = "mqtt.pndsn.com";
const char* topic = "AHC_IOT_01"; // subscribe
const char* topic2 = "AHC_IOT_02"; // publish
// PubNub keys (demo)
const char* clientId = "pub-c-e93def7f-95aa-475a-aa60-cc3cd32ee8a7/sub-c-ec04dbbc-0893-11e8-8e75-cea83f8405bb/YOUR UUID GENERATED";
YunClient ethClient;
PubSubClient client(ethClient);
Process picture;
String filename;
String path = "/mnt/sda1/";
void TakePhoto() {
filename = "";
picture.runShellCommand("date +%s");
while (picture.running());
while (picture.available() > 0) {
char c = picture.read();
filename += c;
}
filename.trim();
filename += ".jpg";
picture.runShellCommand("/usr/bin/fswebcam -i 0 --jpeg 95 --no-banner --fps 1 -S 1 -r 352x288 --save " + path + filename);
while (picture.running());
picture.runShellCommand("python /mnt/sda1/AHC_SendEmail.py " + path + " " + filename);
while (picture.running());
}
void SendTemperature() {
char cstr[16];
float t = dht.readTemperature();
client.publish(topic2, itoa((int)t, cstr, 10));
}
void SendHumidity() {
char cstr[16];
float h = dht.readHumidity();
client.publish(topic2, itoa((int)h, cstr, 10));
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) Serial.print((char)payload[i]);
Serial.println();
StaticJsonBuffer<200> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char*)payload);
String alexa_topic = root["topic"];
String alexa_command = root["command"];
String alexa_id = root["id"];
if (alexa_id.equals(MyID)) {
if (alexa_topic.endsWith("Topic_room")) {
digitalWrite(room_light, alexa_command.endsWith("on") ? LOW : HIGH);
} else if (alexa_topic.endsWith("Topic_kitchen")) {
digitalWrite(kitchen_light, alexa_command.endsWith("on") ? LOW : HIGH);
} else if (alexa_topic.endsWith("Topic_garage")) {
digitalWrite(garage_light, alexa_command.endsWith("on") ? LOW : HIGH);
} else if (alexa_topic.endsWith("Topic_living room")) {
digitalWrite(livingroom_light, alexa_command.endsWith("on") ? LOW : HIGH);
} else if (alexa_topic.endsWith("Topic_temperature")) {
SendTemperature();
} else if (alexa_topic.endsWith("Topic_humidity")) {
SendHumidity();
} else if (alexa_topic.endsWith("Topic_alarm")) {
digitalWrite(alarm, alexa_command.endsWith("on") ? HIGH : LOW);
} else if (alexa_topic.endsWith("Topic_photo")) {
TakePhoto();
}
}
}
void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect(clientId)) {
Serial.println("MQTT broker connected");
client.subscribe(topic);
} else {
Serial.print("Failed, rc = ");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
Bridge.begin();
delay(2000);
Serial.begin(115200);
Serial.println("Init Arduino Home Controller v2.1...");
pinMode(alarm, OUTPUT);
pinMode(room_light, OUTPUT);
pinMode(kitchen_light, OUTPUT);
pinMode(garage_light, OUTPUT);
pinMode(livingroom_light, OUTPUT);
digitalWrite(room_light, HIGH);
digitalWrite(kitchen_light, HIGH);
digitalWrite(garage_light, HIGH);
digitalWrite(livingroom_light, HIGH);
client.setServer(mqtt_server, mqtt_server_port);
client.setCallback(callback);
client.connect(clientId);
Serial.println("Connected to PubNub MQTT broker OK ...");
client.subscribe(topic);
Serial.println("Subscribe to topic at MQTT broker Ok ...");
dht.begin();
delay(4000);
Serial.println("DHT sensor Ok ...");
}
void loop() {
if (!client.connected()) reconnect();
client.loop();
}
AHC_SendEmail.py (Python для OpenWRT)
import smtplib
import sys
import time
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email import encoders
fromaddr = "joseacruzp@gmail.com"
toaddr = "joseacruzp@gmail.com"
emailpassword = "YOUR PASSWORD"
subject = "Arduino Home Controller by Alexa Skill"
body = "Photo taken in " + time.strftime('%a, %d %b %Y %H:%M:%S %Z(%z)')
filedir = sys.argv[1]
filename = sys.argv[2]
msg = MIMEMultipart()
msg['From'] = fromaddr
msg['To'] = toaddr
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
attachment = open(filedir + filename, "rb")
part = MIMEBase('application', 'octet-stream')
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header('Content-Disposition', "attachment; filename= %s" % filename)
msg.attach(part)
server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login(fromaddr, emailpassword)
text = msg.as_string()
server.sendmail(fromaddr, toaddr, text)
server.quit()
index.js (AWS Lambda, Node.js)
// Alexa SDK for Arduino Home Controller
// Jose Cruz https://sites.google.com/view/jriot
var PubNub = require("pubnub");
var pubnub = new PubNub({
ssl: true,
publish_key: "pub-c-e93def7f-95aa-475a-aa60-cc3cd32ee8a7",
subscribe_key: "sub-c-ec04dbbc-0893-11e8-8e75-cea83f8405bb",
uuid: "YOUR UUID"
});
var channel = 'AHC_IOT_01';
var channel2 = 'AHC_IOT_02';
var slotStates = ['on', 'off'];
var APP_ID = 'YOUR APP ID FOR THE SKILL';
var AlexaSkill = require('./AlexaSkill');
var ArduinoHomeSkill = function() {
AlexaSkill.call(this, APP_ID);
};
var mqttListener;
var access_token;
var url;
var https = require("https");
function SendMessage(topicname, commandvalue, speechOutput, iresponse) {
https.get(url, res => {
res.setEncoding("utf8");
let body = "";
res.on("data", data => { body += data; });
res.on("end", () => {
pubnub.publish({
channel: channel,
message: { "topic": topicname, "command": commandvalue, "id": JSON.parse(body).email }
}).then(response => {
console.log("message Published w/ timetoken", response.timetoken);
if (speechOutput) iresponse.tell(speechOutput);
}).catch(error => {
console.log("publishing failed w/ status: ", error);
iresponse.ask("Sorry, I didn't catch what you said");
});
});
});
}
ArduinoHomeSkill.prototype = Object.create(AlexaSkill.prototype);
ArduinoHomeSkill.prototype.constructor = ArduinoHomeSkill;
ArduinoHomeSkill.prototype.eventHandlers.onSessionStarted = function(sessionStartedRequest, session) {
console.log("ArduinoHomeSkill onSessionStarted requestId: " + sessionStartedRequest.requestId + ", sessionId: " + session.sessionId);
pubnub.removeListener(mqttListener);
pubnub.unsubscribeAll();
};
ArduinoHomeSkill.prototype.eventHandlers.onLaunch = function(launchRequest, session, response) {
console.log("ArduinoHomeSkill onLaunch requestId: " + launchRequest.requestId + ", sessionId: " + session.sessionId);
if (session.user.accessToken == undefined) {
response.tellWithCardLink('To start using this skill, please use the companion app to authenticate on Amazon.');
return;
}
var speechOutput = "Welcome to Arduino Home Controller. What would you like to do?";
var repromptText = "I am ready";
response.ask(speechOutput, repromptText);
};
ArduinoHomeSkill.prototype.eventHandlers.onSessionEnded = function(sessionEndedRequest, session) {
console.log("ArduinoHomeSkill onSessionEnded requestId: " + sessionEndedRequest.requestId + ", sessionId: " + session.sessionId);
};
ArduinoHomeSkill.prototype.intentHandlers = {
"AboutIntent": function(intent, session, response) {
var myText = "Arduino Home Controller skill lets you control internet connected devices. It controls Lights (room, kitchen, garage, living room), reads temperature sensor, reads humidity sensor, uses webcam to take a home security photo and send it by email, and activates an alarm. It uses PubNub to manipulate all messages sent by Alexa with Lambda function. Check skill page for details. What would you like to do?";
response.ask(myText);
},
"LightIntent": function(intent, session, response) {
var slotHabRooms = ['room', 'kitchen', 'garage', 'living room'];
var lightSlot = intent.slots.light;
var lightSlotValue = lightSlot ? lightSlot.value : "";
var whichSlot = intent.slots.which;
var whichSlotValue = whichSlot ? whichSlot.value : "";
if (lightSlotValue && whichSlotValue && slotStates.indexOf(lightSlotValue.toLowerCase()) > -1 && slotHabRooms.indexOf(whichSlotValue.toLowerCase()) > -1) {
SendMessage('Topic_' + whichSlotValue, lightSlotValue, "The light is now " + lightSlotValue, response);
} else {
response.ask("Sorry, I didn't catch what you said");
}
},
"AlarmIntent": function(intent, session, response) {
var alarmSlot = intent.slots.alarm;
var alarmSlotValue = alarmSlot ? alarmSlot.value : "";
if (alarmSlotValue && slotStates.indexOf(alarmSlotValue.toLowerCase()) > -1) {
SendMessage('Topic_alarm', alarmSlotValue, "The alarm is now " + alarmSlotValue, response);
} else {
response.ask("Sorry, I didn't catch what you said");
}
},
"TakePhotoIntent": function(intent, session, response) {
SendMessage('Topic_photo', 'take', "Taken home photo ", response);
},
"GetTemperatureIntent": function(intent, session, response) {
mqttListener = {
status: function(statusEvent) {},
message: function(message) {
console.log("Receive=", message);
var myText = "Inside Temperature is " + message.message + " degrees C";
response.tell(myText);
},
presence: function(presenceEvent) {}
};
pubnub.addListener(mqttListener);
pubnub.subscribe({ channels: [channel2] });
SendMessage('Topic_temperature', 'Ok', null, response);
},
"GetHumidityIntent": function(intent, session, response) {
mqttListener = {
status: function(statusEvent) {},
message: function(message) {
console.log("Receive=", message);
var myText = "Inside Humidity is " + message.message + " %";
response.tell(myText);
},
presence: function(presenceEvent) {}
};
pubnub.addListener(mqttListener);
pubnub.subscribe({ channels: [channel2] });
SendMessage('Topic_humidity', 'Ok', null, response);
},
"AMAZON.HelpIntent": function(intent, session, response) {
response.ask("With Arduino Home Controller skill and Alexa you can control internet connected devices using an Arduino Yun or Arduino with Ethernet Shield. Check skill page for details. What would you like to do?");
},
"AMAZON.StopIntent": function(intent, session, response) {
response.tell("Thanks for using Arduino Home Controller. Bye see you later");
},
"AMAZON.CancelIntent": function(intent, session, response) {
response.tell("Thanks for using Arduino Home Controller. Bye see you later");
},
default: function(intent, session, response) {
response.ask("Try again");
}
};
exports.handler = function(event, context) {
try {
access_token = event['context']['System']['user']['accessToken'];
url = 'https://api.amazon.com/user/profile?access_token=' + access_token;
} catch (error) {
console.log(error);
}
var ArduinoHomeControl = new ArduinoHomeSkill();
ArduinoHomeControl.execute(event, context);
};
GitHub
https://github.com/jcruzp/ArduinoHomeController
Схемы
Главная плата с реле, DHT11, зуммером, веб-камерой.

Производственный процесс
- Веб-управляемая DMX-система освещения: Arduino Mega как мастер-контроллер
- Цифровые кости на Arduino: генератор случайных чисел с LCD-дисплеем и кнопкой
- DIY USB-геймпад на Arduino: собственный высокопроизводительный контроллер
- Pixel Chaser: игра на один тап с NeoPixel Ring и Arduino Nano
- Умная система автополива растений на Arduino Nano
- Магнитная левитация на отталкивании с Arduino: Простая схема и DIY-руководство
- Контроллер вакуумно-люминесцентного дисплея (VFD) на Arduino — DIY набор
- Автономный домашний помощник-робот: полный список деталей и руководство по настройке (ROS, Alexa, MoveIt)
- NeoPixel Matrix Pong на Arduino Nano: неоновая игра Pong на матрице 15×10
- Arduino DMX-512 Tester Controller – Полный набор деталей для надежного управления освещением