Как стать автором
Обновить

Автоматизация с удобными Pebble, стабильным Noolite и доступной esp8266

Время на прочтение 12 мин
Количество просмотров 9.9K
С момента прошлой статьи (ссылка прошел почти год. И за этот год, я переосмыслил некоторые вещи, интернет и получилось что-то вроде IoT :) → интернет вещей).

Постараюсь кратко изложить новую порцию накопленных знаний, описать куда стремлюсь и чего хочу добиться → прошу к прочтению.

Noolite F v2.0


В заголовке у меня проскочили слова: pebble & noolite. Перейдем к ним!
Я ещё управляю с pebble своим светом noolite, гаражом и воротами. Про noolite уже написано достаточно много полезной информации, поиск показывает новые статьи.

Помимо блоков первого поколения системы noolite, у меня появились новые: cиловой блок SLF-1-200 (nooLite-F) с обратной связью (и шифрованием нового поколения).

image

силовой блок SB-1-150

image

и обновленный usb адаптера MTRF-64 USB.

image

Весь свет в моем доме на системе noolite.

Ещё недавно у меня в «системном домашнем корче» красовался noolite USB адаптер первого поколения и на 16 устройств, а теперь на 64 канала (MTRF-64 USB), да ещё и для устройств нового поколения с обратной связью (noolite F). Ноотехника движется в правильном направлении, создавая новый современные устройства.

Есть 2 самые главное опции новых устройств: обратная связь и принцип идентификации устройств по адресам: ID. Теперь не надо писать в 1 канал 1 устройств и управлять каналом. Теперь можно обращаться к конкретному устройств в одном канале по его ID и посылая ему команду исполнения.

Документация для блоков нового поколения доступна на сайте производителя.

Все работает через serial port, используя python код.
# -*- coding: utf-8 -*-
#!/usr/bin/env python

import serial
import time

class NooLiteCommand:
    def __init__(self, ch, cmd, mode=0, ctr=0, res=0, fmt=0, d0=0, d1=0, d2=0, d3=0, id0=0, id1=0, id2=0, id3=0):
        self.st = 171
        self.mode = mode
        self.ctr = ctr
        self.res = res
        self.ch = ch
        self.cmd = cmd
        self.fmt = fmt
        self.d0 = d0
        self.d1 = d1
        self.d2 = d2
        self.d3 = d3
        self.id0 = id0
        self.id1 = id1
        self.id2 = id2
        self.id3 = id3
        self.sp = 172

    @property
    def crc(self):
        crc = sum([
            self.st,
            self.mode,
            self.ctr,
            self.res,
            self.ch,
            self.cmd,
            self.fmt,
            self.d0,
            self.d1,
            self.d2,
            self.d3,
            self.id0,
            self.id1,
            self.id2,
            self.id3,
        ])
        return crc if crc < 256 else divmod(crc, 256)[1]

    def to_bytes(self):
        return bytearray([
            self.st,
            self.mode,
            self.ctr,
            self.res,
            self.ch,
            self.cmd,
            self.fmt,
            self.d0,
            self.d1,
            self.d2,
            self.d3,
            self.id0,
            self.id1,
            self.id2,
            self.id3,
            self.crc,
            self.sp
        ])
class NooliteSerial:
    def __init__(self, tty_name):
        self.tty = self._get_tty(tty_name)
    def on(self, ch):
        self.send_command(ch, 2, 2, 0)
        pass
    def off(self, ch):
        self.send_command(ch, 0, 2, 0)
        pass
    def status(self, ch):
        m = self.send_command(ch, 128, 2, 0)
        pass
    def send_command(self, ch, cmd, mode=0, ctr=0, res=0, fmt=0, d0=0, d1=0, d2=0, d3=0, id0=0, id1=0, id2=0, id3=0):
        command = NooLiteCommand(ch, cmd, mode, ctr, res, fmt, d0, d1, d2, d3, id0, id1, id2, id3)
        self.tty.write(command.to_bytes())
        while True:
            bytes_response = list(self.tty.read(117))
            if bytes_response:
                all_responses.append(bytes_response)
                if bytes_response[3] == 0:
                    break
            else:
                break
        return all_responses
    @staticmethod
    def _get_tty(tty_name):
        serial_port = serial.Serial(tty_name, timeout=0.1)
        if not serial_port.is_open:
            serial_port.open()
        serial_port.flushInput()
        serial_port.flushOutput()
        return serial_port
