Email or username:

Password:

Forgot your password?
Joinny Hash

Написал стрёмный формат конфига для вентиляторов и его интерпретатор и сижу довольный.

Пока в голове пара улучшений для sub-rules:
- поддержка hours: from, to
- консистентные minutes: from, to
- да и в rules можно вынести from-to в секцию time

Ещё бы код обобщить для rules и sub-rules который определяет, работает ли сейчас правило или нет.

Концепция action прикольной получилась, просто прозрачно последовательно пробрасывается в mqtt как topic=$devicename$topic, payload=value.

vakiobusy:
before_work:
from: '09:00'
to: '09:15'
action:
state: on
workmode: recuperator
speed: 4

at_work:
from: '09:15'
to: '18:00'
sub_rules:
before_meetings:
hours:
- 12
- 13
- 14
- 15
from_minute: 55
to_minute: 59
action:
speed: 7

meetings:
hours:
- 12
- 13
- 14
- 15
from_minute: 0
to_minute: 15
action:
speed: 1

not_meetings:
from_minute: 16
to_minute: 54
action:
speed: 3

fallback:
action:
speed: 3

after_work:
from: '18:00'
to: '22:59'
action:
speed: 3

night:
from: '23:00'
to: '08:59'
action:
state: off

vakiorest:
before_sleep:
from: '20:00'
to: '21:59'
action:
state: on
speed: 3

max_vent_before_sleep:
from: '22:00'
to: '22:15'
action:
speed: 7

sleep:
from: '22:15'
to: '19:00'
action:
state: off

А так, написал демонюгу, который раз в минуту выискивает в конфиге действующие правила и подправила и выполняет их, в случае если локальная копия состояния девайса отличается.

12 comments
iliazeus

@strizhechenko мне кажется, с таким уровнем сложности правил проще было бы кодом их задавать. Хотя бы на том же питоне, чтобы раз в минуту запускался просто, проверял условия и выдавал нужное состояние.

iliazeus

@strizhechenko что-то вроде такого, к примеру:

def vakiobusy(f, t):
# before work
if '09:00' <= t.time < '09:15':
f.state = 'on'
f.workmode = 'recuperator'
f.speed = 4

# at work
elif '09:15' <= t.time < '18:00':
# before meetings
if 12 <= t.hours <= 15 and 55 <= t.minutes <= 59:
f.speed = 7

# meetings
elif 12 <= t.hours <= 15 and 0 <= t.minutes <= 15:
f.speed = 1

else:
f.speed = 3

# after work
elif '18:00' <= t.time < '23:00':
f.speed = 3

# night
elif '23:00' <= t.time or t.time < '08:00':
f.state = off


def vakiorest(f, t):
# before sleep
if '20:00' <= t.time < '22:00':
f.state = 'on'
f.speed = 3

# max vent before sleep
elif '22:00' <= t.time < '22:15':
f.speed = 7

# sleep
elif '22:15' <= t.time or t.time < '19:00':
f.state = 'off'

@strizhechenko что-то вроде такого, к примеру:

def vakiobusy(f, t):
# before work
if '09:00' <= t.time < '09:15':
f.state = 'on'
f.workmode = 'recuperator'
f.speed = 4

# at work
elif '09:15' <= t.time < '18:00':
# before meetings
if 12 <= t.hours <= 15 and 55 <= t.minutes <= 59:
f.speed = 7

# meetings
elif 12 <= t.hours <= 15 and 0 <= t.minutes <= 15:
f.speed = 1

else:
f.speed = 3

Joinny Hash

Задеплоил эту херовину в LXC-контейнер на файлопомойке. Жрёт аж 25мб оперативки и около нуля проца, так что места хватает ещё где-то под 90 таких сервисов.

Нашёл #mqtt-explorer, жить сталое веселее.

Начал подключать к #mosquitto тёплый пол, управляемый #lytko 101, но там какая-то ебанина с веб-интерфейсом и он тупо не сохраняет настройки MQTT. Ей богу, лучше б SSH сделали и дали с конфигами в файлах пердолиться.

Joinny Hash

Совет от поддержки: на странице /index2.html сохранение работает. И правда. Но я бы хер догадался, конечно.

