RSS блога
Подписка
Альтернативная прошивка для sonoff и управления Livolo
- Цена: $4.85 + Доставка $4.61
- Перейти в магазин
Update: Добавлено видео
Рассказывать подробно о самом решении для управления силовой нагрузкой от Ited под названием Sonoff RF, я не вижу смысла, так как на mysku уже существует подобный обзор от пользователя spc. В данном обзоре, мне бы хотелось затронуть практическую сторону на примере реализации управления выключателем Livolo. Если кому-то интересна подобная тематика, прошу под кат.
Хотелось бы сразу отметить, что, не смотря на очевидные плюсы sonoff, типа готового аппаратно-программного решения в корпусе и народного Wifi модуля ESP8266 внутри, есть очень жирный минус — это закрытая прошивка и привязка к облачным сервисам Itead. Не нужно быть специалистом в области информационной безопасности, чтобы осознать какие могут возникнуть риски. Чтобы не изобретать велосипед и упростить выполнение поставленной задачи, было решено использовать прошивку с открытым исходным кодом от Theo Arends — Sonoff-MQTT-OTA-Arduino.
± Любой USB-UART переходник FTDI32RL, Cp2102 и т.д.*;
+ Wifi роутер в режиме точки доступа;
+ Локальный или облачный MQTT сервер;
± Паяльник, припой, флюс, штырьковый разъем и некоторые навыки пайки*.
* Можно использовать Веб-сервер и прошивку по воздуху (OTA: Over-the-air)
Для прошивки по USB-UART припаиваем штырьки
У меня в наличии есть только переходник, основанный на микросхеме CP2102 от SILICON LABS, обзор на который уже были на mysku.
Sonoff RX -> TX UART
Sonoff TX -> RX UART
Sonoff VCC -> 3.3V UART
Sonoff GND -> GND UART
Пример ошибки
Порядок действий
1. Скачиваем архив с Arduino 1.6.8+ с официального веб-сайта
2. Распаковываем архив в папку, например ESP8266_Arduino
3. Создаем в папке ESP8266_Arduino, папку Portable
4. Запускаем Arduino и открываем окно с настройками Файл->Настройки (File->Preferences);
5. Вводим arduino.esp8266.com/stable/package_esp8266com_index.json в Дополнительные ссылки для менеджера плат (Additional Board Manager URLs field).
Вы можете использовать несколько URL'ов, разделенных запятыми;
6. Окрываем окно с менеджером плат Иструменты->Плата:*->Менеджер плат (Tools->Board:*->Board Manager) и устанавливаем платформу esp8266
7. Копируем директорию sonoff в Ваш sketchfolder посмотреть путь можно в Файл->Настройки->Размещение папки скетчей (File->Preferences)
8. Скачиваем и распаковываем pubsubclient MQTT library в директорию portable\sketchbook\libraries. Переименовываем pubsubclient-master в pubsubclient и редактируем следующий файл pubsubclient\src\PubSubClient.h
Изменяем MQTT_MAX_PACKET_SIZE с 128 на 1024
Изменяем MQTT_KEEPALIVE с 15 на 120
Для sonoff выбираем Инструменты->Плата:*->Generic ESP8266 Module (Tools->Board:*->Generic ESP8266 Module) и устанавливаем следующие настройки:
Открываем sonoff.ino и изменяем параметры подключения к Wifi и MQTT серверу.
Название вашего проекта, если планируется использовать множество sonoff устройств, то меняем, например на sonoff1, sonoff2… sonoffx
…
Задаем имя и пароль от вашего Wifi-роутреа\точки, к которой sonoff будет подключаться.
Вывод полезной для отладки информации в SERIAL
«Прошивка по воздуху» меняем адрес на свой HTTP сервер и копируем файлы, если не требуется можно оставить, как есть по умолчанию.
Адрес MQTT сервера и порт
Пользователь и пароль для подключения к MQTT серверу
Перед подключением USB-UART к порту компьютера, зажмите и удерживайте кнопку на sonoff, после подключения нажмите кнопку Загрузка в Arduino IDE, когда на экране появится сообщение о прошивке и появится прогресс бар, кнопку можно отпускать.
Открываем монитор порта
Проверим работу sonoff с MQTT сервером, запускаем любой mqtt клиент на компьютере (MQTT Spy) или мобильном телефоне (MyMQTT), подключаемся и публикуем запись off в топик cmnd/sonoff/LIGHT
В Мониторе COM-порта:
В топике stat/sonoff/LIGHT должно появится сообщение с контентом Off.
Полный список команд, которые поддерживает данная прошивка, можно найти на странице проекта в Github
В качестве примера подключим радиопередатчик RF433 и добавим в прошивку, новую команду LIVOLO для управления выключателями.
Список свободных и легкодоступных GPIO:
— GPIO1 — TX UART
— GPIO3 — RX UART
— GPIO12 — Красный светодиод
— GPIO13 — Зелёный светодиод, смело можно подключить второе реле
— GPIO14 — Пятый вывод возле UART (Только в новых ревизиях платы)
У моей sonoff, есть выведенный GPIO14, поэтому я буду подключать к нему. Очень интересный вариант, использовать отверстия под радиоприемник для подключения радиопередатчика, кому будет интересно оставляю ссылку.
Для отправки команд выключателям Livolo воспользуемся соответствующей библиотекой. Скачиваем и копируем папку в libraries.
Внимание код представлен для ознакомления и очень далек от идеала, например вместо substring, лучше использовать строковые функции C++.
Прошиваем и публикуем запись на MQTT сервере в топик cmnd/sonoff/LIVOLO с данными
RF433 передатчик, должен передать идентификатор и код по радиоканалу, можно подключать множество устройств меняя значения RemoteID и KeyCode.
Заключение
Рассказывать подробно о самом решении для управления силовой нагрузкой от Ited под названием Sonoff RF, я не вижу смысла, так как на mysku уже существует подобный обзор от пользователя spc. В данном обзоре, мне бы хотелось затронуть практическую сторону на примере реализации управления выключателем Livolo. Если кому-то интересна подобная тематика, прошу под кат.
Хотелось бы сразу отметить, что, не смотря на очевидные плюсы sonoff, типа готового аппаратно-программного решения в корпусе и народного Wifi модуля ESP8266 внутри, есть очень жирный минус — это закрытая прошивка и привязка к облачным сервисам Itead. Не нужно быть специалистом в области информационной безопасности, чтобы осознать какие могут возникнуть риски. Чтобы не изобретать велосипед и упростить выполнение поставленной задачи, было решено использовать прошивку с открытым исходным кодом от Theo Arends — Sonoff-MQTT-OTA-Arduino.
Что нам потребуется
+ Itead Sonoff;± Любой USB-UART переходник FTDI32RL, Cp2102 и т.д.*;
+ Wifi роутер в режиме точки доступа;
+ Локальный или облачный MQTT сервер;
± Паяльник, припой, флюс, штырьковый разъем и некоторые навыки пайки*.
* Можно использовать Веб-сервер и прошивку по воздуху (OTA: Over-the-air)
Подключаем sonoff через USB-UART
Для прошивки по USB-UART припаиваем штырьки
Внимание! Перед прошивкой по USB-UART отключите AC питания sonoff
У меня в наличии есть только переходник, основанный на микросхеме CP2102 от SILICON LABS, обзор на который уже были на mysku.
Sonoff RX -> TX UART
Sonoff TX -> RX UART
Sonoff VCC -> 3.3V UART
Sonoff GND -> GND UART
Установка и настройка ESP8266 Arduino IDE
Внимание!!! Возможны проблемы при компилации скетчей на Windows XP единственное известное мне, работающее решение — это использование Portable версии Arduino IDE.Пример ошибки
c:\documents and settings\USERNAME\local settings\application data\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include\c++\4.8.2\bits\stl_algobase.h:59:28: fatal error: bits/c++config.h: No such file or directory
#include <bits/c++config.h>
compilation terminated.
exit status 1
Порядок действий
1. Скачиваем архив с Arduino 1.6.8+ с официального веб-сайта
2. Распаковываем архив в папку, например ESP8266_Arduino
3. Создаем в папке ESP8266_Arduino, папку Portable
4. Запускаем Arduino и открываем окно с настройками Файл->Настройки (File->Preferences);
5. Вводим arduino.esp8266.com/stable/package_esp8266com_index.json в Дополнительные ссылки для менеджера плат (Additional Board Manager URLs field).
Вы можете использовать несколько URL'ов, разделенных запятыми;
6. Окрываем окно с менеджером плат Иструменты->Плата:*->Менеджер плат (Tools->Board:*->Board Manager) и устанавливаем платформу esp8266
7. Копируем директорию sonoff в Ваш sketchfolder посмотреть путь можно в Файл->Настройки->Размещение папки скетчей (File->Preferences)
8. Скачиваем и распаковываем pubsubclient MQTT library в директорию portable\sketchbook\libraries. Переименовываем pubsubclient-master в pubsubclient и редактируем следующий файл pubsubclient\src\PubSubClient.h
Изменяем MQTT_MAX_PACKET_SIZE с 128 на 1024
Изменяем MQTT_KEEPALIVE с 15 на 120
Компиляция и загрузка прошивки
Для sonoff выбираем Инструменты->Плата:*->Generic ESP8266 Module (Tools->Board:*->Generic ESP8266 Module) и устанавливаем следующие настройки:
Flash Mode: QIO
Flash Frequency: 40MHz
Upload Using: Serial
CPU Frequency: 80MHz
Flash Size: 1M (64K SPIFFS)
Debug Port: Disabled
Debug Level: None
Reset Method: ck
Upload Speed: 115200
Port: Ваш COM-порт к которому подключен sonoff
Открываем sonoff.ino и изменяем параметры подключения к Wifi и MQTT серверу.
Название вашего проекта, если планируется использовать множество sonoff устройств, то меняем, например на sonoff1, sonoff2… sonoffx
#define PROJECT "sonoff"
…
Задаем имя и пароль от вашего Wifi-роутреа\точки, к которой sonoff будет подключаться.
// Wifi
#define STA_SSID ""
#define STA_PASS ""
Вывод полезной для отладки информации в SERIAL
#define SERIAL_LOG_LEVEL LOG_LEVEL_DEBUG_MORE
«Прошивка по воздуху» меняем адрес на свой HTTP сервер и копируем файлы, если не требуется можно оставить, как есть по умолчанию.
// Ota
#if (ARDUINO >= 168)
#define OTA_URL "http://192.168.0.102:80/api/arduino/"PROJECT".ino.bin"
#else
#define OTA_URL "http://192.168.0.102:80/api/arduino/"PROJECT".cpp.bin"
#endif
Адрес MQTT сервера и порт
// MQTT
#define MQTT_HOST "192.168.0.102"
#define MQTT_PORT 1883
...
Пользователь и пароль для подключения к MQTT серверу
#define MQTT_USER ""
#define MQTT_PASS ""
Перед подключением USB-UART к порту компьютера, зажмите и удерживайте кнопку на sonoff, после подключения нажмите кнопку Загрузка в Arduino IDE, когда на экране появится сообщение о прошивке и появится прогресс бар, кнопку можно отпускать.
Использование
Проверим работоспособность, кратковременно нажимаем кнопку на sonoff, светодиод должен моргнуть дважды и устройство отправит сообщение «on» в топик stat/sonoff/POWERОткрываем монитор порта
RTC: sntp 0, Thu Jan 01 00:00:00 1970
APP: Multipress 1
MQTT: sonoff/LIGHT = 2
MQTT: Receive topic cmnd/sonoff/LIGHT, data 2
MQTT: DataCb Topic sonoff, Group 0, Type LIGHT, data 2 (2)
MQTT: sonoff/LIGHT = On
Config: Saved configuration to flash at F9 and count 38
Проверим работу sonoff с MQTT сервером, запускаем любой mqtt клиент на компьютере (MQTT Spy) или мобильном телефоне (MyMQTT), подключаемся и публикуем запись off в топик cmnd/sonoff/LIGHT
В Мониторе COM-порта:
Wifi: Check connection
MQTT: Receive topic cmnd/sonoff/LIGHT, data off
MQTT: DataCb Topic sonoff, Group 0, Type LIGHT, data off (OFF)
MQTT: sonoff/LIGHT = Off
RTC: sntp 0, Thu Jan 01 00:00:00 1970
В топике stat/sonoff/LIGHT должно появится сообщение с контентом Off.
Полный список команд, которые поддерживает данная прошивка, можно найти на странице проекта в Github
Доработка
У ESP8266 в sonoff есть свободные GPIO, которые можно использовать для подключения дополнительных модулей (Радиопередатчик, различные датчики и.т.д.).В качестве примера подключим радиопередатчик RF433 и добавим в прошивку, новую команду LIVOLO для управления выключателями.
Список свободных и легкодоступных GPIO:
— GPIO1 — TX UART
— GPIO3 — RX UART
— GPIO12 — Красный светодиод
— GPIO13 — Зелёный светодиод, смело можно подключить второе реле
— GPIO14 — Пятый вывод возле UART (Только в новых ревизиях платы)
У моей sonoff, есть выведенный GPIO14, поэтому я буду подключать к нему. Очень интересный вариант, использовать отверстия под радиоприемник для подключения радиопередатчика, кому будет интересно оставляю ссылку.
Для отправки команд выключателям Livolo воспользуемся соответствующей библиотекой. Скачиваем и копируем папку в libraries.
Внимание код представлен для ознакомления и очень далек от идеала, например вместо substring, лучше использовать строковые функции C++.
Исправленная прошивка с добавлением команды Livolo.
/*
* Sonoff and Wkaku by Theo Arends
*
* ESP-12F connections (Wkaku)
* 3V3 5V
* |-------------------| |---------|
* | | ------------- | |1N4001| |Relay|
* | | -| Tx |- | |---------|
* | | -| Rx |- | /
* |-------------------| En |- |---| 1k|------|< BC547B
* | | -| |- \
* | | -| IO00 |------|Switch|------|
* | ---| IO12 IO02 |--- LED (ESP-12E/F) |
* |---| 1k|---|LED|---| IO13 IO15 |------|10k|---------|
* |-------------------| Vcc Gnd |--------------------|
* ------------- |
* | | | | | | Gnd
*/
#define PROJECT "sonoff"
#define VERSION 0x01000C00 // 1.0.12
#define CFG_HOLDER 0x20160520 // Change this value to load default configurations
// Wifi
#define STA_SSID ""
#define STA_PASS ""
#define WIFI_HOSTNAME "esp-%06x-%s"
// Syslog
#define LOG_LEVEL_NONE 0
#define LOG_LEVEL_ERROR 1
#define LOG_LEVEL_INFO 2
#define LOG_LEVEL_DEBUG 3
#define LOG_LEVEL_DEBUG_MORE 4
#define SYS_LOG_HOST "sidnas2"
#define SYS_LOG_PORT 514
#define SYS_LOG_LEVEL LOG_LEVEL_NONE
#define SERIAL_LOG_LEVEL LOG_LEVEL_DEBUG_MORE
// Ota
#if (ARDUINO >= 168)
#define OTA_URL "http://192.168.0.102:80/api/arduino/"PROJECT".ino.bin"
#else
#define OTA_URL "http://192.168.0.102:80/api/arduino/"PROJECT".cpp.bin"
#endif
// MQTT
#define MQTT_HOST ""
#define MQTT_PORT 1883
#define MQTT_CLIENT_ID "DVES_%06X" // Also fall back topic using Chip Id = last 6 characters of MAC address
#define MQTT_USER ""
#define MQTT_PASS ""
#define SUB_PREFIX "cmnd"
#define PUB_PREFIX "stat"
#define MQTT_GRPTOPIC PROJECT"s" // Group topic
#define MQTT_TOPIC PROJECT
// Application
#define MQTT_SUBTOPIC "POWER"
#define APP_TIMEZONE 1 // +1 hour (Amsterdam)
#define APP_POWER 0 // Saved power state Off
// End of user defines **************************************************************************
#define SERIAL_IO // Enable serial command line
#define STATES 10 // loops per second
#define MQTT_RETRY_SECS 10 // Seconds to retry MQTT connection
//#define LED_PIN 2 // GPIO 2 = Blue Led (0 = On, 1 = Off) - ESP-12
#define LED_PIN 13 // GPIO 13 = Green Led (0 = On, 1 = Off) - Sonoff
//#define LED_PIN 16 // NodeMCU
#define REL_PIN 12 // GPIO 12 = Red Led and Relay (0 = Off, 1 = On)
#define KEY_PIN 0 // GPIO 00 = Button
#define PRESSED 0
#define NOT_PRESSED 1
#define WIFI_STATUS 0
#define WIFI_SMARTCONFIG 1
#ifdef DEBUG_ESP_PORT
#define DEBUG_MSG(...) DEBUG_ESP_PORT.printf( __VA_ARGS__ )
#else
#define DEBUG_MSG(...)
#endif
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>
#include <PubSubClient.h>
#include <livolo.h>
extern "C" uint32_t _SPIFFS_start;
struct SYSCFG {
unsigned long cfg_holder;
unsigned long saveFlag;
unsigned long version;
byte seriallog_level;
byte syslog_level;
char syslog_host[32];
char sta_ssid[32];
char sta_pwd[64];
char otaUrl[80];
char mqtt_host[32];
char mqtt_grptopic[32];
char mqtt_topic[32];
char mqtt_topic2[32];
char mqtt_subtopic[32];
int8_t timezone;
uint8_t power;
} sysCfg;
struct TIME_T {
uint8_t Second;
uint8_t Minute;
uint8_t Hour;
uint8_t Wday; // day of week, sunday is day 1
uint8_t Day;
uint8_t Month;
char MonthName[4];
uint16_t Year;
unsigned long Valid;
} rtcTime;
char Version[16];
char Hostname[32];
uint8_t mqttcounter = 0;
unsigned long timerxs = 0, timersec = 0;
int state = 0;
int otaflag = 0;
int restartflag = 0;
int smartconfigflag = 0;
int heartbeatflag = 0;
int heartbeat = 0;
WiFiClient espClient;
PubSubClient mqttClient(espClient);
WiFiUDP portUDP; // syslog
int blinks = 1;
uint8_t blinkstate = 1;
uint8_t lastbutton = NOT_PRESSED;
uint8_t holdcount = 0;
uint8_t multiwindow = 0;
uint8_t multipress = 0;
/*********************************************************************************************\
* Livolo
\*********************************************************************************************/
Livolo livolo(14); // transmitter connected to pin #14
void sendCommand(String str) {
String sRemoteID, sKeyCode;
int curIndex = str.indexOf(',');
sRemoteID = str.substring(0, curIndex);
sKeyCode = str.substring(curIndex +1, -1);
if(sRemoteID.startsWith("RemoteID:")) {
sRemoteID = sRemoteID.substring(sRemoteID.lastIndexOf(':') +1,-1);
}
if(sKeyCode.startsWith("KeyCode:")) {
sKeyCode = sKeyCode.substring(sKeyCode.lastIndexOf(':') +1,-1);
}
int iRemoteID = sRemoteID.toInt();
int iKeyCode = sKeyCode.toInt();
livolo.sendButton(sRemoteID.toInt(), sKeyCode.toInt());
}
/*********************************************************************************************\
* Syslog
\*********************************************************************************************/
void syslog(const char *message)
{
char mess[168], str[200];
portUDP.beginPacket(sysCfg.syslog_host, SYS_LOG_PORT);
strncpy(mess, message, 167);
mess[168] = 0;
sprintf_P(str, PSTR("%s %s"), Hostname, mess);
portUDP.write(str);
portUDP.endPacket();
}
void addLog(byte loglevel, const char *line)
{
DEBUG_MSG("DebugMsg %s\n", line);
#ifdef SERIAL_IO
if (loglevel <= sysCfg.seriallog_level) Serial.println(line);
#endif
if ((WiFi.status() == WL_CONNECTED) && (loglevel <= sysCfg.syslog_level)) syslog(line);
}
void addLog(byte loglevel, String& string)
{
addLog(loglevel, string.c_str());
}
/********************************************************************************************/
void mqtt_publish(const char* topic, const char* data)
{
char log[300];
mqttClient.publish(topic, data);
sprintf_P(log, PSTR("MQTT: %s = %s"), strchr(topic,'/')+1, data); // Skip topic prefix
addLog(LOG_LEVEL_INFO, log);
mqttClient.loop(); // Solve LmacRxBlk:1 messages
blinks++;
}
void mqtt_connected()
{
char stopic[40], svalue[40];
sprintf_P(stopic, PSTR("%s/%s/#"), SUB_PREFIX, sysCfg.mqtt_topic);
mqttClient.subscribe(stopic);
mqttClient.loop(); // Solve LmacRxBlk:1 messages
sprintf_P(stopic, PSTR("%s/%s/#"), SUB_PREFIX, sysCfg.mqtt_grptopic);
mqttClient.subscribe(stopic);
mqttClient.loop(); // Solve LmacRxBlk:1 messages
sprintf_P(stopic, PSTR("%s/"MQTT_CLIENT_ID"/#"), SUB_PREFIX, ESP.getChipId()); // Fall back topic
mqttClient.subscribe(stopic);
mqttClient.loop(); // Solve LmacRxBlk:1 messages
sprintf_P(stopic, PSTR("%s/%s/NAME"), PUB_PREFIX, sysCfg.mqtt_topic);
sprintf_P(svalue, PSTR("Sonoff switch"));
mqtt_publish(stopic, svalue);
sprintf_P(stopic, PSTR("%s/%s/VERSION"), PUB_PREFIX, sysCfg.mqtt_topic);
sprintf_P(svalue, PSTR("%s"), Version);
mqtt_publish(stopic, svalue);
sprintf_P(stopic, PSTR("%s/%s/FALLBACKTOPIC"), PUB_PREFIX, sysCfg.mqtt_topic);
sprintf_P(svalue, PSTR(MQTT_CLIENT_ID), ESP.getChipId());
mqtt_publish(stopic, svalue);
}
void mqtt_reconnect()
{
char stopic[40], svalue[40], log[80];
mqttcounter = MQTT_RETRY_SECS;
addLog(LOG_LEVEL_INFO, "MQTT: Attempting connection");
sprintf(svalue, MQTT_CLIENT_ID, ESP.getChipId());
sprintf_P(stopic, PSTR("%s/%s/lwt"), PUB_PREFIX, sysCfg.mqtt_topic);
if (mqttClient.connect(svalue, MQTT_USER, MQTT_PASS, stopic, 0, 0, "offline")) {
addLog(LOG_LEVEL_INFO, "MQTT: Connected");
mqttcounter = 0;
mqtt_connected();
} else {
sprintf_P(log, PSTR("MQTT: Connect failed, rc %d. Retry in %d seconds"), mqttClient.state(), mqttcounter);
addLog(LOG_LEVEL_DEBUG, log);
}
}
void mqttDataCb(char* topic, byte* data, unsigned int data_len)
{
int i, grpflg = 0;
char *str, *p, *mtopic = NULL, *type = NULL;
char stopic[40], svalue[240];
int topic_len = strlen(topic);
char topicBuf[topic_len +1];
char dataBuf[data_len +1];
char dataBufUc[data_len +1];
memcpy(topicBuf, topic, topic_len);
topicBuf[topic_len] = 0;
memcpy(dataBuf, data, data_len);
dataBuf[data_len] = 0;
sprintf_P(svalue, PSTR("MQTT: Receive topic %s, data %s"), topicBuf, dataBuf);
addLog(LOG_LEVEL_DEBUG, svalue);
i = 0;
for (str = strtok_r(topicBuf, "/", &p); str && i < 3; str = strtok_r(NULL, "/", &p)) {
switch (i++) {
case 0: // cmnd
break;
case 1: // Topic / GroupTopic / DVES_123456
mtopic = str;
break;
case 2: // Text
type = str;
}
}
if (!strcmp(mtopic, sysCfg.mqtt_grptopic)) grpflg = 1;
if (type != NULL) for(i = 0; i < strlen(type); i++) type[i] = toupper(type[i]);
for(i = 0; i <= data_len; i++) dataBufUc[i] = toupper(dataBuf[i]);
sprintf_P(svalue, PSTR("MQTT: DataCb Topic %s, Group %d, Type %s, data %s (%s)"),
mtopic, grpflg, type, dataBuf, dataBufUc);
addLog(LOG_LEVEL_DEBUG, svalue);
if (type != NULL) {
sprintf_P(stopic, PSTR("%s/%s/%s"), PUB_PREFIX, sysCfg.mqtt_topic, type);
strcpy(svalue, "Error");
uint16_t payload = atoi(dataBuf);
if (!strcmp(dataBufUc,"OFF")) payload = 0;
if (!strcmp(dataBufUc,"ON")) payload = 1;
if (!strcmp(dataBufUc,"TOGGLE")) payload = 2;
if (!strcmp(type,"STATUS")) {
switch (payload) {
case 1:
sprintf_P(svalue, PSTR("%s, "MQTT_CLIENT_ID", %s, %s, %d, %d"),
sysCfg.mqtt_grptopic, ESP.getChipId(), sysCfg.otaUrl, sysCfg.mqtt_host, heartbeat, sysCfg.saveFlag);
break;
case 2:
sprintf_P(svalue, PSTR("Version %s, Boot %d, SDK %s"),
Version, ESP.getBootVersion(), ESP.getSdkVersion());
break;
case 3:
sprintf_P(svalue, PSTR("Seriallog %d, Syslog %d, LogHost %s, SSId %s, Password %s"),
sysCfg.seriallog_level, sysCfg.syslog_level, sysCfg.syslog_host, sysCfg.sta_ssid, sysCfg.sta_pwd);
break;
case 4:
sprintf_P(svalue, PSTR("Sketch size %d, Free %d (Heap %d), Spiffs start %d, Flash size %d (%d)"),
ESP.getSketchSize(), ESP.getFreeSketchSpace(), ESP.getFreeHeap(), (uint32_t)&_SPIFFS_start - 0x40200000,
ESP.getFlashChipRealSize(), ESP.getFlashChipSize());
break;
case 5: {
IPAddress ip = WiFi.localIP();
IPAddress gw = WiFi.gatewayIP();
IPAddress nm = WiFi.subnetMask();
sprintf_P(svalue, PSTR("Hostname %s, IP %u.%u.%u.%u, Gateway %u.%u.%u.%u, Subnetmask %u.%u.%u.%u"),
Hostname, ip[0], ip[1], ip[2], ip[3], gw[0], gw[1], gw[2], gw[3], nm[0], nm[1], nm[2], nm[3]);
break;
}
default:
sprintf_P(svalue, PSTR("%s, %s, %s, %s, %d, %d"),
Version, sysCfg.mqtt_topic, sysCfg.mqtt_topic2, sysCfg.mqtt_subtopic, sysCfg.power, sysCfg.timezone);
}
}
else if (!grpflg && !strcmp(type,"UPGRADE")) {
if ((data_len > 0) && (payload == 1)) {
otaflag = 3;
sprintf_P(svalue, PSTR("Upgrade %s"), Version);
}
else
sprintf_P(svalue, PSTR("1 to upgrade"));
}
else if (!grpflg && !strcmp(type,"OTAURL")) {
if ((data_len > 0) && (data_len < 80))
strcpy(sysCfg.otaUrl, (payload == 1) ? OTA_URL : dataBuf);
sprintf_P(svalue, PSTR("%s"), sysCfg.otaUrl);
}
else if (!strcmp(type,"SERIALLOG")) {
if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_DEBUG_MORE)) {
sysCfg.seriallog_level = payload;
}
sprintf_P(svalue, PSTR("%d"), sysCfg.seriallog_level);
}
else if (!strcmp(type,"SYSLOG")) {
if ((data_len > 0) && (payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_DEBUG_MORE)) {
sysCfg.syslog_level = payload;
}
sprintf_P(svalue, PSTR("%d"), sysCfg.syslog_level);
}
else if (!strcmp(type,"LOGHOST")) {
if ((data_len > 0) && (data_len < 32)) {
strcpy(sysCfg.syslog_host, (payload == 1) ? SYS_LOG_HOST : dataBuf);
restartflag = 2;
}
sprintf_P(svalue, PSTR("%s"), sysCfg.syslog_host);
}
else if (!grpflg && !strcmp(type,"SSID")) {
if ((data_len > 0) && (data_len < 32)) {
strcpy(sysCfg.sta_ssid, (payload == 1) ? STA_SSID : dataBuf);
restartflag = 2;
}
sprintf_P(svalue, PSTR("%s"), sysCfg.sta_ssid);
}
else if (!grpflg && !strcmp(type,"PASSWORD")) {
if ((data_len > 0) && (data_len < 64)) {
strcpy(sysCfg.sta_pwd, (payload == 1) ? STA_PASS : dataBuf);
restartflag = 2;
}
sprintf_P(svalue, PSTR("%s"), sysCfg.sta_pwd);
}
else if (!grpflg && !strcmp(type,"MQTTHOST")) {
if ((data_len > 0) && (data_len < 32)) {
strcpy(sysCfg.mqtt_host, (payload == 1) ? MQTT_HOST : dataBuf);
restartflag = 2;
}
sprintf_P(svalue, PSTR("%s"), sysCfg.mqtt_host);
}
else if (!strcmp(type,"GROUPTOPIC")) {
if ((data_len > 0) && (data_len < 32)) {
for(i = 0; i <= data_len; i++)
if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
sprintf_P(svalue, PSTR(MQTT_CLIENT_ID), ESP.getChipId());
if (!strcmp(dataBuf, svalue)) payload = 1;
strcpy(sysCfg.mqtt_grptopic, (payload == 1) ? MQTT_GRPTOPIC : dataBuf);
restartflag = 2;
}
sprintf_P(svalue, PSTR("%s"), sysCfg.mqtt_grptopic);
}
else if (!grpflg && !strcmp(type,"TOPIC")) {
if ((data_len > 0) && (data_len < 32)) {
for(i = 0; i <= data_len; i++)
if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
sprintf_P(svalue, PSTR(MQTT_CLIENT_ID), ESP.getChipId());
if (!strcmp(dataBuf, svalue)) payload = 1;
strcpy(sysCfg.mqtt_topic, (payload == 1) ? MQTT_TOPIC : dataBuf);
restartflag = 2;
}
sprintf_P(svalue, PSTR("%s"), sysCfg.mqtt_topic);
}
else if (!grpflg && !strcmp(type,"BUTTONTOPIC")) {
if ((data_len > 0) && (data_len < 32)) {
for(i = 0; i <= data_len; i++)
if ((dataBuf[i] == '/') || (dataBuf[i] == '+') || (dataBuf[i] == '#')) dataBuf[i] = '_';
sprintf_P(svalue, PSTR(MQTT_CLIENT_ID), ESP.getChipId());
if (!strcmp(dataBuf, svalue)) payload = 1;
strcpy(sysCfg.mqtt_topic2, (payload == 1) ? MQTT_TOPIC : dataBuf);
}
sprintf_P(svalue, PSTR("%s"), sysCfg.mqtt_topic2);
}
else if (!grpflg && !strcmp(type,"SMARTCONFIG")) {
if ((data_len > 0) && (payload == 1)) {
blinks = 1999;
smartconfigflag = 1;
sprintf_P(svalue, PSTR("Smartconfig started"));
} else
sprintf_P(svalue, PSTR("1 to start smartconfig"));
}
else if (!grpflg && !strcmp(type,"RESTART")) {
if ((data_len > 0) && (payload == 1)) {
restartflag = 2;
sprintf_P(svalue, PSTR("Restarting"));
} else
sprintf_P(svalue, PSTR("1 to restart"));
}
else if (!grpflg && !strcmp(type,"RESET")) {
switch (payload) {
case 1:
restartflag = 11;
sprintf_P(svalue, PSTR("Reset and Restarting"));
break;
case 2:
restartflag = 12;
sprintf_P(svalue, PSTR("Erase, Reset and Restarting"));
break;
default:
sprintf_P(svalue, PSTR("1 to reset"));
}
}
else if (!grpflg && !strcmp(type,"LIVOLO")) {
sprintf(sysCfg.mqtt_subtopic, "%s", type);
if ((data_len > 0) && (data_len < 32)) {
sendCommand((const char*)dataBuf);
sprintf_P(svalue, PSTR("Livolo send"));
} else
sprintf_P(svalue, PSTR("Livolo data wrong"));
}
if (!strcmp(type,"TIMEZONE")) {
if ((data_len > 0) && (payload >= -12) && (payload <= 12)) {
sysCfg.timezone = payload;
rtc_timezone(sysCfg.timezone);
}
sprintf_P(svalue, PSTR("%d"), sysCfg.timezone);
}
else if ((!strcmp(type,"LIGHT")) || (!strcmp(type,"POWER")) || (!strcmp(type,"LIVOLO"))) {
sprintf(sysCfg.mqtt_subtopic, "%s", type);
if ((data_len > 0) && (payload >= 0) && (payload <= 2)) {
switch (payload) {
case 0: // Off
case 1: // On
sysCfg.power = payload;
break;
case 2: // Toggle
sysCfg.power ^= 1;
break;
}
digitalWrite(REL_PIN, sysCfg.power);
}
strcpy(svalue, (sysCfg.power) ? "On" : "Off");
}
else {
type = NULL;
}
if (type == NULL) {
blinks = 1;
sprintf_P(stopic, PSTR("%s/%s/SYNTAX"), PUB_PREFIX, sysCfg.mqtt_topic);
if (!grpflg)
strcpy_P(svalue, PSTR("Status, Upgrade, Otaurl, Restart, Reset, Smartconfig, Seriallog, Syslog, LogHost, SSId, Password, MqttHost, GroupTopic, Topic, ButtonTopic, Timezone, Light, Power, Livolo"));
else
strcpy_P(svalue, PSTR("Status, GroupTopic, Timezone, Light, Power"));
}
mqtt_publish(stopic, svalue);
}
}
void send_button(char *cmnd)
{
char stopic[128], svalue[128];
char *token;
token = strtok(cmnd, " ");
if ((!strcmp(token,"light")) || (!strcmp(token,"power"))) strcpy(token, sysCfg.mqtt_subtopic);
sprintf_P(stopic, PSTR("%s/%s/%s"), SUB_PREFIX, sysCfg.mqtt_topic2, token);
token = strtok(NULL, "");
sprintf_P(svalue, PSTR("%s"), (token == NULL) ? "" : token);
mqtt_publish(stopic, svalue);
}
void do_cmnd(char *cmnd)
{
char stopic[128], svalue[128];
char *token;
token = strtok(cmnd, " ");
sprintf_P(stopic, PSTR("%s/%s/%s"), SUB_PREFIX, sysCfg.mqtt_topic, token);
token = strtok(NULL, "");
sprintf_P(svalue, PSTR("%s"), (token == NULL) ? "" : token);
mqttDataCb(stopic, (byte*)svalue, strlen(svalue));
}
void send_power()
{
char stopic[40], svalue[20];
sprintf_P(stopic, PSTR("%s/%s/%s"), PUB_PREFIX, sysCfg.mqtt_topic, sysCfg.mqtt_subtopic);
strcpy(svalue, (sysCfg.power == 0) ? "Off" : "On");
mqtt_publish(stopic, svalue);
}
void send_updateStatus(const char* svalue)
{
char stopic[40];
sprintf_P(stopic, PSTR("%s/%s/UPGRADE"), PUB_PREFIX, sysCfg.mqtt_topic);
mqtt_publish(stopic, svalue);
}
void every_second()
{
char stopic[40], svalue[20];
if (heartbeatflag) {
heartbeatflag = 0;
heartbeat++;
sprintf_P(stopic, PSTR("%s/%s/HEARTBEAT"), PUB_PREFIX, sysCfg.mqtt_topic);
sprintf_P(svalue, PSTR("%d"), heartbeat);
mqtt_publish(stopic, svalue);
}
}
const char commands[6][14] PROGMEM = {
{"reset 1"}, // Hold button for more than 4 seconds
{"light 2"}, // Press button once
{"light 2"}, // Press button twice
{"smartconfig 1"}, // Press button three times
{"upgrade 1"}, // Press button four times
{"restart 1"}}; // Press button five times
void stateloop()
{
uint8_t button;
char scmnd[20], log[30];
timerxs = millis() + (1000 / STATES);
state++;
if (state == STATES) { // Every second
state = 0;
every_second();
}
button = digitalRead(KEY_PIN);
if ((button == PRESSED) && (lastbutton == NOT_PRESSED)) {
multipress = (multiwindow) ? multipress +1 : 1;
sprintf_P(log, PSTR("APP: Multipress %d"), multipress);
addLog(LOG_LEVEL_DEBUG, log);
blinks = 1;
multiwindow = STATES /2; // 1/2 second multi press window
}
lastbutton = button;
if (button == NOT_PRESSED) {
holdcount = 0;
} else {
holdcount++;
if (holdcount == (STATES *4)) { // 4 seconds button hold
strcpy_P(scmnd, commands[0]);
multipress = 0;
do_cmnd(scmnd);
}
}
if (multiwindow) {
multiwindow--;
} else {
if ((!holdcount) && (multipress >= 1) && (multipress <= 5)) {
strcpy_P(scmnd, commands[multipress]);
if (strcmp(sysCfg.mqtt_topic2,"0") && (multipress == 1) && mqttClient.connected())
send_button(scmnd); // Execute command via MQTT using ButtonTopic to sync external clients
else
do_cmnd(scmnd); // Execute command internally
multipress = 0;
}
}
if ((blinks || restartflag || otaflag) && (!(state % ((STATES/10)*2)))) {
if (restartflag || otaflag)
blinkstate = 0; // Stay lit
else
blinkstate ^= 1; // Blink
digitalWrite(LED_PIN, blinkstate);
if (blinkstate) blinks--;
}
switch (state) {
case (STATES/10)*2:
if (otaflag) {
otaflag--;
if (otaflag <= 0) {
otaflag = 255;
ESPhttpUpdate.update(sysCfg.otaUrl);
send_updateStatus(ESPhttpUpdate.getLastErrorString().c_str());
restartflag = 2;
}
}
break;
case (STATES/10)*4:
CFG_Save();
if (restartflag) {
if (restartflag == 11) {
CFG_Default();
restartflag = 2;
}
if (restartflag == 12) {
CFG_Erase();
restartflag = 1;
}
restartflag--;
if (restartflag <= 0) ESP.restart();
}
break;
case (STATES/10)*6:
if (smartconfigflag) {
smartconfigflag = 0;
WIFI_Check(WIFI_SMARTCONFIG);
} else {
WIFI_Check(WIFI_STATUS);
}
break;
case (STATES/10)*8:
if ((WiFi.status() == WL_CONNECTED) && (!mqttClient.connected())) {
if (!mqttcounter)
mqtt_reconnect();
else
mqttcounter--;
}
break;
}
}
#ifdef SERIAL_IO
#define INPUT_BUFFER_SIZE 128
byte SerialInByte;
int SerialInByteCounter = 0;
char serialInBuf[INPUT_BUFFER_SIZE + 2];
void serial()
{
while (Serial.available())
{
yield();
SerialInByte = Serial.read();
if (SerialInByte > 127) // binary data...
{
Serial.flush();
SerialInByteCounter = 0;
return;
}
if (isprint(SerialInByte))
{
if (SerialInByteCounter < INPUT_BUFFER_SIZE) // add char to string if it still fits
serialInBuf[SerialInByteCounter++] = SerialInByte;
else
SerialInByteCounter = 0;
}
if (SerialInByte == '\n')
{
serialInBuf[SerialInByteCounter] = 0; // serial data completed
Serial.println(serialInBuf);
SerialInByteCounter = 0;
do_cmnd(serialInBuf);
}
}
}
#endif
void setup()
{
char log[128];
Serial.begin(115200);
delay(10);
Serial.println();
sprintf_P(Version, PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff);
if (VERSION & 0x1f) {
byte idx = strlen(Version);
Version[idx] = 96 + (VERSION & 0x1f);
Version[idx +1] = 0;
}
CFG_Load();
if (sysCfg.version != VERSION) { // Fix version dependent changes
sysCfg.version = VERSION;
}
sprintf_P(Hostname, PSTR(WIFI_HOSTNAME), ESP.getChipId(), sysCfg.mqtt_topic);
WIFI_Connect(Hostname);
mqttClient.setServer(sysCfg.mqtt_host, MQTT_PORT);
mqttClient.setCallback(mqttDataCb);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, blinkstate);
pinMode(REL_PIN, OUTPUT);
digitalWrite(REL_PIN, sysCfg.power);
pinMode(KEY_PIN, INPUT_PULLUP);
rtc_init(sysCfg.timezone);
sprintf_P(log, PSTR("App: Project %s (Topic %s, Fallback "MQTT_CLIENT_ID", GroupTopic %s) Version %s"),
PROJECT, sysCfg.mqtt_topic, ESP.getChipId(), sysCfg.mqtt_grptopic, Version);
addLog(LOG_LEVEL_INFO, log);
}
void loop()
{
if (millis() >= timersec) {
timersec = millis() + 1000;
rtc_second();
if ((rtcTime.Minute == 2) && (rtcTime.Second == 30)) heartbeatflag = 1;
}
if (millis() >= timerxs) stateloop();
mqttClient.loop();
#ifdef SERIAL_IO
if (Serial.available()) serial();
#endif
yield();
}
Прошиваем и публикуем запись на MQTT сервере в топик cmnd/sonoff/LIVOLO с данными
RemoteID:23801,KeyCode:8
RF433 передатчик, должен передать идентификатор и код по радиоканалу, можно подключать множество устройств меняя значения RemoteID и KeyCode.
ЗаключениеУстройство у Itead получилось интересное и достаточно простое в использовании, к тому же в сообществах развиваются различные доработки и альтернативные прошивки.
СсылкиОфициальная WIKI ITEAD
Альтернативная прошивка Sonoff-MQTT-OTA-Arduino
Библиотека Livolo для Arduino
Подключение ESP8266 к MQTT серверу Mosquitto
Рubsubclient
Модернизация sonoff
Кошка по традиции
СсылкиОфициальная WIKI ITEAD
Альтернативная прошивка Sonoff-MQTT-OTA-Arduino
Библиотека Livolo для Arduino
Подключение ESP8266 к MQTT серверу Mosquitto
Рubsubclient
Модернизация sonoff
Кошка по традиции
Альтернативная прошивка Sonoff-MQTT-OTA-Arduino
Библиотека Livolo для Arduino
Подключение ESP8266 к MQTT серверу Mosquitto
Рubsubclient
Модернизация sonoff
Кошка по традиции
Самые обсуждаемые обзоры
+71 |
3340
133
|
+51 |
3572
66
|
+29 |
2535
48
|
+38 |
2894
41
|
+55 |
2043
37
|
Посылаем команду -> MQTT-сервер < — Sonoff по Wifi её считывает и отправляет по RF-> выключателю Livolo
Sonoff RF имеет на борту только RF-приемник, т.е. из коробки ничего передавать не может.
То, что РЕАЛЬНЫЙ статус в телефоне показывается цветом иконки это конечно радует, но не более. Не всегда можно постоянно смотреть статус, хотелось бы быть уверенным, что если включил (и электричество через минуту перемигнет), то устройство вернется в прежнее состояние. Вот например розетка Сяоми (по крайней мере 2я версия) помнит состояние.
P.S. Если кому интересно, доставка одного девайса до Новосибирска 4,61 бакса показывает. Четыре девайса 8,61$ доставка.
А запросить состояние у ливоло невозможно
Подскажите, а реально реализовать управление димером от Livolo?
github.com/arendst/Sonoff-MQTT-OTA-Arduino
Но ни слова про поддержку димера от Livolo.
Подскажите какая батарейка идет в пульт Sonoff?
А вопрос следующий.
Sonoff basic или RF например из коробки. Вот скажем задан цикличный таймер 20 минут включено/ 20 минут выключено.
А что будет, если интернет отрубится? Sonoff продолжит включать/выключать? Или нафиг все работать перестанет сразу, и просто будет состояние выключено?
Спасибо