noo_serial = NooliteSerial('/dev/ttyUSB0')
#ch, cmd, mode, ctr
#noo_serial.send_command(1, 15, 2, 0) #включить привязку на канал 1
#noo_serial.send_command(0, 4, 2, 0) #switch
#noo_serial.send_command(0, 2, 2, 0) #turn on
#noo_serial.send_command(0, 3, 2, 0) #Регулировка яркости вверх
#noo_serial.send_command(0, 0, 2, 0) #turn off
#noo_serial.send_command(0, 15, 2, 0) #включить привязку
#noo_serial.send_command(0, 128, 2, 0,0,1) #CMD_Read_State + fmt = 1
#noo_serial.on(0)
#noo_serial.status(0)
#noo_serial.off(0)
#noo_serial.off(0)
#noo_serial.status(0)
#noo_serial.send_command(ch=0,ctr=8, cmd=4, id0=0,id1=0,id2=48,id3=114) #switch noolite ID 0.0.48.114



Используя нужные функции в нужное время, мы можем управлять noolite блоками и получать статус от новых. Так как у меня 99% это старые блоки, мне остается использовать старую схему записи 1 блока в 1 канал, с учетом того, что usb адаптер поддерживает и старые блоки и новые (спасибо разработчикам за совместимость без танцев бубном). На лету можно управлять и получать статус новых блоков и управлять старыми.

До недавнего времени, в доме не было ни одного кнопочного выключателя. Пришлось добавить в гараж свет (я просто добрался туда навести порядок :D ), а так же появился выключатель в подсветке зеркала для макияжа. Зная, что есть силовой блок SB-1-150, я поставил выключатель обычный и подключил блок. Подсветку зеркала сделал из белой плиты, 3w лампочек 4000 К, белый свет. SB-1-150 уникален тем, что он может быть размещен в коробке настенного выключателя, (выключателя) в разрез существующей схемы и подключением кнопочного выключателя к самому блоку.

Именно то, что надо для макияжа моим девочкам :)
image

Pebble


Когда я начал увлекаться автоматизацией, одним из главных факторов было: грамотное и удобное управление.

Управлять со смартфона вроде бы и удобно, но… и не удобно, каждый раз грузить приложение, где маленькие неудобные кнопки, левая реклама или пока доберешься до нажатия кнопки… отпадет все желание. Голосовое управление отпадает, так как это тоже неудобно и странно :) — пусть это останется в сюжетах фантастических фильмов.

image

А я решил задачу по другому — через наручные часы, которые ПОСТОЯННО со мной, не занимают места и сочетают в себе все удобства, мобильность и быстроту управления. Вообщем — Pebble жив! Но самое главное то, что, для часов все ещё можно писать приложения — свои приложения, для управления своими системами автоматизации. Это вообще так-то «бомба».


Трагичность закрытия pebble.com компании (Fitbit выкупил ради патентов весь pebble.com) не отразилось на их производительности и работе. Недавно прошло обновление ios, android — часы отвязали от облачных сервисов pebble (в случае прекращения поддержки) и авторизации.

Визуально — ничего не изменилось, зато остался жить сервис: cloudpebble.net — WTF? Это очень полезная штука. Если Вы можете писать код на JS (и нет желания тратить много времени на C++) — пожалуйста, welcome on board :) — cloud pebble позволяет Вам быстро «накидать» приложение. Ну вот как я сделал. Конечно, производительность JS не стоит сравнивать с C++ (это святое), стерпиться. дА!

Pebble cloud JS code для работы с API
var UI = require('ui');
var ajax = require('ajax');