Топики для записи температуры и вкл/выкл нашёл в выхлопе для home assistant. В целом, управляются, так что на дачу куплю ещё два. Хоть они и некрасивые, гг. ну или красивые, но подороже.

Накидал под них такой конфиг:

floor:
sleep:
from: '00:00'
to: '05:59'
action:
mode: off

morning:
from: '06:00'
to: '11:59'
action:
mode: on
temperature: 23

day:
from: '12:00'
to: '19:59'
action:
temperature: 19

evening:
from: '20:00'
to: '23:59'
action:
temperature: 22

wall:
sleep:
from: '00:00'
to: '05:59'
action:
mode: off

morning:
from: '06:00'
to: '11:59'
action:
mode: on
temperature: 22

day:
from: '12:00'
to: '19:59'
action:
mode: off

evening:
from: '20:00'
to: '23:59'
action:
temperature: 22

Но интерпретатор пока его парсить (и подключать в зависимости от девайса нужный клиент) не научил. Хорошо с самопалом, пока можно хардкодить и не усложнять.

Совет от поддержки: на странице /index2.html сохранение работает. И правда. Но я бы хер догадался, конечно.

Топики для записи температуры и вкл/выкл нашёл в выхлопе для home assistant. В целом, управляются, так что на дачу куплю ещё два. Хоть они и некрасивые, гг. ну или красивые, но подороже.

Накидал под них такой конфиг:

Joinny Hash

Дорефачил формат конфига до более компактного, докрутил всё под тёплый пол, задеплоил, через 10 минут пойду наслаждаться заранее прогретым под чистку зубов и умывание полом и тёплыми полотенцами от заранее прогретой стены, которые сами выключатся.

Сделал кэширование стейта, чтобы не долбить устройства сообщениями. Прикрутил логику для рулёжки выходной/будний день (или обои). Завтра, надеюсь за подписку на ручные изменения возьмусь и тогда ЗАЖИВУ.

Joinny Hash

Чот не понимаю я ничего в #asyncio в #python, похоже. Единственный способ нормально завершать несколько запущенных asyncio.Task с while True внутри и не сыпать кучей трейсбэков при KeyboardInterrupt оказался:

asyncio.run(func)

def func():
...
await asyncio.wait([
asyncio.create_task(task1()),
asyncio.create_task(task2())
])

Любые другие приводили либо к мусору, либо к бесконечному повисанию, я явно что-то делал не так.

Что пробовал:

1. Gather

def func():
...
await asyncio.gather(
asyncio.create_task(task1()),
asyncio.create_task(task2())
)

2. Обработку сигналов и ручная отмена тасок

loop.add_signal_handler(sigint, cancel_tasks, [t1, t2]))

def cancel_all(tasks: list[Task]):
for task in tasks:
if task != asyncio.current_task() and not task.done():
task.cancel()

3. Где-то посреди этого было явное создание эвент-лупа и попытки его останавливать, что-то типа:

if __name__ == '__main__':
loop = asyncio.new_event_loop()
loop.run_until_complete(func())
try:
loop.run_forever()
except KeyboardInterrupt:
loop.stop()

4. В том числе без каких-либо зацепок на событие, то есть таски просто создавались и.. ну сдохнут и сдохнут:

def func():
...
asyncio.create_task(task1())
asyncio.create_task(task2())

Конкретно ругань уже не помню, а откатываться к падающему состоянию не хочу, но что-то про то что task was destroyed but it was pending. Типа надо дождаться пока они на следующих тиках эвентлупа нормально отработают asyncio.CancelledError и только после этого уже loop стопать. Но мне чот не повезло и дебаггером побегать по коду обработки сигнала не вышло, он тупо завершался, вот я хер и забил.

Теперь чувствую себя джуном :c

Чот не понимаю я ничего в #asyncio в #python, похоже. Единственный способ нормально завершать несколько запущенных asyncio.Task с while True внутри и не сыпать кучей трейсбэков при KeyboardInterrupt оказался:

asyncio.run(func)

def func():
...
await asyncio.wait([
asyncio.create_task(task1()),
asyncio.create_task(task2())
])

Rubikoid

@strizhechenko «просто создавать таски» из 4 пункта нельзя.
За ними может прийти GC и они испарятся.
Что у тебя и происходит, вроде как.

