Smart Cube: контроллер нового поколения для умного дома
Компоненты и материалы
| Компонент | Количество |
|---|---|
| Arduino 101 | 1 |
| Светодиоды (общие): 1 синий, 1 жёлтый, 1 зелёный, 2 красных | 5 |
| Резисторы 220 Ом | 5 |
| Макетная плата (не обязательна) | 1 |
| Seeed Base Shield V2 | 1 |
| Seeed Grove LCD RGB Backlight | 1 |
| Seeed Grove Sound Sensor | 1 |
| Seeed Grove Buzzer | 1 |
| Seeed Grove Temperature Sensor | 1 |
| Матричная клавиатура (Keypad) | 1 |
| Перемычки | 1 |
| Крона 9V | 1 |
| Клипса для кроны 9V | 1 |
| МДФ 20x30 см, 3 мм (для лазерной резки) | 1 |
Инструменты
- Лазерный резак
- Паяльник
Программное обеспечение и сервисы
- Arduino IDE
- Nordic Semiconductor nRF Connect SDK (не обязательно)
О проекте
Что такое SMART CUBE?
Smart Cube — это контроллер для устройств умного дома. В наше время умное управление всеми приборами дома становится необходимостью. Нам нужно что-то простое и интуитивное. Smart Cube может стать решением: это портативное устройство с платой Arduino 101 и набором датчиков, которые общаются с приборами и исполнительными механизмами вашего умного дома.
Инновация пульта связана с инновациями Arduino 101. Он использует Bluetooth (BLE) для управления светом, температурой и ТВ, а также синхронизируется со смартфоном. Но главная инновация — способ взаимодействия с кубиком жестами с помощью гироскопа и акселерометра. У куба 6 граней, каждая управляет определённой функцией. Функция определяется гироскопом, который распознаёт верхнюю грань. Например, чтобы включить свет в комнате, просто положите куб гранью «свет» вверх. Чтобы выключить — встряхните куб.
Если грань ВВЕРХУ и не двигается более 2 секунд — активируется соответствующая функция. Эта задержка даёт время повернуть куб и найти нужную грань без случайных срабатываний. Встряхивание более 2 секунд закрывает последнюю активированную функцию.
Проект помогает построить умный контроллер. Устройству нужен приёмник (умная центральная единица на картинке), который интерпретирует данные и управляет функциями дома. Основной выход куба — BLE-сигнал, который можно использовать по-разному: подключить к приложению на смартфоне (или написать своё), или построить базовую станцию на другом Arduino, которая принимает BLE и напрямую управляет приборами. Так как вариантов много, эта часть зависит от ваших задач. В этом руководстве мы разберём, как собрать Smart Cube и как его персонализировать.

Функции граней
Структура кода легко персонализируется (разберём позже), поэтому вы решаете, какие функции управлять. Вот мой выбор:

СВЕТ (LIGHTS): Грань вверх — включает свет в комнате. Встряхивание — выключает свет и зажигает жёлтый LED на этой грани, чтобы найти куб в темноте. Связь по BLE.
ТЕМПЕРАТУРА (TEMPERATURE): Грань вверх — включает кондиционер, если жарко, или отопление, если холодно. Определяет температуру термодатчиком, зажигает синий LED (снежинка) при кондиционере или красный (солнце) при отоплении.
ТАЙМЕР (TIMER): Грань вверх — запускает обратный отсчёт на LCD. Зуммер пищит как будильник по окончании. Встряхивание — стоп, показывает остаточное время. Когда время вышло — LCD краснеет, зуммер пищит 3 раза.
ТВ (TV): Грань вверх — включает ТВ. Куб становится пультом. Кнопочная панель с цифрами активна только когда эта грань вверх (защита от случайных нажатий). Нажатая цифра уходит по BLE в центральный контроллер, подключённый к ТВ. Встряхивание — выключает ТВ.
ЗВУК (SOUND): Грань вверх — яркость зелёного LED зависит от уровня шума в комнате (датчик звука). Встряхивание — отключает функцию. (Можно использовать хлопки для активации своих сценариев).
OFF грань: Если не встряхивать — нейтральная позиция, ничего не меняется. Чтобы выключить всё — положите OFF грань вверх и встряхните. Жест как «Home» на смартфоне. Красный LED помогает найти куб в темноте.
Отличия от концепции
Изначально планировалось также использовать IR-коммуникацию для прямого управления ТВ. Но Arduino 101 пока не поддерживает библиотеку IR, поэтому решил передавать команды по BLE на станцию, где эта библиотека есть (например, Arduino UNO). Из-за этого убрал функцию радио (похожую на TV), заменив на грань со звуковым датчиком. Эта грань может распознавать громкие звуки (хлопки) для активации сценариев. Крупнейшее изменение — материал корпуса. Сначала думал печатать на 3D-принтере, но выбрал лазерную резку. Так легче заменять грани, если хотите сменить функцию. Например, убрать таймер и поставить грань для автозаполнения миски кота. Будучи модульным, не нужно пересобирать весь куб!