var noolite = [
    ['ворота','http://your-home-server-ip-address:1183/mqtt/gate/slidegate', 'images/gate.png'],
    ['гараж','http://your-home-server-ip-address:1183/mqtt/gate/garage', 'images/door.png'],
    ['приехали!','http://your-home-server-ip-address:1183/noolite/switch/103', 'images/light.png'],    
    ['выкл все','http://your-home-server-ip-address:1183/noolite/switch/100', 'images/system2.png'],    
    ['1 этаж свет','', 'images/light.png'],
    ['2 этаж свет','', 'images/light.png'],    
    ['выкл 1эт','http://your-home-server-ip-address:1183/noolite/switch/101', 'images/light.png'],    
    ['выкл 2эт','http://your-home-server-ip-address:1183/noolite/switch/102', 'images/light.png'],
    ['вода','', 'images/water.png'],
    ['мусорка','http://your-home-server-ip-address:1183/admin/system/1', 'images/system1.png'],
    ];

var water = [
    ['сенди вода','http://your-home-server-ip-address:1183/admin/poliv/1', 'images/water.png'],
    ['баня вода','http://your-home-server-ip-address:1183/admin/poliv/2', 'images/water.png'],
    ['зона 1 фасад','http://your-home-server-ip-address:1183/noolite/switch/20', 'images/water.png'],
    ['зона 2 фасад','http://your-home-server-ip-address:1183/noolite/switch/21', 'images/water.png'],
    ['зона 3 фасад','http://your-home-server-ip-address:1183/noolite/switch/22', 'images/water.png'],
    ['зона 4 фасад','http://your-home-server-ip-address:1183/noolite/switch/23', 'images/water.png'],    
    ['зона 5 зад','http://your-home-server-ip-address:1183/admin/poliv/switch/5', 'images/water.png'],    
    ['зона 6 зад','http://your-home-server-ip-address:1183/admin/poliv/switch/6', 'images/water.png'],    
    ['зона 7 зад','http://your-home-server-ip-address:1183/admin/poliv/switch/7', 'images/water.png'],    
    ['зона 8 зад','http://your-home-server-ip-address:1183/admin/poliv/switch/8', 'images/water.png'],    
    ['зона 9 зад','http://your-home-server-ip-address:1183/admin/poliv/switch/9', 'images/water.png'], 
];

var light1 = [
    ['коридор свет','http://your-home-server-ip-address:1183/noolite/switch/6', 'images/light.png'],
    ['кухня свет','http://your-home-server-ip-address:1183/noolite/switch/0', 'images/light.png'],
    ['кухня стол свет','http://your-home-server-ip-address:1183/noolite/switch/1', 'images/light.png'],
    ['зал низ свет','http://your-home-server-ip-address:1183/noolite/switch/7', 'images/light.png'],
    ['зал верх свет','http://your-home-server-ip-address:1183/noolite/switch/8', 'images/light.png'],
    ['ванная 1 свет','http://your-home-server-ip-address:1183/noolite/switch/2', 'images/light.png'],
    ['ванная 1 свет','http://your-home-server-ip-address:1183/noolite/switch/3', 'images/light.png'],
    ['гостевая 1 свет','http://your-home-server-ip-address:1183/noolite/switch/4', 'images/light.png'],
    ['топочная свет','http://your-home-server-ip-address:1183/noolite/switch/5', 'images/light.png'],
    ['гараж свет','http://your-home-server-ip-address:1183/noolite/switch/9', 'images/light.png'],
];
    
var light2 = [
    ['спальня свет','http://your-home-server-ip-address:1183/noolite/switch/15', 'images/light.png'],
    ['гардероб свет','http://your-home-server-ip-address:1183/noolite/switch/16', 'images/light.png'],
    ['коридор 2 свет','http://your-home-server-ip-address:1183/noolite/switch/10', 'images/light.png'],
    ['ванная 2 свет','http://your-home-server-ip-address:1183/noolite/switch/11', 'images/light.png'],
    ['гостевая 2 свет','http://your-home-server-ip-address:1183/noolite/switch/12', 'images/light.png'],
    ['Алиса свет','http://your-home-server-ip-address:1183/noolite/switch/13', 'images/light.png'],
    ['Алиса картина свет','http://your-home-server-ip-address:1183/noolite/switch/14', 'images/light.png'],
];

