This commit is contained in:
Your Name
2019-11-29 21:19:01 +03:00
parent 5f60f4ea3f
commit fda2e0cc24
11 changed files with 445 additions and 42 deletions
+35 -6
View File
@@ -1,6 +1,7 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
import struct import struct
import binascii import binascii
import codecs
class Climate(hass.Hass): class Climate(hass.Hass):
ir_topic = "home/remote/rm2/code/set" ir_topic = "home/remote/rm2/code/set"
@@ -38,7 +39,8 @@ class Climate(hass.Hass):
return return
remote = Remote() remote = Remote()
code = remote.set_mode("NONE", "NONE", 18, "OFF") code = remote.set_mode("NONE", "NONE", 18, "OFF")
self.call_service("mqtt/publish", topic = self.ir_topic, payload = code.decode("utf-8")) self.log("command: {}".format(code))
self.call_service("remote/send_command", entity_id=self.args['remote'], command = code)
self.split_state = "off" self.split_state = "off"
if (self.timer != None): if (self.timer != None):
self.cancel_timer(self.timer) self.cancel_timer(self.timer)
@@ -52,15 +54,17 @@ class Climate(hass.Hass):
self.turn_on("switch.plug_158d00010dd98d") self.turn_on("switch.plug_158d00010dd98d")
if (self.timer != None): if (self.timer != None):
self.cancel_timer(self.timer) self.cancel_timer(self.timer)
self.timer = self.run_in(self.run_in_split_mode, 10, mode=mode, temp=temp, state="ON") self.timer = self.run_in(self.run_in_split_mode, 1, mode=mode, temp=temp, state="ON")
def run_in_split_mode(self, args): def run_in_split_mode(self, args):
code = "" code = ""
self.log('mode: {}'.format(args['mode'])) self.log('mode: {}'.format(args['mode']))
remote = Remote() remote = Remote()
code = remote.set_mode(args['mode'], self.fan_speed, args['temp'], args['state']) code = remote.set_mode(args['mode'], self.fan_speed, args['temp'], args['state'])
code = code.decode("utf-8") self.log("command: {}".format(code))
self.call_service("mqtt/publish", topic = self.ir_topic, payload = code) # code = code.decode("utf-8")
self.call_service("remote/send_command", entity_id=self.args['remote'], command = code)
#self.call_service("mqtt/publish", topic = self.ir_topic, payload = code)
def do_action(self): def do_action(self):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']): if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
@@ -227,8 +231,33 @@ class Remote(object):
self.fill_buffer(24, 4, self.crc) self.fill_buffer(24, 4, self.crc)
self.codes[BUFFER_SIZE - 1] = ZERO_AND_ONE_HIGH self.codes[BUFFER_SIZE - 1] = ZERO_AND_ONE_HIGH
self.codes = binascii.hexlify(self.lirc2broadlink(self.codes))
return self.codes pronto = self.lirc2pronto(self.codes)
# print(pronto)
# self.codes = binascii.hexlify(self.lirc2broadlink(self.codes))
# base64code = codecs.encode(codecs.decode(self.codes, 'hex'), 'base64').decode()
# print(base64code)
return pronto
def lirc2pronto(self, pulses, ff=0x6d):
frequency=1/(float(ff)*0.241246)
prontoByte = []
prontoByte.append(int(0))
prontoByte.append(ff)
prontoByte.append(int(0))
prontoByte.append(int(33))
for i in range(0,len(pulses)):
prontoByte.append(int(round(float(pulses[i])*frequency)))
prontoByte[3] = int(len(prontoByte)/2 - 2)
prontoCode = []
for i in range(0,len(prontoByte)):
beB = struct.pack('>I', prontoByte[i])
c = str(binascii.hexlify(beB)[4:8])[2:6]
prontoCode.append(c)
codeStr = "pronto: {}".format(' '.join(prontoCode))
return codeStr
def lirc2broadlink(self, pulses): def lirc2broadlink(self, pulses):
array = bytearray() array = bytearray()
@@ -10,5 +10,6 @@ mainroom_climate:
ha_panel: alarm_control_panel.ha_alarm ha_panel: alarm_control_panel.ha_alarm
door_window: group.ac_door_window door_window: group.ac_door_window
color_entity: light.gateway_light_f0b429aa1463 color_entity: light.gateway_light_f0b429aa1463
remote: remote.xiaomi_miio_192_168_1_52
global_dependencies: global_dependencies:
- globals - globals
+326
View File
@@ -0,0 +1,326 @@
from base64 import b64encode, b64decode
import binascii
from itertools import islice
gen_raw_nec_protocols_standard = ['nec1',
'nec2',
'necx1',
'necx2']
gen_raw_nec_protocols_suffixes = ['',
'-y1',
'-y2',
'-y3',
'-f16']
gen_raw_nec_protocols = list(x + y for x in gen_raw_nec_protocols_standard
for y in gen_raw_nec_protocols_suffixes)
gen_raw_rc5_protocols = ['rc5']
gen_raw_rc6_protocols = ['rc6']
gen_raw_protocols = [*gen_raw_nec_protocols, *gen_raw_rc5_protocols, *gen_raw_rc6_protocols]
def uX_to_bin(v, x):
if(v < 0):
v += (1 << x)
return bin(v)[2:].rjust(x, '0')
def gen_raw_rc5(protocol, device, subdevice, function, toggle=0):
logical_bit = 889.0
def encode_bit(s):
if s == '1':
yield logical_bit * -1
yield logical_bit * 1
else:
yield logical_bit * 1
yield logical_bit * -1
def encode_uX(x, l):
for s in uX_to_bin(x, l):
yield from encode_bit(s)
yield from encode_bit('1') # start
if function < 64:
yield from encode_bit('1') # field (function 0-63)
else:
yield from encode_bit('0') # field (function 64-127)
yield from encode_bit(str(toggle)) # toggle
# address
yield from encode_uX(device, 5)
# command
yield from encode_uX(function % 64, 6)
# trailing silence
yield logical_bit * -100
def gen_raw_rc6(protocol, device, subdevice, function, toggle=0, mode=0):
logical_bit = 444.0
def encode_bit(s):
if s == '1':
yield logical_bit * 1
yield logical_bit * -1
else:
yield logical_bit * -1
yield logical_bit * 1
def encode_uX(x, l):
for s in uX_to_bin(x, l):
yield from encode_bit(s)
#LS
yield logical_bit * 6
yield logical_bit * -2
#SB
yield from encode_bit('1')
#Mode
yield from encode_uX(mode, 3)
#TB
if toggle:
yield logical_bit * 2
yield logical_bit * -2
else:
yield logical_bit * -2
yield logical_bit * 2
#Control
yield from encode_uX(device, 8)
#Information
yield from encode_uX(function, 8)
#Signal Free
yield logical_bit * -6
def gen_raw_nec(protocol, device, subdevice, function):
logical_bit = 562.5
protocol_base, protocol_suffix = (protocol.split('-') + [None])[:2]
def encode(value):
b = uX_to_bin(value, 8)
for s in reversed(b):
yield logical_bit # burst
if s == '1':
yield logical_bit * -3 # one is encoded by 3 length
else:
yield logical_bit * -1 # zero is encoded by 1 lengths
if protocol_base in ('nec1', 'necx1'):
yield logical_bit * 16 # leading burst
else:
yield logical_bit * 8 # leading burst
yield logical_bit * -8 # space before data
yield from encode(device)
if subdevice >= 0:
yield from encode(subdevice)
else:
yield from encode(~device)
yield from encode(function & 0xFF)
if protocol_suffix == 'y1': # Yamaha special version 1
yield from encode(function ^ 0x7F)
elif protocol_suffix == 'y2': # Yamaha special version 2
yield from encode(function ^ 0xFE)
elif protocol_suffix == 'y3': # Yamaha special version 3
yield from encode(function ^ 0x7E)
elif protocol_suffix == 'f16': # 16 bit function
yield from encode((function >> 8) & 0xFF)
else: # Standard invert
yield from encode(function ^ 0xFF)
yield logical_bit # Trailing burst
yield logical_bit * -3 # Trailing zero to separate
def gen_raw_general(protocol, device, subdevice, function, **kwargs):
if protocol.lower() in gen_raw_nec_protocols:
yield from gen_raw_nec(protocol.lower(),
int(device),
int(subdevice),
int(function))
if protocol.lower() in gen_raw_rc5_protocols:
yield from gen_raw_rc5(protocol.lower(),
int(device),
int(subdevice),
int(function))
if protocol.lower() in gen_raw_rc6_protocols:
yield from gen_raw_rc6(protocol.lower(),
int(device),
int(subdevice),
int(function))
def gen_simplified_from_raw(x):
"""
Simplify raw string.
Combine successive same sign value, drop zeros, drop leading negative
"""
value = 0
for i in x:
if i == 0:
continue
elif value == 0:
if i > 0:
value = i
else:
pass # leading negative
elif (value > 0) == (i > 0):
value += i
else:
yield value
value = i
if value != 0:
yield value
def gen_paired_from_raw(x):
"""
Create pairs of on, off
"""
sign = 1
for i in x:
if (i < 0) ^ (sign < 0):
yield 0.0
yield i
else:
yield i
sign = -sign
if sign < 0:
yield 0.0
def gen_raw_from_broadlink(data):
v = iter(data)
code = next(v)
repeat = next(v)
assert code == 0x26 # IR
length = int.from_bytes(islice(v, 2), byteorder='little')
def decode_one(x):
return round(x * 8192 / 269)
def decode_iter(x):
sign = 1
while True:
d = next(x)
if d == 0:
d = int.from_bytes(islice(x, 2), byteorder='big')
yield sign * decode_one(d)
sign = sign * -1
yield from decode_iter(islice(v, length))
def gen_raw_from_broadlink_base64(data):
yield from gen_raw_from_broadlink(b64decode(data))
def gen_broadlink_from_raw(data, repeat=0):
yield from b'\x26' # IR
yield from repeat.to_bytes(1, byteorder='big') # Repeat
def encode_one(x):
# v = abs(int(i / 32.84))
v = abs(int(x * 269 / 8192))
if v > 255:
yield from b'\x00'
yield from v.to_bytes(2, byteorder='big')
else:
yield from v.to_bytes(1, byteorder='big')
def encode_list(x):
for i in gen_simplified_from_raw(x):
yield from encode_one(i)
c = bytearray(encode_list(data))
count = len(c)
yield from count.to_bytes(2, byteorder='little')
yield from c
yield from b'\x0d'
yield from b'\x05'
# calculate total length for padding
count += 6 # header+len+trailer
count += 4 # rm.send_data() 4 byte header (not seen here)
yield from bytearray(16 - (count % 16))
def gen_broadlink_base64_from_raw(data, repeat=0):
return b64encode(bytes(gen_broadlink_from_raw(data, repeat)))
def gen_raw_from_pronto(data):
clock = 0.241246 # Pronto clock base: 1000000 / (32768 * 506 / 4)
v = iter(data)
zero = next(v)
assert zero == 0
base = next(v)
freq = 1.0 / (base * clock)
seq1_len = next(v)
seq2_len = next(v)
for _ in range(seq1_len):
yield +round(next(v) / freq, 1)
yield -round(next(v) / freq, 1)
for _ in range(seq2_len):
yield +round(next(v) / freq, 1)
yield -round(next(v) / freq, 1)
def gen_pronto_from_raw_int(seq1, seq2, base=None, freq=None):
clock = 0.241246 # Pronto clock base: 1000000 / (32768 * 506 / 4)
if freq is None:
if base is None:
freq = 0.040
else:
freq = 1.0 / (base * clock)
if base is None:
base = int(1 / (freq * clock))
yield 0
yield base
def fixup(x):
return list(gen_paired_from_raw(gen_simplified_from_raw(x)))
#return list(gen_paired_from_raw((x)))
simple1 = fixup(seq1)
simple2 = fixup(seq2)
yield int(len(simple1)/2) # sequence 1
yield int(len(simple2)/2) # sequence 2
for x in simple1:
yield int(abs(x) * freq)
for x in simple2:
yield int(abs(x) * freq)
def gen_pronto_from_raw(seq1, seq2, base=None, freq=None):
data = gen_pronto_from_raw_int(seq1, seq2, base, freq)
for value in data:
yield "{0:0{1}x}".format(value,4)
+14 -3
View File
@@ -1,12 +1,12 @@
import appdaemon.plugins.hass.hassapi as hass import appdaemon.plugins.hass.hassapi as hass
class Noolite(hass.Hass): class Noolite(hass.Hass):
brightness = 25 brightness = 40
force = False force = False
def initialize(self): def initialize(self):
self.run_at_sunrise(self.sunrise_cb) self.run_at_sunrise(self.sunrise_cb)
self.run_at_sunset(self.sunset_cb) # self.run_at_sunset(self.sunset_cb)
self.listen_state(self.light_change, "group.mainroom_light") self.listen_state(self.light_change, "group.mainroom_light")
self.listen_event(self.away_mode, "away_mode") self.listen_event(self.away_mode, "away_mode")
@@ -15,7 +15,7 @@ class Noolite(hass.Hass):
def light_change(self, entity, attribute, old, new, kwargs): def light_change(self, entity, attribute, old, new, kwargs):
sun_down = self.sun_down() sun_down = self.sun_down()
if new == "off" and (sun_down or self.force): if new == "off" and (sun_down or self.force) and self.somebody_home():
self.noolite_on(self.brightness) self.noolite_on(self.brightness)
else: else:
self.noolite_off() self.noolite_off()
@@ -29,7 +29,18 @@ class Noolite(hass.Hass):
self.turn_on(self.args['entity_id'], brightness=self.brightness) self.turn_on(self.args['entity_id'], brightness=self.brightness)
def sunset_cb(self, kwargs): def sunset_cb(self, kwargs):
if not self.somebody_home():
return
self.noolite_on(self.brightness) self.noolite_on(self.brightness)
def somebody_home(self):
if 'ha_panel' not in self.args:
return True
ha_panel_state = self.get_state(self.args['ha_panel'])
if ha_panel_state == 'armed_away':
return False
return True
def sunrise_cb(self, kwargs): def sunrise_cb(self, kwargs):
self.noolite_off() self.noolite_off()
@@ -2,3 +2,4 @@ salt_lamp:
module: salt_lamp module: salt_lamp
class: Noolite class: Noolite
entity_id: light.salt_lamp entity_id: light.salt_lamp
ha_panel: alarm_control_panel.ha_alarm
+16
View File
@@ -187,6 +187,22 @@ services:
options: options:
max-size: 50m max-size: 50m
esphome:
image: esphome/esphome
container_name: esphome
# ports:
# - "6052:6052"
volumes:
- ./esphome/settings:/config
- ./hass/settings/secrets.yaml:/config/secrets.yaml:ro
network_mode: "host"
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
# networks: # networks:
# traefik: # traefik:
# external: # external:
+38
View File
@@ -0,0 +1,38 @@
logger:
baud_rate: 0
esphome:
name: senseair
platform: ESP8266
board: d1_mini
wifi:
ssid: !secret wifi_24_name
password: !secret wifi_24_pass
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Senseair Fallback Hotspot"
password: "FVFA2EoK8phQ"
captive_portal:
# Enable Home Assistant API
api:
password: !secret esphome_pass
ota:
password: !secret esphome_pass
uart:
rx_pin: D7
tx_pin: D8
baud_rate: 9600
sensor:
- platform: senseair
co2:
name: "SenseAir CO2 Value"
update_interval: 60s
@@ -1,3 +1,4 @@
- platform: xiaomi_miio - platform: xiaomi_miio
name: xiaomi_remote
host: !secret xiaomi_remote_host host: !secret xiaomi_remote_host
token: !secret xiaomi_remote_token token: !secret xiaomi_remote_token
+8
View File
@@ -17,6 +17,13 @@ yandex_tts_key: xxxxxxxxx
################################### ###################################
mysql_url: xxxxxxxxx mysql_url: xxxxxxxxx
###################################
# ESPHOME SETTINGS #
###################################
wifi_24_name: xxxxxxxxx
wifi_24_pass: xxxxxxxxx
esphome_pass: xxxxxxxxx
################################### ###################################
# APPLETV SETTINGS # # APPLETV SETTINGS #
################################### ###################################
@@ -142,3 +149,4 @@ amazon_mail: xxxxxxxxx
amazon_pass: xxxxxxxxx amazon_pass: xxxxxxxxx
alexa_client_id: xxxxxxxxx alexa_client_id: xxxxxxxxx
alexa_client_secret: xxxxxxxxx alexa_client_secret: xxxxxxxxx
+2
View File
@@ -23,6 +23,7 @@ views:
- sensor.temperature_158d0001149b3c - sensor.temperature_158d0001149b3c
- sensor.humidity_158d0001149b3c - sensor.humidity_158d0001149b3c
- sensor.xiaomi_airpurifier_air_quality_pm25 - sensor.xiaomi_airpurifier_air_quality_pm25
- sensor.senseair_co2_value
- light.yeelight_color1_34ce008fe328 - light.yeelight_color1_34ce008fe328
- light.yeelight_color1_286c071065c7 - light.yeelight_color1_286c071065c7
- light.yeelight_mono1_34ce0087ff33 - light.yeelight_mono1_34ce0087ff33
@@ -321,6 +322,7 @@ views:
refresh_interval: 60 refresh_interval: 60
entities: entities:
- sensor.xiaomi_airpurifier_air_quality_pm25 - sensor.xiaomi_airpurifier_air_quality_pm25
- sensor.senseair_co2_value
- title: "Настройки" - title: "Настройки"
icon: mdi:settings icon: mdi:settings
cards: cards:
@@ -1,30 +0,0 @@
server {
listen 80 default_server;
#listen 443 default ssl;
server_name _;
#ssl_certificate /config/keys/cert.crt;
#ssl_certificate_key /config/keys/cert.key;
client_max_body_size 0;
#letsencrypt challenge response
CdVsNcvBapg7d3o0ZaixdMbMt5FqG7T7Z19CJsotuGI.hTi2mqIkE64NQibEfAxiC4ih0s6_pk4Ne2hCgHXFntI location ~/.well-known/acme-challenge/(.*)$ {
return 200 '$1';
}
location / {
real_ip_header X-Forwarded-For;
set_real_ip_from 0.0.0.0/0;
proxy_pass http://hass;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}