esphome
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
import struct
|
||||
import binascii
|
||||
import codecs
|
||||
|
||||
class Climate(hass.Hass):
|
||||
ir_topic = "home/remote/rm2/code/set"
|
||||
@@ -38,7 +39,8 @@ class Climate(hass.Hass):
|
||||
return
|
||||
remote = Remote()
|
||||
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"
|
||||
if (self.timer != None):
|
||||
self.cancel_timer(self.timer)
|
||||
@@ -52,15 +54,17 @@ class Climate(hass.Hass):
|
||||
self.turn_on("switch.plug_158d00010dd98d")
|
||||
if (self.timer != None):
|
||||
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):
|
||||
code = ""
|
||||
self.log('mode: {}'.format(args['mode']))
|
||||
remote = Remote()
|
||||
code = remote.set_mode(args['mode'], self.fan_speed, args['temp'], args['state'])
|
||||
code = code.decode("utf-8")
|
||||
self.call_service("mqtt/publish", topic = self.ir_topic, payload = code)
|
||||
self.log("command: {}".format(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):
|
||||
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.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):
|
||||
array = bytearray()
|
||||
|
||||
@@ -10,5 +10,6 @@ mainroom_climate:
|
||||
ha_panel: alarm_control_panel.ha_alarm
|
||||
door_window: group.ac_door_window
|
||||
color_entity: light.gateway_light_f0b429aa1463
|
||||
remote: remote.xiaomi_miio_192_168_1_52
|
||||
global_dependencies:
|
||||
- globals
|
||||
@@ -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)
|
||||
@@ -1,12 +1,12 @@
|
||||
import appdaemon.plugins.hass.hassapi as hass
|
||||
|
||||
class Noolite(hass.Hass):
|
||||
brightness = 25
|
||||
brightness = 40
|
||||
force = False
|
||||
|
||||
def initialize(self):
|
||||
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_event(self.away_mode, "away_mode")
|
||||
|
||||
@@ -15,7 +15,7 @@ class Noolite(hass.Hass):
|
||||
|
||||
def light_change(self, entity, attribute, old, new, kwargs):
|
||||
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)
|
||||
else:
|
||||
self.noolite_off()
|
||||
@@ -29,7 +29,18 @@ class Noolite(hass.Hass):
|
||||
self.turn_on(self.args['entity_id'], brightness=self.brightness)
|
||||
|
||||
def sunset_cb(self, kwargs):
|
||||
if not self.somebody_home():
|
||||
return
|
||||
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):
|
||||
self.noolite_off()
|
||||
|
||||
@@ -2,3 +2,4 @@ salt_lamp:
|
||||
module: salt_lamp
|
||||
class: Noolite
|
||||
entity_id: light.salt_lamp
|
||||
ha_panel: alarm_control_panel.ha_alarm
|
||||
|
||||
@@ -187,6 +187,22 @@ services:
|
||||
options:
|
||||
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:
|
||||
# traefik:
|
||||
# external:
|
||||
|
||||
@@ -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
|
||||
name: xiaomi_remote
|
||||
host: !secret xiaomi_remote_host
|
||||
token: !secret xiaomi_remote_token
|
||||
@@ -17,6 +17,13 @@ yandex_tts_key: xxxxxxxxx
|
||||
###################################
|
||||
mysql_url: xxxxxxxxx
|
||||
|
||||
###################################
|
||||
# ESPHOME SETTINGS #
|
||||
###################################
|
||||
wifi_24_name: xxxxxxxxx
|
||||
wifi_24_pass: xxxxxxxxx
|
||||
esphome_pass: xxxxxxxxx
|
||||
|
||||
###################################
|
||||
# APPLETV SETTINGS #
|
||||
###################################
|
||||
@@ -142,3 +149,4 @@ amazon_mail: xxxxxxxxx
|
||||
amazon_pass: xxxxxxxxx
|
||||
alexa_client_id: xxxxxxxxx
|
||||
alexa_client_secret: xxxxxxxxx
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ views:
|
||||
- sensor.temperature_158d0001149b3c
|
||||
- sensor.humidity_158d0001149b3c
|
||||
- sensor.xiaomi_airpurifier_air_quality_pm25
|
||||
- sensor.senseair_co2_value
|
||||
- light.yeelight_color1_34ce008fe328
|
||||
- light.yeelight_color1_286c071065c7
|
||||
- light.yeelight_mono1_34ce0087ff33
|
||||
@@ -321,6 +322,7 @@ views:
|
||||
refresh_interval: 60
|
||||
entities:
|
||||
- sensor.xiaomi_airpurifier_air_quality_pm25
|
||||
- sensor.senseair_co2_value
|
||||
- title: "Настройки"
|
||||
icon: mdi:settings
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user