var menu = new UI.Menu({
    sections: [{
        items: [{
            title: '',
            subtitle: ''
        }]
    }]
});
var menu1 = new UI.Menu({
    sections: [{
        items: [{
            title: '',
            subtitle: ''
        }]
    }]
});
var menu2 = new UI.Menu({
    sections: [{
        items: [{
            title: '',
            subtitle: ''
        }]
    }]
});
var menu3 = new UI.Menu({
    sections: [{
        items: [{
            title: '',
            subtitle: ''
        }]
    }]
});
var items = [];
for (var i=0; i<noolite.length; i++) {
    items[i] = {
        title: noolite[i][0], 
        subtitle: '',
        icon: noolite[i][2] 
    };
}    
var items1 = [];
for (var i=0; i<light1.length; i++) {
    items1[i] = {
        title: light1[i][0], 
        subtitle: '',
        icon: light1[i][2] 
    };
}
var items2 = [];
for (var i=0; i<light2.length; i++) {
    items2[i] = {
        title: light2[i][0], 
        subtitle: '',
        icon: light2[i][2] 
    };
}
var items3 = [];
for (var i=0; i<water.length; i++) {
    items3[i] = {
        title: water[i][0], 
        subtitle: '',
        icon: water[i][2] 
    };
}
menu.items(0, items);
menu1.items(0, items1);
menu2.items(0, items2);
menu3.items(0, items3);
menu.show();
menu.on('select', function(e) {
    if (e.itemIndex == 4) {
        menu1.show();
    }
    else if (e.itemIndex == 5) {
        menu2.show();
    }
    
    else if (e.itemIndex ==8) {
        menu3.show();
    }
    
    else {
        
        var url = noolite[e.itemIndex][1]; 
        console.log(url);
        ajax({ 
            url: url, 
            method: 'get'
        },
            function(data) {
                console.log('switched OK');
                             
             },
            
            function(error) {
                // Failure!
                console.log('error');
            }
  
        );
    }
});
menu1.on('select', function(e) {
        var url = light1[e.itemIndex][1]; 
        console.log(url);
        ajax({ 
            url: url, 
            method: 'get'
        },
            function(data) {
                console.log('switched OK');
                             
             },
            
            function(error) {
                // Failure!
                console.log('error');
            }
        );
        
});
menu2.on('select', function(e) {
   
        var url = light2[e.itemIndex][1]; 
        console.log(url);
        ajax({ 
            url: url, 
            method: 'get'
        },
            function(data) {
                console.log('switched OK');           
             },
            
            function(error) {
                // Failure!
                console.log('error');
            }
        );
});
menu3.on('select', function(e) {
   
        var url = water[e.itemIndex][1]; 
        ajax({ 
            url: url, 
            method: 'get'
        },
            function(data) {
                console.log('switched OK');              
             },

            function(error) {
                // Failure!
                console.log('error');
            }
        );
});



Код пишется, компилируется прямо на сайте cloud Pebble и через телефон, скачивая скомпилированное приложение, можно установить на часы — за секунды. Все настолько просто, что справится любой юный автоматизатор ;)

Жаль… очень жаль, что Pebble прекратило существование и на текущий момент альтернативы я не вижу. Pebble Time всегда будут в наших сердцах!

MQTT


В прошлом году я познакомился с таким замечательным протоколом как MQTT (История: Первая версия протокола была разработана доктором Энди Станфорд-Кларком (IBM) и Арлен Ниппер (Arcom) в 1999 году и опубликована под роялти-фри лицензией. Спецификация MQTT 3.1.1 была стандартизирована консорциумом OASIS в 2014 году. Ссылка).

Есть много информации как работает протокол и какие есть клиенты… и был сильно разочарован тем, что настоящих клиентов для мобильных приложений — нет, только на android.