Сборка: пошагово
Следуйте инструкциям. Начнём с электроники, соберём компоненты, потом — корпус из МДФ. После загрузки кода на Arduino 101 можно скачать приложение для просмотра BLE-выходов или подключить напрямую к умному дому!

Электроника: подключение
Начнём с Arduino 101. Ниже схема Fritzing:

Установите Grove Base Shield на плату и начните подключения.


Keypad (клавиатура): провода подключаются к пинам в порядке: 10, 8, 7, 6, 5, 4, 2.



Светодиоды:
- Длинная нога (анод) → резистор 220 Ом → красный провод (к пину)
- Короткая нога (катод) → белый провод (к GND)
- Припаяйте, изолируйте изолентой
- Все белые провода объедините в один (на GND)
Подключение красных проводов к Arduino: зелёный LED → пин 9, жёлтый → 11, красный → 12, синий → 13, второй красный → A2.




Grove-датчики: подключите к шилду по схеме (см. вложения). Зуммер → D3, LCD RGB → I2C, температуру → A0, звук → A1.

Корпус: лазерная резка и сборка
Скачайте чертеж куба (.dxf) для лазерного резака. Красные линии — гравировка (символы граней), чёрные — рез. Внутренний размер куба — 9 см. Чертеж рассчитан на материал 3 мм, при другой толщине нужно скорректировать (можно использовать MakerCase).
Материал — МДФ (средней плотности), но можно использовать другой.




Сборка:
- Приклеил кусочки картона под LED, чтобы они выходили в одну плоскость с поверхностью.



Установка клавиатуры: вставьте в отверстие, снимите защитную плёнку, подключите пины.



RGB LCD вставляется идеально в отверстие.


Подготовка батареи: отрезал старый штекер блока питания, припаял к клипсе для короны. Экономит место внутри.



Итоговый вид перед закрытием:

Осталось подключить батарею, загрузить код и закрыть куб!
Совет: зафиксируйте плату и батарею внутри корпуса, чтобы они не шатались при встряхивании.

