updating configuration

This commit is contained in:
monster
2019-06-04 19:29:12 +03:00
parent dddbb2e108
commit 2036b027a8
13 changed files with 109 additions and 350 deletions
@@ -1,337 +0,0 @@
import appdaemon.plugins.hass.hassapi as hass
import struct
import binascii
# for PID
import time
class Climate(hass.Hass):
ir_topic = "home/remote/rm2/code/set"
target_temp = 24.0 #default
pid = PID(P = 0.2, I=0.0, D=0.0)
def initialize(self):
if "target_temp" not in self.args or "temp_sensor" not in self.args or "ha_panel" not in self.args or "door_window" not in self.args:
self.error("Please provide target_temp, temp_sensor, ha_panel, door_window in config!")
return
self.listen_state(self.target_temp_changed, self.args['target_temp'])
self.listen_state(self.temp_sensor_changed, self.args['temp_sensor'])
self.listen_state(self.constraint_changed, self.args['constraint'])
self.listen_state(self.ha_panel_changed, self.args['ha_panel'])
self.listen_state(self.door_window_changed, self.args['door_window'])
self.target_temp_changed("", "", "", "", "")
def door_window_changed(self, entity, attribute, old, new, kwargs):
self.do_action()
def ha_panel_changed(self, entity, attribute, old, new, kwargs):
self.do_action()
def constraint_changed(self, entity, attribute, old, new, kwargs):
self.do_action()
def target_temp_changed(self, entity, attribute, old, new, kwargs):
self.target_temp = float(self.get_state(self.args['target_temp']))
self.pid.SetPoint = self.target_temp
self.log('Target temp changed to: ' + str(self.target_temp))
self.do_action()
def temp_sensor_changed(self, entity, attribute, old, new, kwargs):
self.log('New room temp: ' + str(new))
self.do_action()
def split_off(self):
remote = Remote()
code = remote.set_mode("NONE", "NONE", self.target_temp, "OFF")
self.call_service("mqtt/publish", topic = self.ir_topic, payload = code.decode("utf-8"))
def do_action(self):
if 'constraint' in self.args and not self.constrain_input_boolean(self.args['constraint']):
self.log("Temperature control is disabled.")
self.split_off()
return
# Проверить alarm_panel
ha_panel_state = self.get_state(self.args['ha_panel'])
if (ha_panel_state != 'disarmed'):
self.log("Nobody home. Turning off split.")
self.split_off()
return
# Проверить датчики дверей и окон
door_window_state = self.get_state(self.args['door_window'])
if (door_window_state != 'off'):
self.log("Balcony door is opened. Turning off split.")
self.split_off()
return
room_temp = float(self.get_state(self.args['temp_sensor']))
self.pid.update(room_temp)
output = self.pid.output
self.log("PID: " + str(output) + ".")
# Определяем режим
mode = "COOLING"
room_temp = float(self.get_state(self.args['temp_sensor']))
if (room_temp >= self.target_temp):
mode = "COOLING"
else:
mode = "HEATING"
temp_diff = abs(room_temp - self.target_temp)
if (output < 0):
self.log("Temperature differs on {} degree, turn on split for {}".format(temp_diff, mode))
remote = Remote()
code = remote.set_mode(mode, "2", int(self.target_temp), "ON")
self.call_service("mqtt/publish", topic = self.ir_topic, payload = code.decode("utf-8"))
else:
self.log("Temperature is ok, turn off split.")
self.split_off()
#
# App to control climate device (split system)
#
# Args:
#
# notify = notification platform to send notifications to
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
# -------------------------------------------------------------------------------------------------
"""
Library that genrates LG air conditioner remote codes
"""
FIRST_BYTE = 136 # b10001000
STATE = {
"ON": 0,
"OFF": 24, # b11000
"CHANGE_MODE": 1,
}
MODE = {
"HEATING": 4, # b100
"AUTO": 3, # b011
"DEHUIDIFICATION": 1, # b001
"COOLING": 0,
"NONE": 0,
}
TEMPERATURE_OFFSET = 15
FAN = {
"1": 1,
"2": 0,
"3": 2,
"4": 4, # b100
"NONE": 5, # b101
}
FIRST_HIGH = 8271
FIRST_LOW = 4298
ZERO_AND_ONE_HIGH = 439
ZERO_LOW = 647
ONE_LOW = 1709
BUFFER_SIZE = 59
def test_bit(num, offset):
"""
Test the num(int) if at the given offset bit is 1
"""
mask = 1 << offset
return num & mask
def set_bit(num, offset):
"""
Set bit to at the given offset to 1
"""
mask = 1 << offset
return num | mask
class Remote(object):
"""
Library that genrates LG air conditioner remote codes
"""
def __init__(self):
self.codes = [0] * BUFFER_SIZE
self.crc = 0
def set_mode(self, mode, fan, temperature, state):
"""
Generate code and put it in the buffer
"""
self.codes[0] = FIRST_HIGH
self.codes[1] = FIRST_LOW
self.crc = 0
self.fill_buffer(0, 8, FIRST_BYTE)
self.fill_buffer(8, 5, STATE[state])
if state == 'OFF':
self.fill_buffer(13, 3, MODE['NONE'])
else:
self.fill_buffer(13, 3, MODE[mode])
if state == 'OFF':
self.fill_buffer(16, 4, 0)
else:
self.fill_buffer(16, 4, temperature - TEMPERATURE_OFFSET)
self.fill_buffer(20, 1, 0) # jet
if state == 'OFF':
self.fill_buffer(21, 3, FAN['NONE'])
else:
self.fill_buffer(21, 3, FAN[fan])
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
def lirc2broadlink(self, pulses):
array = bytearray()
for pulse in pulses:
pulse = int(pulse * 269 / 8192) # 32.84ms units
if pulse < 256:
array += bytearray(struct.pack('>B', pulse)) # big endian (1-byte)
else:
array += bytearray([0x00]) # indicate next number is 2-bytes
array += bytearray(struct.pack('>H', pulse)) # big endian (2-bytes)
packet = bytearray([0x26, 0x00]) # 0x26 = IR, 0x00 = no repeats
packet += bytearray(struct.pack('<H', len(array))) # little endian byte count
packet += array
packet += bytearray([0x0d, 0x05]) # IR terminator
# Add 0s to make ultimate packet size a multiple of 16 for 128-bit AES encryption.
remainder = (len(packet) + 4) % 16 # rm.send_data() adds 4-byte header (02 00 00 00)
if remainder:
packet += bytearray(16 - remainder)
return packet
def fill_buffer(self, pos, bits, value):
"""
Fill buffer
"""
i = bits
while i > 0:
index = 2 + 2 * (pos + bits-i)
self.codes[index] = ZERO_AND_ONE_HIGH
if test_bit(value, i - 1) != 0:
self.codes[index + 1] = ONE_LOW
else:
self.codes[index + 1] = ZERO_LOW
if test_bit(value, i - 1) != 0:
bitset = 0
bitset = set_bit(bitset, (128 + i - pos - bits - 1) % 4)
self.crc = self.crc + bitset
i -= 1
# ------------------- PID CONTROLLER --------------------------------------------
class PID:
"""PID Controller
"""
def __init__(self, P=0.2, I=0.0, D=0.0):
self.Kp = P
self.Ki = I
self.Kd = D
self.sample_time = 0.00
self.current_time = time.time()
self.last_time = self.current_time
self.clear()
def clear(self):
"""Clears PID computations and coefficients"""
self.SetPoint = 0.0
self.PTerm = 0.0
self.ITerm = 0.0
self.DTerm = 0.0
self.last_error = 0.0
# Windup Guard
self.int_error = 0.0
self.windup_guard = 20.0
self.output = 0.0
def update(self, feedback_value):
"""Calculates PID value for given reference feedback
.. math::
u(t) = K_p e(t) + K_i \int_{0}^{t} e(t)dt + K_d {de}/{dt}
.. figure:: images/pid_1.png
:align: center
Test PID with Kp=1.2, Ki=1, Kd=0.001 (test_pid.py)
"""
error = self.SetPoint - feedback_value
self.current_time = time.time()
delta_time = self.current_time - self.last_time
delta_error = error - self.last_error
if (delta_time >= self.sample_time):
self.PTerm = self.Kp * error
self.ITerm += error * delta_time
if (self.ITerm < -self.windup_guard):
self.ITerm = -self.windup_guard
elif (self.ITerm > self.windup_guard):
self.ITerm = self.windup_guard
self.DTerm = 0.0
if delta_time > 0:
self.DTerm = delta_error / delta_time
# Remember last time and last error for next calculation
self.last_time = self.current_time
self.last_error = error
self.output = self.PTerm + (self.Ki * self.ITerm) + (self.Kd * self.DTerm)
def setKp(self, proportional_gain):
"""Determines how aggressively the PID reacts to the current error with setting Proportional Gain"""
self.Kp = proportional_gain
def setKi(self, integral_gain):
"""Determines how aggressively the PID reacts to the current error with setting Integral Gain"""
self.Ki = integral_gain
def setKd(self, derivative_gain):
"""Determines how aggressively the PID reacts to the current error with setting Derivative Gain"""
self.Kd = derivative_gain
def setWindup(self, windup):
"""Integral windup, also known as integrator windup or reset windup,
refers to the situation in a PID feedback controller where
a large change in setpoint occurs (say a positive change)
and the integral terms accumulates a significant error
during the rise (windup), thus overshooting and continuing
to increase as this accumulated error is unwound
(offset by errors in the other direction).
The specific problem is the excess overshooting.
"""
self.windup_guard = windup
def setSampleTime(self, sample_time):
"""PID that should be updated at a regular interval.
Based on a pre-determined sampe time, the PID decides if it should compute or return immediately.
"""
self.sample_time = sample_time
+1 -1
View File
@@ -273,4 +273,4 @@ class Remote(object):
bitset = set_bit(bitset, (128 + i - pos - bits - 1) % 4)
self.crc = self.crc + bitset
i -= 1
i -= 1
@@ -0,0 +1,47 @@
import appdaemon.plugins.hass.hassapi as hass
#
# Water leak detector
# close water valve and send a report
#
# Args:
#
# sensor = water leak sensor group
# valve = water valve - app will close it, when leak is detected
# notify = notify name to send notification
#
# None
#
# Release Notes
#
# Version 1.0:
# Initial Version
class ToiletPresence(hass.Hass):
state = ""
def initialize(self):
if 'sensor' not in self.args:
self.error("Please provide sensor in config!")
return
self.listen_state(self.distance_changed, self.args['sensor'])
def distance_changed(self, entity, attribute, old, new, kwargs):
distance = int(new)
if distance > 1000 or distance < 850:
if self.state == "" or self.state == "off":
self.set_value("on")
elif distance < 1000 and distance > 850:
if self.state == "" or self.state == "on":
self.set_value("off")
def set_value(self, state):
self.log('Toilet presense: {}'.format(state))
self.state = state
entity_id = "sensor.toilet_presence"
attributes = {}
attributes['description'] = 'Created and updated from appdaemon ({})'.format(__name__)
self.set_state(entity_id, state = state, attributes = attributes)
@@ -0,0 +1,13 @@
#################################
# WATER LEAK #
#################################
toilet_presence:
module: toilet_presence
class: ToiletPresence
# constraint: input_boolean.water_leak_control
sensor: sensor.toilet_distance
# valve: switch.water_valve
# notify: telegram
# gw_mac: !secret aqara1_mac
global_dependencies:
- globals
+10 -5
View File
@@ -17,7 +17,8 @@ import datetime
class AirFresher(hass.Hass):
timers = []
fresh_times = 0
fresh_every_mins = 15
fresh_times_max = 5
fresh_every_mins = 1
last_fresh_date = None
def initialize(self):
@@ -41,8 +42,8 @@ class AirFresher(hass.Hass):
self.log('Start freshing timer.')
self.last_fresh_date = self.datetime()
self.fresh_times = 0
newtime = self.datetime()+datetime.timedelta(seconds=5)
self.timers.append(self.run_every(self.timer_tick, newtime, 4*60))
newtime = self.datetime()+datetime.timedelta(seconds=15)
self.timers.append(self.run_every(self.timer_tick, newtime, self.fresh_every_mins*60))
def cancel_timers(self) -> None:
for timer in self.timers:
@@ -50,15 +51,19 @@ class AirFresher(hass.Hass):
timers = []
def timer_tick(self, args) -> None:
self.fresh()
toilet_presence = self.get_state("sensor.toilet_presence")
self.log('Timer ticked, presence is: {}'.format(toilet_presence))
if (toilet_presence == "on"):
self.fresh()
def fresh(self):
self.log("current timer: {}".format(self.last_fresh_date))
self.fresh_times = self.fresh_times + 1
if (self.fresh_times > 5):
if (self.fresh_times > self.fresh_times_max):
self.log('Fresh max times is reached. Canceling timer.')
self.cancel_timers()
return
self.log('freshing in {}'.format(self.fresh_times))
self.call_service("mqtt/publish", topic = "home/airfresher/airfresher/fresh/set", payload = "1")
+3 -2
View File
@@ -64,14 +64,15 @@ homekit: !include configuration/homekit/homekit.yaml
#power meter
modbus: !include configuration/modbus.yaml
mysensors: !include configuration/mysensors.yaml
#mysensors: !include configuration/mysensors.yaml
utility_meter: !include_dir_merge_named configuration/utility_meter
speedtestdotnet: !include configuration/speedtest.yaml
yandex_smart_home: !include configuration/alice.yaml
# fan:
# - platform: xiaomi_miio
# host: 192.168.1.14
# token: '411cf70bb69052c0121813388f30e193'
# model: zhimi.airpurifier.mc1
# model: zhimi.airpurifier.mc1
+18
View File
@@ -0,0 +1,18 @@
filter:
include_domains:
- alarm_control_panel
- switch
- light
- vacuum
- cover
exclude_entities:
- switch.plug_158d00020f3a29
- switch.plug_158d00026ea017
- switch.xiaomi_miio_switch
# include_entities:
# - media_player.tv
# entity_config:
# switch.kitchen:
# name: CUSTOM_NAME_FOR_YANDEX_SMART_HOME
# light.living_room:
# room: LIVING_ROOM
+4 -4
View File
@@ -1,4 +1,4 @@
gateways:
- device: !secret mysensors_gateway_ip
tcp_port: 5003
version: '2.0'
#gateways:
# - device: !secret mysensors_gateway_ip
# tcp_port: 5003
#version: '2.0'
@@ -0,0 +1,6 @@
airfresher_reset:
sequence:
- service: mqtt.publish
data:
topic: 'home/airfresher/airfresher/remain/set'
payload: 1500
@@ -10,4 +10,4 @@
- platform: template
sensors:
airfresher_remain_percent:
value_template: "{{ ((100 / 1400) * (states('sensor.airfresher_remain') | int )) | round(2) }}"
value_template: "{{ ((100 / 1400) * (states('sensor.airfresher_remain') | int )) | round(2) }}"
@@ -0,0 +1,4 @@
- platform: mqtt
state_topic: 'home/bath/bath/distance'
name: 'toilet_distance'
unit_of_measurement: 'cm'
Submodule hass/settings/custom_components/yandex_smart_home added at a323a790d1
+1
View File
@@ -52,6 +52,7 @@ views:
- light.bath
- light.philips_led
- sensor.airfresher_remain
- sensor.toilet_presence
- type: entities
title: "Прихожая"
show_header_toggle: false