Понимая, что отсутствие хорошего мобильного приложения, это весьма большая проблема (дыра) в области IoT (очень многие говорят, а делают очень мало....) — я со своей командой, на работе, решили создать клиент для android, ios, wp, чтобы он соответствовал всем критериям и был удобным. О клиенте отдельно я напишу чуть позже, а так же том, как мы подружили ESP8266+MTRF64 (Noolite) и подготовили Nodemcu прошивку. Будет здорово, удобно, дешево и красиво! Следите за новостями приложения тут.

image

Управлять Noolite освещением теперь можно будет без всяких домашних серверов и usb адаптеров за 50-100$!

Общая схема работы домашней автоматизации


Все ещё существует следующая схема:

Видеонаблюдение: xeoma видеонаблюдение [ip камеры]
Сервер на системном блоке: nginx+gunicorn+python+mqtt broker
Управление: Pebble watch + MQTT client
Модули управления (ноды системы): ESP8266 + оптопары \ датчики ds18b20, dht11[22]

Ввиду того, что я увидел в MQTT протоколе множество удобств, начиная от архитектуры подписки и мгновенным общением с конечными IoT устройствами, заканчивая тем, что MQTT сообщения могут пролетать через роутер, без «проброса» всяких там портов и танцами с бубном. Удобно! Весело! Задорно :)

Все это привело меня к децентрализации системы, то есть уход от одного «домашнего корчвагена» и переход на множество так называемых нодов и использование облачного MQTT брокера (есть бесплатные MQTT брокеры, например: mqtt.ximxim.com (на сайте логин и пасс для доступа) ) — так как esp8266 решают все задачи, а она в свою очередь работают через wifi по протоколу MQTT.

Учитывая то, что MQTT Buddy будет предоставлять сервис сценариев, я вообще не буду думать о том, что мне надо писать какие то домашние сценарии, я просто буду создавать их в cloud решении и это и будет настоящий IoT и он работает! От слов к делу!

ESP8266


Когда я познакомился с этой платой разработки, меня сразу подкупило то, что плата маленькая, есть поддержка языка программирования LUA на пошивке Nodemcu. То, что Вам надо + MQTT модуль есть. В автоматизации, простые и надежные решения — это основа. Стоимость от 2$ до 5$ за плату (за 5 модель сразу с usb адаптером на борту) позволяет быстро разворачивать подсистемы.

Подключение по WiFi быстрое, возможностей масса. Например: управление воротами\гаражом\поливом\освещением. У меня 30% полива газонов работает от системы Noolite (блоки сухого контакта), остальные 70% от esp8266 development board с управлением через LUA язык по протоколу MQTT.

Пример кода на LUA для esp8266, получение ip и вызов файла работы с MQTT
--load credentials
dofile("credentials.lua")
function init(name, pass)
    wifi.setmode(wifi.STATION)
    wifi.sta.config(name, pass)
    wifi.sta.connect()
    tmr.alarm(0, 1000, 1, function() 
            if wifi.sta.getip()== nil then 
                print("IP unavaiable, Waiting...") 
                                
            else 
                tmr.stop(0)
                print("Config done, IP is "..wifi.sta.getip())
                print("mac : "..wifi.sta.getmac())
                
                dofile("mqtt.lua")
            end 
    end)

end
print("START!")
init(SSID,PASSWORD)


— mqtt.lua file


local door = 7 -- gpio13
local window = 6 -- gpio12
local cooler = 5 --gpio14
local led = 4 --GPIO2 board led! LOW == turn ON

local light1 = 8 --gpio 15
local light2 = 1 --gpio 5
local light3 = 2 -- gpio 4

function register_myself()  
    m:subscribe("mqtt_buddy/#",0,function(conn) 
    print ("subscribed to Xim mqtt.ximxim.com server")
    end)    
end