Код
Код в вложениях. Загрузите через Arduino IDE (обновите IDE для поддержки Arduino 101). Почти каждая строка прокомментирована. Не пугайтесь 500+ строк — структура понятна.
Структура кода:
- Библиотеки и объявление переменных, определение BLE-сервисов и характеристик (Automation IO: Digital для света/температуры, Analog для ТВ)
- setup() — однократная инициализация пинов, BLE, гироскопа
- loop() — делится на 4 части:
- Определение ориентации платы (из туториала Arduino)
- Активация грани, если она вверх > 2 сек (через millis)
- Деактивация при встряхивании > 2 сек (последняя активная грань закрывается)
- Исполнение функций активных граней (ядро логики)
Функции граней можно модифицировать, меняя код внутри if (faceX == true) и соответствующие блоки деактивации при встряхивании. Не забывайте устанавливать faceX = false при встряхивании.
Приложение для проверки BLE
Скачайте nRF Connect: Google Play.
Откройте, найдите устройство «smartcubesketch», подключитесь. Откроются три вкладки «Automation IO» — нажмите на каждой непрерывный сбор данных.
Важно: некоторые функции (свет, температура, ТВ) работают только при подключении к центральному устройству.
Видео-демонстрация
Загрузите код, вставьте батарею, закройте коробку — и у вас есть SMART CUBE!
Снял видео со всеми функциями и управлением.
Заключение
Этот проект — только первый шаг к умному дому. Цель — использовать потенциал Arduino 101 для создания чего-то нового. Акселерометр и гироскоп открывают новые возможности мобильности, а BLE делает подключение простым.
Это мой первый завершённый рабочий прототип. Было сложно, были трудности, но в итоге многому научился и доволен результатом. Надеюсь, читатели тоже почерпнут что-то полезное и адаптируют проект под себя. Извините за английский — надеюсь, изображения помогут.
В будущем планирую создать центральную единицу — мост между Smart Cube и исполнительными механизмами (свет, температура, ТВ...). Она будет принимать BLE от куба и выполнять умные действия в доме.
Рад возможности использовать Arduino 101 и Grove kit (очень удобен для прототипирования). Надеюсь, это вдохновит вас на свои проекты. Персонализируйте как угодно — любопытно увидеть, какие функции придумаете и в каких контекстах примените!
Может, это не революция, но это новый способ взаимодействия с домом.
Теперь ваша очередь: возьмите куб в руки и хакните его!
Код (полный, с комментариями)
// Libraries
#include "CurieIMU.h" // Акселерометр и гироскоп
#include // Bluetooth Low Energy
#include
#include "rgb_lcd.h" // LCD
#include // Keypad
rgb_lcd lcd;
// Bluetooth:
BLEPeripheral blePeripheral;
// BLE Services
BLEService lightsService("1815"); // Automation IO - свет
BLEService termoService("1815"); // Automation IO - температура
BLEService TVService("1815"); // Automation IO - TV
// BLE Characteristics
BLEUnsignedCharCharacteristic lightsChar("2A56", BLERead | BLENotify); // Digital - свет
BLEUnsignedCharCharacteristic termoChar("2A56", BLERead | BLENotify); // Digital - температура
BLEUnsignedCharCharacteristic TVChar("2A58", BLERead | BLENotify); // Analog - TV
// Константы и переменные:
// Ориентация и встряхивание
int lastOrientation = -1;
unsigned long previousMillis = 0;
unsigned long interval = 2000;
unsigned long SHAKEpreviousMillis = 0;
unsigned long SHAKEinterval = 2000;
boolean keep = false;
int lastFUNCTION = -1;
// Грани (изначально все false)
boolean face0 = false; // TIMER
boolean face1 = false; // TV
boolean face2 = false; // TEMPERATURE
boolean face3 = false; // OFF
boolean face4 = false; // LIGHTS
boolean face5 = false; // SOUND
// LIGHTS
const int LEDlights = 11;
// TEMPERATURE
const int pinTemp = A0;
const int LEDhot = 12;
const int LEDcold = 13;
float temperature;
int B = 3975;
float resistance;
float tooHot = 26.0;
float tooCold = 23.0;
// TIMER
int BUZZER = 3;
boolean KEEPtime = false;
int TIMERmillis = 0;
int prevSHOWsecond = 0;
int CountdownInMillis = 0;
int SHOWmillis = 0;
int SHOWminute = 0;
int SHOWseconds = 0;
const int SETminute = 2;
const int SETsecond = 30;
// SOUND
const int soundLED = 9;
const int soundSENSOR = A1;
int brightness = 0;
// TV - Keypad
const byte ROWS = 4;
const byte COLS = 3;
char keys[ROWS][COLS] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte rowPins[ROWS] = {10, 8, 7, 6};
byte colPins[COLS] = {5, 4, 2};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// OFF
const int LEDOFF = A2;
void setup() {
pinMode(LEDlights, OUTPUT);
pinMode(LEDhot, OUTPUT);
pinMode(LEDcold, OUTPUT);
pinMode(soundLED, OUTPUT);
pinMode(LEDOFF, OUTPUT);
Serial.begin(9600);
CountdownInMillis = (SETminute * 60 + SETsecond) * 1000;
lcd.begin(16, 2);
lcd.setRGB(0, 0, 0);
Serial.println("Initializing IMU device...");
CurieIMU.begin();
CurieIMU.setAccelerometerRange(2);
CurieIMU.setDetectionThreshold(CURIE_IMU_SHOCK, 7000); // 7.0g = 7000 mg
CurieIMU.setDetectionDuration(CURIE_IMU_SHOCK, 2000); // 2000ms
CurieIMU.interrupts(CURIE_IMU_SHOCK);
// BLE setup
blePeripheral.setLocalName("SmartCubeSketch");
blePeripheral.setAdvertisedServiceUuid(lightsService.uuid());
blePeripheral.addAttribute(lightsService);
blePeripheral.addAttribute(lightsChar);
lightsChar.setValue(3); // 0=off, 1=on, 3=initial
blePeripheral.setAdvertisedServiceUuid(termoService.uuid());
blePeripheral.addAttribute(termoService);
blePeripheral.addAttribute(termoChar);
termoChar.setValue(0); // 0=cold off/hot off
blePeripheral.setAdvertisedServiceUuid(TVService.uuid());
blePeripheral.addAttribute(TVService);
blePeripheral.addAttribute(TVChar);
TVChar.setValue('x'); // x=nothing
blePeripheral.begin();
Serial.println("Bluetooth device active, waiting for connections...");
}
void loop() {
BLECentral central = blePeripheral.central();
unsigned long currentMillis = millis();
// Определение ориентации (из туториала Arduino)
int orientation = -1;
String orientationString;
int x = CurieIMU.readAccelerometer(X_AXIS);
int y = CurieIMU.readAccelerometer(Y_AXIS);
int z = CurieIMU.readAccelerometer(Z_AXIS);
int absX = abs(x);
int absY = abs(y);
int absZ = abs(z);
if ((absZ > absX) && (absZ > absY)) {
if (z > 0) { orientationString = "up"; orientation = 0; }
else { orientationString = "down"; orientation = 1; }
} else if ((absY > absX) && (absY > absZ)) {
if (y > 0) { orientationString = "digital pins up"; orientation = 2; }
else { orientationString = "analog pins up"; orientation = 3; }
} else {
if (x < 0) { orientationString = "connector up"; orientation = 4; }
else { orientationString = "connector down"; orientation = 5; }
}
/* Ориентации:
0: плоско, процессор вверх (TIMER)
1: плоско, процессор вниз (TV)
2: ландшафт, аналоговые пины вниз (TEMPERATURE)
3: ландшафт, аналоговые пины вверх (OFF)
4: портрет, USB вверх (LIGHTS)
5: портрет, USB вниз (SOUND)
*/
// Активация грани при удержании вверх > interval
if (orientation != lastOrientation) {
lastOrientation = orientation;
previousMillis = currentMillis;
keep = false;
} else if (currentMillis - previousMillis > interval && keep == false && lastFUNCTION != orientation) {
Serial.println(orientationString);
if (orientation == 1) { face1 = true; lastFUNCTION = orientation; Serial.println("TV true"); TVChar.setValue('O'); }
if (orientation == 4) { face4 = true; lastFUNCTION = orientation; Serial.println("LIGHTS true"); }
if (orientation == 3) { face3 = true; lastFUNCTION = orientation; Serial.println("OFF true"); }
if (orientation == 5) { face5 = true; lastFUNCTION = orientation; Serial.println("SOUND true"); }
if (orientation == 2) { face2 = true; lastFUNCTION = orientation; Serial.println("TEMPERATURE true"); }
if (orientation == 0) { face0 = true; lastFUNCTION = orientation; Serial.println("TIMER true"); if (!KEEPtime) TIMERmillis = currentMillis; }
keep = true;
}
// Встряхивание — деактивация
if (CurieIMU.getInterruptStatus(CURIE_IMU_SHOCK) && currentMillis - SHAKEpreviousMillis > SHAKEinterval) {
Serial.println("SHAKE");
// TV
if (lastFUNCTION == 1) { TVChar.setValue('C'); Serial.println("TV false - CLOSE"); face1 = false; }
// LIGHTS
if (lastFUNCTION == 4) { if (central.connected()) { lightsChar.setValue(0); digitalWrite(LEDlights, HIGH); } Serial.println("LIGHTS false - CLOSE"); face4 = false; }
// OFF — закрыть всё
if (lastFUNCTION == 3) {
digitalWrite(LEDOFF, HIGH);
TVChar.setValue('C'); Serial.println("TV false - CLOSE"); face1 = false;
Serial.println("LIGHTS false - CLOSE"); if (central.connected()) { lightsChar.setValue(0); digitalWrite(LEDlights, LOW); } face4 = false;
analogWrite(soundLED, LOW); Serial.println("SOUND false - CLOSE"); face5 = false;
if (central.connected()) { digitalWrite(LEDhot, LOW); digitalWrite(LEDcold, LOW); termoChar.setValue(0); } Serial.println("TEMPERATURE false - CLOSE"); face2 = false;
Serial.println("TIMER false - CLOSE"); lcd.setRGB(0,0,0); lcd.clear(); KEEPtime = false; face0 = false;
Serial.println("OFF false - CLOSE"); face3 = false;
}
// SOUND
if (lastFUNCTION == 5) { analogWrite(soundLED, LOW); Serial.println("SOUND false - CLOSE"); face5 = false; }
// TEMPERATURE
if (lastFUNCTION == 2) { if (central.connected()) { digitalWrite(LEDhot, LOW); digitalWrite(LEDcold, LOW); termoChar.setValue(0); } Serial.println("TEMPERATURE false - CLOSE"); face2 = false; }
// TIMER
if (lastFUNCTION == 0) {
Serial.println("TIMER false - CLOSE"); face0 = false;
lcd.setRGB(180, 40, 0); lcd.clear(); lcd.setCursor(0,0); lcd.print("STOP AT "); lcd.setCursor(8,0); lcd.print(SHOWminute); lcd.setCursor(9,0); lcd.print(":"); lcd.setCursor(10,0); lcd.print(SHOWseconds);
tone(BUZZER,1000,1000); delay(2000); lcd.clear(); lcd.setRGB(0,0,0); KEEPtime = false;
}
SHAKEpreviousMillis = currentMillis;
}
// Исполнение активных граней
if (face1 == true) { // TV
digitalWrite(LEDOFF, LOW);
if (central.connected()) {
char key = keypad.getKey();
if (key && orientation == 1) {
TVChar.setValue(key); Serial.println(key);
}
}
}
if (face4 == true) { // LIGHTS
digitalWrite(LEDOFF, LOW);
if (central.connected()) { lightsChar.setValue(1); digitalWrite(LEDlights, LOW); }
}
if (face3 == true) { // OFF - ничего не делает, LED OFF только при встряхивании
digitalWrite(LEDOFF, LOW);
}
if (face5 == true) { // SOUND
digitalWrite(LEDOFF, LOW);
long sum = 0;
for (int i = 0; i < 32; i++) sum += analogRead(soundSENSOR);
sum >>= 5;
brightness = (sum * 255) / 1024;
analogWrite(soundLED, brightness);
delay(50);
}
if (face2 == true) { // TEMPERATURE
digitalWrite(LEDOFF, LOW);
if (central.connected()) {
int val = analogRead(pinTemp);
resistance = (float)(1023 - val) * 10000 / val;
temperature = 1 / (log(resistance / 10000) / B + 1 / 298.15) - 273.15;
if (temperature > tooHot) { digitalWrite(LEDhot, LOW); digitalWrite(LEDcold, HIGH); termoChar.setValue(1); }
else if (temperature < tooCold) { digitalWrite(LEDhot, HIGH); digitalWrite(LEDcold, LOW); termoChar.setValue(2); }
else { digitalWrite(LEDhot, LOW); digitalWrite(LEDcold, LOW); termoChar.setValue(0); }
}
}
if (face0 == true) { // TIMER
digitalWrite(LEDOFF, LOW);
SHOWmillis = (CountdownInMillis - (currentMillis - TIMERmillis)) / 1000;
SHOWminute = SHOWmillis / 60;
SHOWseconds = SHOWmillis % 60;
lcd.setRGB(50, 100, 70);
if (prevSHOWsecond != SHOWseconds) {
lcd.clear(); lcd.setCursor(0,0); lcd.print("TIMER "); lcd.setCursor(7,0); lcd.print(SETminute); lcd.setCursor(8,0); lcd.print(":"); lcd.setCursor(9,0); lcd.print(SETsecond);
lcd.setCursor(0,1); lcd.print(SHOWminute); lcd.setCursor(1,1); lcd.print(":"); lcd.setCursor(2,1); lcd.print(SHOWseconds);
KEEPtime = true; prevSHOWsecond = SHOWseconds;
}
if (SHOWminute == 0 && SHOWseconds == 0) {
lcd.clear(); lcd.setCursor(4,0); lcd.setRGB(180,40,0); lcd.print("TIME OUT");
tone(BUZZER,1000,500); delay(900); tone(BUZZER,1000,500); delay(900); tone(BUZZER,1000,1000); delay(1500);
lcd.clear(); lcd.setRGB(0,0,0); face0 = false; KEEPtime = false;
}
}
}
3D-модели / Детали корпуса
Файл .dxf для лазерного резака. Использовал МДФ 3 мм. Чёрные линии — рез, красные — гравировка. Важно: при другой толщине материала чертеж нужно скорректировать.
Тот же файл в формате .pdf.
Схемы

Схема подключений на Grove Shield
Производственный процесс
- Солнечные панели: выгодная инвестиция для вашего дома
- Почему слегка больший дизайн может ускорить инновации и снизить риски
- Как технологии умного дома трансформируют повседневную жизнь
- Построение доступной беспроводной сети датчиков для дома
- Мониторинг домашней температуры с панелью управления на Raspberry Pi
- Преодоление разрыва навыков умного дома: почему Британия отстает от Европы
- Эволюция умного дома: что будущее готовит для коннектед жилья
- Умная безопасность: экспертные стратегии защиты домашних IoT-устройств от хакеров
- Как усилить безопасность умного дома: экспертные советы
- Умная теплица IoT на Arduino и Azure: датчики, автополив и удалённый контроль