Вариант с gather наиболее логичный и он должен работать.
Проверь параметры gather’а, он умеет обрабатывать исключения.

Если совсем правильно делать, конечно, то надо в каждой таске ловить и обрабатывать cancelerror, ну и кэнселить их.

Плюс у тебя await’ы в некоторых местах в синхронных функах, это точно то?)

@strizhechenko «просто создавать таски» из 4 пункта нельзя.
За ними может прийти GC и они испарятся.
Что у тебя и происходит, вроде как.

Вариант с gather наиболее логичный и он должен работать.
Проверь параметры gather’а, он умеет обрабатывать исключения.

Если совсем правильно делать, конечно, то надо в каждой таске ловить и обрабатывать cancelerror, ну и кэнселить их.

[DATA EXPUNGED]
Rubikoid

@strizhechenko доехал домой, добрался до репла питона, проверил.

В общем случае, SIGINT у тебя вызывает KeyboardException в текущей исполняющейся строке питона.

Для asyncio есть несколько вариантов развития событий, в зависимости от того, чем занимается луп прямо сейчас.

Вариант 1: луп исполняет какой-то синхронный код. Тогда это исключение вылетит на этот синхронный код, провалится наверх до gather'а, и дальше в зависимости от параметров gather'а будет обрабатываться.

Вариант 2: луп что-то ждёт. В этом случае оно прилетит в сам луп, и луп тебе отменит все корутины, какие найдет (в том числе все таски твои).
В итоге await gather кинет исключение и оно поедет наверх.

Вот на этом можно поиграться: yaso.su/dAlePnTi

Учитывай, что CancelledError наследуется от BaseException, и через обычный except Exception его не поймать.

@strizhechenko доехал домой, добрался до репла питона, проверил.

В общем случае, SIGINT у тебя вызывает KeyboardException в текущей исполняющейся строке питона.

Для asyncio есть несколько вариантов развития событий, в зависимости от того, чем занимается луп прямо сейчас.

Вариант 1: луп исполняет какой-то синхронный код. Тогда это исключение вылетит на этот синхронный код, провалится наверх до gather'а, и дальше в зависимости от параметров gather'а будет обрабатываться.

Joinny Hash

Научил наконец демонюгу следить за подписками, инициировать из них свой стейт и отправлять команды только по необходимости. Осталось придумать как это добро отличать от ручного переключения (скорее всего достаточно запомнить время подписки на топик) и что и насколько блокировать. В теории, если я кнопками на приборе поменял скорость в 22:35, на расписание выключения в 23:00 оно влиять не должно. А вот скорость лучше сохранять.. некоторое время. Пока склоняюсь к четырём часам.

Переключился на протокол v5, судя по wireshark он умеет группировать пакеты в один, типа подтвердить подписку + отправить текущее значение топика. Но делает это рандомно. Но вроде работает.

Научил этого демонюгу управлять светильниками в кабинете. Светильники в MQTT не умеют, но умеют в JSON (ну почти RPC) over TCP. Базово разобрался с вкл/выкл, но вроде можно и цвет + температуру настроить, вот тогда заживём (в смысле можно будет выход в инет им заблочить уже наконец), до этого после отрубания у них происходил сброс настроек до какого-то раздражающего красного цвета.

Нашёл наконец где вебка у рекуператора в детской. Как оказалось, я перепутал его с водонагревателем, когда создавал статические записи в DHCP. А вот с водонагревателем сложно. Я обновил его до 1.25 по глупости и теперь способ его хака с подсовыванием своего брокера с помощью DNS-hijacking не прокатывает. Клятi rusclimat =/

Научил наконец демонюгу следить за подписками, инициировать из них свой стейт и отправлять команды только по необходимости. Осталось придумать как это добро отличать от ручного переключения (скорее всего достаточно запомнить время подписки на топик) и что и насколько блокировать. В теории, если я кнопками на приборе поменял скорость в 22:35, на расписание выключения в 23:00 оно влиять не должно. А вот скорость лучше сохранять.. некоторое время. Пока склоняюсь к четырём часам.

Rubikoid

@strizhechenko а какой-нибудь homeassistant не подходит?

Кажется, что ты его сидишь изобретаешь сейчас.

Go Up