m = mqtt.Client("MQTT_BUDDY_SHOW_ROOM", 120, MQTT_USER, MQTT_PASS)
m:on("connect", function(client) print ("connected to Xim mqtt.ximxim.com server") end)
m:on("offline", function(client) reconnect_mqtt() end)
m:on("message", function(client, topic, data) 
        --print(topic.." data:"..data)
    
        if topic == "mqtt_buddy/window" and data == "1" then
        
            print "open-close window..."
            gpio.write(window, gpio.HIGH)
            gpio.write(led, gpio.LOW)    
            
            tmr.alarm(1, 1000, tmr.ALARM_SINGLE, function() 
                gpio.write(window, gpio.LOW)
                gpio.write(led, gpio.HIGH)    
                print "command to close\open window is sent!"
                end)
                
         elseif topic == "mqtt_buddy/door" and data == "1" then
        
            print "open-close door..."
            gpio.write(door, gpio.HIGH)
            gpio.write(led, gpio.LOW)    
            
            tmr.alarm(2, 1000, tmr.ALARM_SINGLE, function() 
                gpio.write(door, gpio.LOW)
                gpio.write(led, gpio.HIGH)    
                print "command to close\open door is sent!"
                end)
                
         elseif topic == "mqtt_buddy/cooler" then
            if data == "1" then
                print "switch on fan..."
                gpio.write(cooler, gpio.HIGH)
                gpio.write(led, gpio.LOW)    

            else
                print "switch off fan..."
                gpio.write(cooler, gpio.LOW)
                gpio.write(led, gpio.HIGH)    

            end

        elseif topic == "mqtt_buddy/light1" then
            if data == "1" then
                print "switch light1 ON..."
                gpio.write(light1, gpio.HIGH)
                gpio.write(led, gpio.LOW)    

            else
                print "switch light1 off..."
                gpio.write(light1, gpio.LOW)
                gpio.write(led, gpio.HIGH)    

            end

        elseif topic == "mqtt_buddy/light2" then
            if data == "1" then
                print "switch light2 ON..."
                gpio.write(light2, gpio.HIGH)
                gpio.write(led, gpio.LOW)    

            else
                print "switch light2 off..."
                gpio.write(light2, gpio.LOW)
                gpio.write(led, gpio.HIGH)    

            end

        elseif topic == "mqtt_buddy/light3" then
            if data == "1" then
                print "switch light3 ON..."
                gpio.write(light3, gpio.HIGH)
                gpio.write(led, gpio.LOW)    

            else
                print "switch light3 off..."
                gpio.write(light3, gpio.LOW)
                gpio.write(led, gpio.HIGH)    

            end
        end    
    
 end)
m:connect(MQTT_SERVER, MQTT_SERVER_PORT, 0, function(conn) register_myself() end) 



Проблема


Обращаясь к огромному сообществу думающих людей, хотел задеть крайне важную тему, а точнее проблему, которую хочу решить, но не хватает времени на более глубокие исследования \ знаний.

Суть: при децентрализации, остается видеонаблюдение, которое так или иначе ТРЕБУЕТ наличие host машины для (минимум) передачи source images либо на сервера видеонаблюдения (например ivideon) или вообще обрабатывать видео на домашней машине, как xeoma (хотя у них тоже есть cloud) — так или иначе — надо «ПРОБРАСЫВАТЬ» видео потоки, картинки на конечные вычислительные мощности (cloud решения) — или наоборот — сделать стриминг простой.

Есть мысли получать source images с камеры (тут тоже есть проблема, не в каждой камере есть URL для получения картинки с камеры и вообще непонятно, как другие узнают этот урл....) и пробрасывать в MQTT канал, где поддерживаются бинарные данные (то есть картинку можно легко передать через mqtt протокол в бинарном виде).

Возможно, кто-то уже пробовал реализовать такие вещи = ESP8266 + IP web cam?
Отзовитесь плиз (bogdanovich.alex[@]gmail.com). Буду очень благодарен!

При реализации проброса, отпадет вопрос домашнего сервера. Зачем? Потому что он электричество кушает и топочную мне греет :)

image

Всем позитивного дня и хорошего настроения!
Теги:
Хабы:
+17
Комментарии 19
Комментарии Комментарии 19

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн