Перейти к содержанию

Изменения

Формат основан на Keep a Changelog, версионирование — семантическое.

1.4.4 — 2026-08-07

Шестая ночь — первая на 1.4.3 и самая спокойная: 37 прогонов, заряд 67 → 99 % к концу окна и 100 % вскоре после. Правки 1.4.3 подтвердились: шлейф «машина не дома» после финиша исчез, журнал больше не обещает применения там, где знает только об отправке. Регулирование снова трогать не пришлось — уставка 13 А продержалась с 23:30 до 05:00, и это не залипание, а зона нечувствительности, погасившая колебания плана в пределах 12.0–12.8 А.

Оба исправления ниже нашлись потому, что пользователь сдвинул начало окна на 23:30. Ни одно из них к этой настройке не привязано.

Исправлено

  • Два триггера, совпавшие по времени, слали одну и ту же команду дважды. Круглое время старта попадает на сетку пересчёта: window_start и tick дали два прогона с разницей 204 мс, и оба успели дойти до number.set_value. Режим restart не спасает — первый прогон отправляет команду раньше, чем второй его вытесняет. В станцию ушли две одинаковые записи за пятую долю секунды: ровно тот интервал, от которого заведён command_gap, и ровно то, чего дешёвые станции не переваривают.

    Троттлинг этого не видел принципиально. Он считает время по возрасту сущности станции, а та за 204 мс измениться не успела: второй прогон прочитал тот же current_age, что и первый, и счёл паузу выдержанной. Добавлен run_age — возраст предыдущего прогона самой автоматизации, который берётся из this.attributes.last_triggered. Снимок this делается до запуска, поэтому там всегда предыдущий прогон, а не текущий.

    Проверено на живом Home Assistant 2026.2.3 вместе с тремя его отказами, каждый из которых обработан: this равен none, пока сущность автоматизации не создана; last_triggered равен None при первом в жизни запуске, причём ключ существует — значит get со значением по умолчанию не спасает, а вычитание бросает TypeError; атрибута может не быть вовсе. Во всех трёх случаях блюпринт отвечает «давно», и троттлинг работает как раньше — свежая установка не откажется заряжать.

    Проверка добавлена и отдельным множителем к needs_write: исключение or not switch_on снимает троттлинг перед стартом сессии целиком, а гонка случилась именно там, при выключенной станции.

  • Блюпринт называл собственную сессию чужой прямо посреди её остановки. Ветка остановки гасит выключатель, а следом опускает флаг сессии. Флаг — локальный input_boolean, он падает мгновенно; станция подтвердила выключение через 43 секунды. В этом зазоре выключатель ещё on, а флаг уже off — в точности признак ручной зарядки, и прогон 06:55:28 в него попал.

    В ту ночь обошлось: гасили по рапорту «заряжено», а он входит в список причин, действующих независимо от владельца. Останавливай мы по концу окна — повторная команда уже не ушла бы, потому что must_stop требует not foreign_session. Проглоти станция единственный turn_off, зарядка лилась бы дальше, а блюпринт до утра звал бы её чужой и не трогал: выхода из этого состояния нет, уборка флага требует выключенного выключателя.

    Добавлен finishing_own_stop — узкий признак «выключатель свежий, значит это след нашей же только что отправленной команды». Ослаблять сам foreign_session нельзя, и это проверено: выдержка там снимает защиту ручной зарядки в первую же секунду после обрыва связи, потому что обрыв омолаживает выключатель, и блюпринт начинает переписывать человеку его ток — ровно то, против чего флаг и заведён.

  • Обрыв связи снимал щадящий финиш вместе с выключателем. Ограничение «на последних процентах ток не поднимаем» действовало, пока выключатель включён, — а обрыв уводит его не в off, а в unavailable. В шестую ночь прогон 06:31:06 при заряде 99 % посчитал 18 А вместо разрешённых 14, и уберегло только то, что станция была офлайн и команды не слались. Оборвись один выключатель при живой станции — в почти полную батарею ушло бы 28 А, ровно то, от чего щадящий финиш и заведён.

    Теперь потолок снимается только при подтверждённом off, то есть перед началом новой сессии. Это то, ради чего условие и существовало: новая сессия не должна наследовать вчерашнюю уставку как потолок.

Инструменты

  • Отчёт по трассировкам замечает гонку триггеров: два прогона, стартовавших меньше чем за секунду друг от друга и пославших станции одну и ту же команду. Очередь «уставка → включение» гонкой не считается — это штатное разнесение команд по разным прогонам.
  • ha_sim научился this и заворачивает ошибки рендера в TemplateError так же, как это делает Template.async_render в Home Assistant. Прежде эмулятор пропускал наружу собственные исключения Jinja, то есть на одном и том же сломанном шаблоне сообщал не тот тип ошибки, что продакшен, — дифференциальная сверка поймала это на this.attributes и на арифметике с пустым last_triggered.

1.4.3 — 2026-08-06

Пятая ночь обкатки прошла штатно: 40 прогонов против 65, заряд 69 → 100 %, цель взята через две минуты после конца окна. Правки 1.4.1 подтвердились — дребезг car_arrived исчез полностью (16 срабатываний → 0), а гейт свежести придержал ровно один преждевременный подъём вместо прежней раскачки.

Регулирование трогать не пришлось: КПД 88 попал точно (реальный на линейном участке 85–87 %), запас по току 10 % покрывает недодачу станции, щадящий финиш стоил 0.12 п.п. заряда. Исправления ниже — про диагностику и про одно обещание, которого блюпринт не выполнял.

Исправлено

  • Снято ложное обещание ловить проглоченную запись тока. Комментарий к триггеру current_written (а до слияния — к setpoint_stale) утверждал, что он замечает уставку, не изменившуюся после записи. Он этого не может: триггер на состояние в Home Assistant срабатывает только при смене значения, а for: отсчитывает время уже после перехода. Проглоченная команда не меняет ничего — события нет, отсчитывать нечего. Проверено на живом Home Assistant 2026.2.3: повторная запись того же значения даёт ноль срабатываний, и с not_to, и без него.

    В пятую ночь это стоило получаса регулирования: запись 13 А в 03:30 пропала, current_age рос 1584 → 3384 → 5184 с без сброса, ток стоял на 12 А, и повтор ушёл только с тиком в 04:00. Механизм повтора при этом исправен — не хватало лишь пробуждения.

    Триггер command_missed на выключателе продолжает работать, и теперь в комментарии объяснено, почему у него получается: off — состояние покоя, в которое выключатель попадает настоящим переходом, и отсчёт уже идёт к моменту пропажи команды. «Неверный ток» состоянием покоя не является, поэтому того же приёма для уставки не существует.

  • «Машина не дома» больше не перехватывает чужие причины. Вердикт объясняет местоположением ровно две вещи: почему гасим идущую зарядку и почему не начинаем новую. В пятую ночь трекер ушёл в not_home за 48 минут до конца окна и не вернулся; пока шёл ток, защита держала сессию (это её первое живое подтверждение), но после штатной остановки шесть прогонов подряд объясняли бездействие уехавшей машиной — при воткнутом кабеле и заряде 100 %.

  • пишем ток вместо меняем ток. Строка печаталась по факту отправки команды, а выглядела как свершившееся изменение: в пятую ночь «меняем ток 12.0 → 13.0 А» напечаталось дважды, в 03:30 и в 04:00, хотя ток сменился один раз. Отличить повтор от первой отправки внутри одного прогона нечем — блюпринт не помнит прошлый запрос, — поэтому исправлена формулировка, а не логика.

  • trace_report.py больше не советует поднимать запас по току впустую. Недодача измерялась в амперах и сравнивалась с параметром-множителем: инструмент писал «недодаёт 1.52 А, стоит поднять запас» при запасе 10 %, который эту недодачу уже покрывал. Теперь считается доля, а замеры в момент остановки отбрасываются — один такой (14 А против 4.8 А на спаде тока) поднимал среднее с 1.25 до 1.52 А.

  • В комментарии к current_shortfall уточнены цифры разрыва по четырём ночам: 1.10 А при уставке 11 и 1.38 при 14 — рядом с прежними 1.31 при 16 и 1.34 при 19. Вывод не изменился (разрыв постоянен в амперах, потому множитель и работает), но теперь он опирается на диапазон вдвое шире.

Документация

  • Из текстов убрано предположение, что зарядка обязательно ночная. Блюпринт работает по окну, а окно бывает любым: дневной избыток солнечных панелей, рабочая парковка, дозарядка перед поездкой. Формулировки вроде «перед первой ночью», «к утру от зарядки не остаётся ничего», «ток падает до минимума на всю ночь» заменены на «перед первым запуском», «к концу сессии», «на всю сессию» — всего около сорока мест в README, документации и описаниях параметров.

    Намеренно оставлены четыре случая, где ночь означает именно ночь: ночная зона двухтарифного счётчика, переход окна через полночь, имена файлов ночных регрессий и фактические записи о реальных ночах обкатки, на которых найдены дефекты, — это свидетельства, а не обороты речи. - Из блюпринта появились ссылки в документацию. Ссылка на сайт добавлена в общее описание, а девять параметров, у которых есть что пояснить сверх двух абзацев, ведут прямо в нужный раздел: КПД, запас по току, щадящий финиш, зона нечувствительности, пауза между командами, флаг сессии, требование зоны, детектор замороженных данных и три хука уведомлений. Подробности остаются в одном месте, а из интерфейса до них один клик. - В описание «Сколько минут неизменного процента считать заморозкой» добавлено недостающее следствие: детектор работает только при подтверждённо идущей зарядке — без сенсора мощности или заполненных статусов «идёт зарядка» он не сработает никогда. Тихий отказ, о котором нужно знать при настройке. - Предупреждение про лимит трассировок сведено в одно место. Оно жило в четырёх файлах, и stored_traces: 100 пришлось бы менять в каждом. Теперь конкретика — только в разделе «Диагностика → Трассировки», а остальные три места объясняют, зачем это нужно, и ссылаются туда. - В быстром старте два соседних списка «перед первой ночью» и «что проверить до первой ночи» слиты в один: первый пункт у них совпадал дословно. - Описание входа «КПД зарядки» сокращено вдвое: расчёт своего значения и оговорка про сенсор мощности живут в документации, в блюпринте осталась суть и следствие неверной настройки.

Тесты

  • Добавлена сверка эмулятора с настоящим Home Assistant (tests/differential_against_home_assistant.py). Весь набор работает на переписанном движке шаблонов и потому не может сам заметить, что тот разошёлся с оригиналом: он и есть тот, кто в него верит. Скрипт рендерит 63 шаблона обоими движками и сверяет результаты. Проверено, что он ловит настоящий дрейф: возвращённая на место старая ошибка is_state немедленно даёт красный. В CI информационный, как и сверка схемы — внутренние API Home Assistant стабильности не обещают.
  • Ссылки из блюпринта проверяются тестом (test_every_docs_link_in_the_blueprint_resolves): блюпринт не собирается mkdocs, поэтому --strict этих ссылок не видит, и битый якорь остался бы незамеченным до первого клика в интерфейсе.
  • Починена сама сверка движков — первый же прогон в CI упал. Скрипт брал State из conftest, а тот импортирует pytest, которого в этой джобе нет: она ставила только Home Assistant. Одного отсутствующего пакета хватило, чтобы все 63 сравнения дали <raises ModuleNotFoundError> — в отчёте это выглядело как полное расхождение движков, хотя не выполнилось вообще ничего. State берётся из ha_sim напрямую, джоба ставит и requirements-dev.txt, а сам скрипт теперь загружает эмулятор до сравнений и на отсутствующей зависимости говорит об этом одной строкой с кодом возврата 2, вместо того чтобы выдавать её за расхождение.

1.4.2 — 2026-08-05

Ревизия кода без новой ночи. Главное — дыра в защите от раскачки тока, добавленной в 1.4.1: она гасила подъём не всегда, а только пока расчёт не дорастал до потолка.

Исправлено

  • Защита от раскачки тока больше не обходится на потолке. В want_write исключение для граничного тока (at_boundary) стояло без внешних скобок, а and в Jinja связывает крепче or — то есть исключение становилось самостоятельным условием и проверку свежести процента не проходило вовсе. В трассировках третьей ночи это видно построчно: 06:32–06:35 уставка прошла 16 → 20 → 23 → 27 → 28 А, каждый шаг разбужен собственной предыдущей записью, а процент всё это время стоял на 99. Защита гасила первые два шага и пропускала остальные. Теперь свежесть требуется для любого подъёма, включая выход на потолок; снижение, ради которого исключение и заведено, работает по-прежнему.
  • tools/trace_report.py больше не падает трейсбеком на постороннем файле. Вложенный ключ trace проверялся на наличие, но не на тип: строка или список вместо объекта роняли разбор уже после того, как отчёт начинал печататься. Ровно тот случай, для которого написана соседняя проверка: инструмент запускают на папке загрузок, где лежит чужой JSON.
  • Сверка тега с версией в CI действительно выполняется. Джоба packaging сравнивала тег с версией блюпринта, но workflow не запускался на push тега (фильтр branches тегам не соответствует), поэтому проверка не срабатывала никогда.
  • Проверка расположения блюпринта сравнивает полный путь, а не имя файла: переименование каталога слага оставляло имя прежним, ссылка на импорт вела на 404, а CI оставался зелёным.
  • Ссылка на диагностику в шаблоне баг-репорта вела на несуществующий якорь в README (раздел переехал на сайт документации).

Упрощено

  • physically_present перестал дублировать charging_now. Оба выражения слово в слово повторяли одно и то же определение «ток идёт», отличаясь одной ведущей веткой. Порог записан теперь в одном месте: от него зависят и вывод «машина уехала», и детектор замороженных данных, и правка одной копии без второй разводила их между собой.
  • Проверка здоровья батареи (soh_rawsoh_scaledsoh_pct) свёрнута в одну переменную: три шага существовали ради одного значения и наружу не показывались.

Тесты

  • Эмулятор: is_state для отсутствующей сущности возвращал True при сравнении со строками, которые states() подставляет вместо неё, тогда как настоящий Home Assistant в этом случае всегда даёт False. Эмулятор был мягче настоящего HA — ровно та ошибка, которую этот файл призван исключать. Заодно поддержан список состояний, как в HA.
  • Добавлены регрессии на реальные числа третьей ночи (лестница к потолку по замороженному проценту) и мутация, стерегущая скобки в want_write.
  • Закрыты семь дыр в покрытии, найденных мутационным перебором: каждая означала защиту, которую можно было удалить, не уронив ни одного теста. Самая опасная — проверка soc_valid в аварийной дозарядке: без неё soc = -1 (признак негодных данных) оказывается ниже любого порога, и потерявшая авторизацию интеграция машины читается как «батарея разряжена» — с запросом максимального тока среди дня, вне окна. Остальные шесть: режим станции в чужой сессии, троттлинг команды режима, выдержка перед сбросом флага сессии, порог «ток идёт» против потребления станции в простое, и пол в 20 кВт·ч у проверки счётчика энергии (без него законный сессионный счётчик PHEV отвергался бы).
  • Убрана тавтология в проверке слепка diag и усилен тест про остановку по достигнутой цели: он проверял только сам факт достижения.

Документация

  • Исправлены расхождения документации с кодом: несуществующий ключ заявлено в описании слепка diag (заряжать и остановить лежат в нём по отдельности), неверное название галки «Требовать нахождения дома», предел тока в SECURITY.md (32 А против 80 А по селектору), условие немедленного включения, и таблица тестов, отставшая от репозитория на три файла.

1.4.1 — 2026-08-05

Четвёртая ночь, первая на 1.4.0. Механизмы прошлой ночи заработали, и вместе с ними вылезла их цена: 65 прогонов вместо 42, половина из них впустую, и автоколебание тока под утро.

Исправлено

  • Ток больше не раскачивается сам себя. В 05:24–05:27 уставка прошла 18 → 19 → 20 → 17 → 18 А за четыре минуты, и ни одно из четырёх изменений не несло информации. Механизм: знаменатель плана (hours_left) тает непрерывно, а числитель обновляется скачком, когда машина сообщит новый процент. Между сообщениями расчёт растёт сам по себе примерно на четверть ампера в минуту, а каждая запись будила автоматизацию через паузу между командами — и она писала снова. Теперь подъём тока требует, чтобы процент заряда обновился после нашей же последней записи; снижение по-прежнему проходит сразу. Сравнение именно с возрастом уставки, а не с абсолютным порогом: в первую реальную ночь процент обновлялся раз в пять минут при пересчёте раз в полчаса, и все подъёмы там были настоящими.
  • Триггер приезда домой больше не срабатывает на дрожание координат. 16 прогонов за ночь, и во всех машина стояла дома там же, где и всю ночь. Home Assistant переоценивает шаблонный триггер при любом обновлении device_tracker, включая обновление одних лишь координат, и состояние успевает мигнуть. Добавлена выдержка в 30 секунд — настоящий приезд её переживает, дребезг нет. То же и для триггера достижения цели: сенсор заряда облачный, его атрибуты меняются чаще самого процента.
  • Накопительный счётчик энергии распознаётся после сброса. Защита от перепутанных сенсоров отвергала слишком большие показания, но переподключение электричества обнуляет счётчик, и дальше он снова выглядит правдоподобно, оставаясь накопительным. Теперь проверяется и второй признак, от величины не зависящий: сессионный счётчик перед началом зарядки стоит на нуле, накопительный — нет.

Упрощено

  • Два триггера на уставке тока схлопнуты в один. setpoint_stale за целую ночь не сработал ни разу: current_written всегда успевал раньше, и его прогон либо переписывал уставку, либо перезапускал отсчёт. Проверку проглоченной записи делает теперь тот же прогон.

Что стоит поменять в настройках

Реальный КПД четвёртой ночи — 88.8 % (35.38 кВт·ч из розетки на 31.42 кВт·ч в батарею по счётчику, сброшенному накануне), а выставлено было 75 %. План требовал 42.49 кВт·ч вместо нужных ~35.9 — переоценка в 1.18 раза. Именно поэтому ночь открылась на 28 А и весь остаток шла на снижение. Оценка 74.6 % из прошлого разбора получена интегрированием сенсора мощности по редким отсчётам и, судя по всему, занижала: прямому показанию счётчика доверия больше.

1.4.0 — 2026-08-04

Третья ночь на реальном железе. Машина зарядилась до 100 %, но полчаса в начале пропали впустую, а под утро регулятор гнал ток с 13 до 28 А за шесть минут.

Исправлено

  • Потерянная команда включения больше не стоит полчаса. В 23:06 команда switch.turn_on ушла в станцию, и станция её проглотила: выключатель остался off. Промах команды не порождает события состояния, а все триггеры блюпринта были либо часами, либо изменением чужой сущности — поэтому повтор случился только на очередном пересчёте в 23:30, через 24 минуты. Добавлен триггер command_missed: выключатель, пролежавший в off дольше паузы между командами, поднимает прогон, и включение повторяется в следующую минуту.
  • Потерянная запись уставки тоже больше не стоит полчаса. Тем же способом станция теряла и уставку: 11 → 12 писалось в 23:30, 00:00 и 00:30, подействовала только третья запись (возраст уставки при этом рос 4761 → 6561 → 8361 с — она не менялась). То же с 13 → 16 в 03:00 и 03:30. Добавлен триггер setpoint_stale на застоявшуюся уставку. Ждёт вдвое дольше паузы между командами, иначе срабатывал бы на каждой нормальной записи.
  • Обрыв связи больше не выглядит как «машина уехала». Признак физического присутствия («ток идёт — значит машина на месте») проверял выключатель условием «не включён», а недоступный выключатель под это подходит. В третью ночь трекер четыре часа врал not_home из-за дрейфа GPS — ровно тот случай, ради которого признак и заведён, — и одного секундного обрыва хватило, чтобы «машина уехала» стало правдой при живых 2616 Вт на сенсоре мощности. Зарядку тогда спасло лишь то, что в тот же миг была недоступна и сама станция. Теперь, как и в остальных местах после 1.3.0, разрушительный вывод делается только по подтверждённо выключенному выключателю.
  • Недодача тока больше не выдумывается на ровном месте. Сенсор тока станции обновляется с задержкой: под утро он стоял на 11.669 А целых семь минут, пока мощность росла 2592 → 5683 Вт, и «недодача» насчитывалась до 16 А. Теперь величина считается только на установившемся режиме — все завышенные значения в трассировках пришлись ровно на свежую уставку.

Добавлено

  • «Запас по току, %» (по умолчанию 10). Станция почти всегда выдаёт меньше заказанного: на уставке 11 А шло 9.9 А. Планировщик считал по уставке и весь вечер отставал незаметно для себя — пока до конца окна далеко, недобор делится на большой остаток времени и тонет в шаге тока. К утру остаток сжимался, и тот же недобор требовал разгона до максимума за минуты. Запас поднимает ток с самого начала, и к утру он плавно снижается вместо рывка. Потолок тока запас не пробивает.

Реализован множителем, а не прибавкой: прибавка сравнивалась бы с уставкой в зоне нечувствительности и переписывала бы её на каждом пересчёте. - «Не повышать ток начиная с заряда, %» (по умолчанию 95). На последних процентах BMS сам ограничивает приём, и лишний заказанный ток уже не ускоряет зарядку, а греет батарею. Выше порога уставку можно только снижать. 100 — выключено. Не действует при недостоверных данных о заряде, в мороз и при аварийной дозарядке — там ток нужен весь.

Изменено

  • Описание параметра «КПД зарядки» дополнено формулой расчёта. Раньше предлагались только ориентиры «зимой 80–85, летом 90–93», а подобрать своё число было не из чего. Теперь описано, какие два измерения нужны и как из них посчитать КПД, и явно разделено, что относится к КПД, а что — к недодаче станции (для неё появился отдельный параметр).
  • Мутационный прогон распараллелен. Каждая мутация требует отдельного запуска всего набора, поэтому семь десятков мутаций подряд занимали около получаса. Мутации независимы, и каждый воркер работает в своей копии репозитория, поэтому pytest -m slow -n auto даёт несколько минут вместо получаса. Добавлена зависимость pytest-xdist, CI обновлён.
  • Документация переехала на сайтsaippuakauppias.github.io/ha-ev-smart-charging. README похудел с 743 строк до сотни: он остался витриной, а подробности разложены по разделам с поиском и навигацией. IDEAS.md и docs/EXAMPLES.md переехали туда же; CHANGELOG.md, CONTRIBUTING.md и SECURITY.md остались в корне и подключаются в сайт сниппетами, чтобы не расходиться в двух копиях.
  • Проект переименован в «EV Smart Charging», файл блюпринта — ev_smart_charging.yaml. Ссылка для импорта изменилась; у тех, кто уже установил блюпринт, автоматизация продолжает работать на локальной копии, но для обновлений нужно импортировать файл заново и поправить path: в автоматизации.
  • Добавлена кнопка импорта в Home Assistant — установка в один клик вместо копирования ссылки руками.

Удалено

  • hacs.json. HACS не поддерживает блюпринты: среди его категорий (integration, theme, template, python_script, appdaemon, plugin) блюпринтов нет, и в каталоге hacs/default нет файла, куда можно было бы подать заявку. Добавить репозиторий вручную тоже нельзя — при добавлении требуется категория. Файл создавал ложное впечатление, что установка через HACS возможна.

1.3.0 — 2026-08-03

Вторая ночь на реальном железе. Машина зарядилась до 100 %, но неправильно: семь часов из восьми автоматизация считала собственную зарядку чужой, не регулировала ток и не остановилась в конце окна — заряд лился ещё двадцать три минуты после 06:55.

Причина — двухсекундный обрыв связи со станцией. Все её сущности уходят в недоступность разом, а возвращаются по очереди, за четыре такта. В первом такте выключатель «не включён» — и уборка прочитала это как «зарядку выключили руками», погасив флаг сессии. Через полторы секунды выключатель вернулся, флаг остался опущенным, и выхода из этого состояния не было: поднять флаг умела только ветка включения, а она требует выключенной станции.

Исправлено

  • Обрыв связи больше не отдаёт сессию. Появилось различие между «выключатель выключен» и «выключателя нет на связи» (switch_off_confirmed). Сброс флага теперь требует подтверждённого выключения при живой связи, читаемом статусе и выдержанной паузе — мигание сущности под это не подходит.
  • Потерянный флаг возвращается. Если зарядка идёт, флаг опущен, а уставка сходится с расчётом, автоматизация забирает владение обратно (session_lost). Признак выбран по уликам, а не по времени: возрасты сущностей после обрыва неотличимы от любых других, а вот ток, совпадающий с планом, человек выставляет разве что случайно.
  • Конец окна больше не единственная жертва потерянного флага. Раньше все причины остановки висели на одном признаке, и ложная «чужая сессия» отключала их разом. Теперь неисправность, уехавшая машина, выдернутый кабель, достигнутая цель и рапорт «заряжено» останавливают зарядку независимо от того, кто её начал: к выбору человека они отношения не имеют. Ток и конец окна по-прежнему остаются за владельцем сессии.
  • Флаг сессии поднимается до включения станции, а не после. Включение само меняет статус — то есть срабатывает триггер и обрывает последовательность в режиме restart. Существовало окно, в котором станция уже включена, а владение не отмечено; это был второй, независимый путь к той же потере сессии, не требующий никакого обрыва связи.
  • Ложная неисправность при обрыве. Сенсор проблемы читается только при живой связи и читаемом статусе: при обрыве интеграция успевает отдать on там, где ничего не сломано. За ночь это трижды поднимало тревогу и вдобавок блокировало зарядку.
  • Недоступный флаг сессии больше не означает чужую сессию. При перезапуске Home Assistant input_boolean проходит через unknown, а триггер ha_start гарантирует прогон именно в этот момент — то есть каждый перезапуск посреди зарядки отдавал сессию.
  • Обрыв связи не порождает шторм прогонов. Переходы в недоступность отфильтрованы у триггеров статуса и уставки. Обратный переход остаётся: это единственный момент, когда можно узнать о восстановлении станции.
  • Уведомление «зарядка началась» больше не приходит на каждый обрыв. Команда включения теперь требует подтверждённо выключенного выключателя, а не просто «не включённого»: иначе она уходила в отсутствующую интеграцию, а вместе с ней срабатывал хук старта.
  • Моргание сущности не сбрасывает уставку заряжающейся машины. Уборка после остановки читала недоступный выключатель как выключенный и роняла ток к минимуму — до следующего пересчёта машина заряжалась на шести амперах.

Добавлено

  • Недодача тока видна в журнале и диагностике. Станция стабильно отдаёт меньше заказанного — на реальном железе 1.3 А при любой уставке, что стоит около восьми процентов плана. Величина измеряется и показывается (недодача 1.29 А), но не компенсируется автоматически: прибавка к расчёту попадает в сравнение с уставкой и заставила бы переписывать её на каждом пересчёте, а при машине, ограничившей приём, раскачивала бы ток до потолка. Вывод — занизить КПД в настройках — остаётся за человеком.
  • tools/trace_report.py подсказывает, что пошло не так. К журналу и списку команд добавилась секция «на что обратить внимание»: обрывы связи, периоды, когда автоматизация считала зарядку чужой, и средняя недодача тока. На разбор ночи это отвечает тремя строками вместо девяноста.

1.2.1 — 2026-08-02

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

Исправлено

  • Станция, округляющая уставку по-своему, получала одну и ту же команду до утра. Допуск на повышение тока был нулевым, а расхождение бывает неустранимым: станция с внутренним шагом 0.5 А на команду 28 А рапортует 27.5 и будет рапортовать так всегда. Автоматизация видела расхождение, писала те же 28 А, получала те же 27.5 — и так каждые 60 секунд. Допуск теперь равен половине шага станции; законное повышение на шаг проходит по-прежнему.
  • При пропавшем выключателе автоматизация продолжала работать вхолостую. Признак switch_present вычислялся и попадал в вердикт, но ни на что не влиял: команды уходили в никуда, ошибку глотал continue_on_error, хук старта отрабатывал. Теперь это останавливает работу и поднимает alarm_reason: switch_entity_missing.
  • Сброс уставки после остановки почти никогда не выполнялся. Он стоял за паузой delay: 5, а в режиме restart паузу обрывает любой триггер — в том числе смена статуса станции, которую вызывает само выключение строкой выше. Сброс вынесен в отдельный прогон и больше не зависит от того, доживёт ли последовательность до конца.
  • Флаг сессии залипал, если зарядку выключили не мы. Сброс стоял только в ветке остановки, а она недостижима при уже выключенной станции. После ручного выключения, самостоятельного отключения станции по неисправности или перезапуска Home Assistant флаг оставался поднятым — и следующая ручная зарядка считалась нашей, то есть автоматизация перебивала выставленный человеком ток. Ровно то, против чего флаг и заведён.
  • Аварийная дозарядка вне окна превращалась в дневную сессию до 100 %. Гистерезис снимал признак аварии на пороге плюс запас, но условие «доработать начатое» тут же подхватывало сессию и вело её до цели по дневному тарифу. Теперь продлевается только сессия, начатая внутри окна; различаются они по возрасту выключателя относительно конца окна.
  • Уставка колебалась между минимумом и минимумом плюс ампер до утра. Под конец ночи, когда машина почти заряжена, план колеблется около нижней границы, и правило «на границе диапазона пишем в обход зоны нечувствительности» пропускало каждое такое колебание. В симуляции это давало одиннадцать записей за ночь вместо одной. Правило оставлено только для потолка, где лишний ампер решает, дойдёт ли машина до цели.

  • Числовые статусы станции роняли автоматизацию целиком. Поле статусов объявлено текстовым, но YAML превращает 0 в число, а у числа нет .split — падал рендер всей секции переменных, то есть за ночь не происходило ничего: ни зарядки, ни остановки, ни уведомления об ошибке. Станции с кодами вместо слов обычны у обёрток над OCPP и у части Tuya.

  • Статусы сравнивались с учётом регистра. Станции сплошь отдают CHARGING и SUSPENDED_EV, а в поле их переписывают как придётся. Теперь регистр приводится с обеих сторон; в журнал по-прежнему идёт то, что реально сказала станция.
  • Процент, дошедший до цели, считался «замёрзшим». Перестать меняться ему положено, а станция в это время ещё отдаёт мощность на балансировку ячеек. Детектор срабатывал, данные объявлялись негодными, вместе с ними отменялась и достигнутая цель — и зарядка не выключалась до конца окна.
  • При пропаже данных ток падал на резервный посреди сессии. Уставка 22 А сваливалась на 10 А ровно тогда, когда не хватало и 22: на 10 А процент растёт втрое медленнее, и отставание только росло. Резервный ток — это ответ на вопрос «с чего начинать», и теперь берётся большее из двух.
  • Здоровье батареи в виде доли вдвое занижало ёмкость. Часть интеграций отдаёт 0.98 вместо 98; значение зажималось нижней границей 50 %, и весь ночной расчёт шёл от половины пакета. Доля теперь распознаётся, а явно неверные показания игнорируются, а не подтягиваются к границе.
  • Причина остановки называлась там, где ничего не останавливалось. Чужая сессия при достигнутой цели давала target_reached, нечитаемый статус при идущей зарядке — status_unknown. В трассировке это читалось как отчёт о прерванной сессии.
  • Ток регулировался вслепую при нечитаемом статусе. Идущую зарядку из-за пропавшего сенсора рвать не нужно — но и писать уставку в станцию, связь с которой потеряна, тоже. Теперь уставка замирает до восстановления связи.
  • Бюджет в научной нотации останавливал автоматизацию на всю ночь. Home Assistant считает результат шаблона числом, только если текст похож на число по его собственному правилу; 4.9e-05 под него не подходит и остаётся строкой. Машина, отдающая процент с четырьмя знаками, при цели 100 % и заряде 99.9999 роняла расчёт тока с TypeError. Проверено на настоящем Home Assistant: до правки — ошибка, после — чисто.

Исправлено в tools/trace_report.py

  • Под ASCII-локалью (ssh без проброса локали, cron, Docker) скрипт молча терял все трассировки и сообщал «файлов не найдено».
  • Посторонний JSON в папке загрузок ронял отчёт целиком с AttributeError.
  • Нечитаемый файл, каталог с именем *.json, logbookEntries неожиданного типа, отсутствующая отметка времени, --tz вне диапазона — всё это давало стек вызовов вместо отчёта.
  • Суффикс Z в отметке времени не разбирался на Python 3.9, хотя поддержка 3.9 заявлена и намеренна.
  • Время без указания зоны трактовалось как локальное для машины, где запущен скрипт: один и тот же файл читался в Москве и Нью-Йорке с разницей в семь часов.
  • Одинаковые записи журнала внутри одного файла считались дубликатами и терялись, хотя mode: restart даёт такие пачки штатно.

Добавлено

  • tests/test_trace_report.py — раньше единственный Python-код продукта не имел ни одного теста.
  • Проверка, что каждая мутация всё ещё встречается в блюпринте ровно один раз. Устаревшая мутация применяется вхолостую, и прогон «проходит», не проверив ничего; так уже случалось после рефакторингов. Проверка идёт с быстрыми тестами, а не только внутри десятиминутной джобы.
  • В CI: запуск trace_report.py на Python 3.9 под ASCII-локалью, отдельный прогон ruff с target-version py39 для tools/, валидация hacs.json вместе со структурой каталогов, которую ожидает HACS, и сверка версии между блюпринтом, changelog и git-тегом.
  • Тесты перехода на летнее время и обратно: весь остальной набор работает на фиксированном смещении UTC+3 и такую ночь увидеть не может.
  • Снимок всех значений по умолчанию. Набор почти везде передаёт свои значения, так что изменённый умолчательный параметр раньше прошёл бы незамеченным — а именно на умолчаниях работает большинство установок.
  • SECURITY.md с приватным каналом для дефектов, влияющих на выдачу тока, dependabot.yml и config.yml для шаблонов issue.
  • .gitattributes, фиксирующий переводы строк: при core.autocrlf=true следующий коммит превратил бы весь репозиторий в CRLF.

Изменено

  • ruff добавлен в requirements-dev.txt: CONTRIBUTING велит его запускать, а установить было нечем.
  • .direnv/, .envrc и _tmp/ добавлены в .gitignore репозитория; раньше окружение direnv скрывал только личный глобальный gitignore автора. Оно же исключено из песочницы мутационного тестирования, которая копировала его при каждом прогоне.
  • Из hacs.json убрано поле filename: для блюпринтов HACS ориентируется на структуру каталогов, а не на него.
  • Джоба схемной валидации больше не считает код 77 («пропущено») провалом.
  • В CHANGELOG добавлены ссылки сравнения версий.

1.2.0 — 2026-08-02

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

Исправлено

  • Трекер, сообщивший not_home, обрывал идущую зарядку. Ночью GPS регулярно «теряет» стоящую машину, причём чаще не молчанием, а ложным not_home — координаты уплывают, и машина формально выходит из зоны. Защита прежде срабатывала только на unavailable, то есть на редкий случай, а на частый — нет. В 04:30 автоматизация выключила зарядку, пока машина стояла дома и потребляла 17.6 А, и до утра к ней уже не вернулась.

Теперь идущий ток служит доказательством присутствия: уехать, не отключив кабель, невозможно. Пока станция отдаёт ток, никакой not_home сессию не прерывает. Настоящий отъезд — вне зоны и тока нет — гасит зарядку как раньше. Старт при машине вне зоны по-прежнему запрещён: пока ток не идёт, подтвердить присутствие нечем. - Зона нечувствительности глушила попытки поднять ток. Просьба поднять ток означает, что по расчёту мы не укладываемся в окно. За ночь регулятор четыре раза подряд просил 20–21 А при уставке 19 А, и каждый раз зона в 3 А гасила запись — недобор так и не был отработан. Теперь зона действует только при снижении тока; вверх уставка пишется при отличии хотя бы в один шаг. - Уставка тока и включение уходили в станцию одной пачкой (интервал 14 мс). Дешёвые станции теряют команду, пришедшую вплотную к предыдущей. Команды разнесены по разным пересчётам: сначала ток, следующим шагом включение. Блокирующая пауза здесь по-прежнему невозможна — в режиме restart её оборвал бы триггер смены статуса, который при старте срабатывает дважды за пять секунд. Чтобы включение не ждало полного интервала пересчёта, добавлен триггер на принятую станцией уставку. - Уведомление «зарядка началась» повторялось на каждом пересчёте, если станция не принимала команду включения, — за ночь оно пришло бы полтора десятка раз и заглушило бы собой реальные тревоги. Теперь сообщается только о первой попытке, сама команда по-прежнему повторяется.

Диагностика

  • В журнал и трассировку больше не попадает «расчёт 495 А»: в последние минуты окна горизонт планирования сжимается до секунд, и формула выдаёт сотни ампер. На уставку это не влияло, но при разборе выглядело как поломка расчёта. Сырое значение осталось в diag отдельным полем — по нему видно, что станции не хватает мощности.
  • Вердикт различает два шага старта («ставим ток, включим следующим шагом» и «запускаем зарядку») и больше не показывает отрицательное время паузы.
  • В diag добавлены ток_подтверждает_присутствие, машина_уехала и ток_растёт.
  • tools/trace_report.py — сводка по скачанным трассировкам: лента журнала с временем, реально отправленные команды, таблица решений (--vars), машинночитаемый вывод (--json). Записи журнала лежат внутри трассировок, поэтому выгружать его отдельно не нужно — через интерфейс «Активность» это и невозможно. Работает системным Python начиная с 3.9, без установки зависимостей; ничего никуда не отправляет.
  • В шаблоне issue уточнено, что достаточно двух-трёх трассировок вокруг проблемного момента, а не всей ночи.
  • Пропавший выключатель (сущность переименована, интеграция не загрузилась) назван в вердикте прямо. Раньше это выглядело как беспричинное бездействие: возраст отсутствующей сущности равен нулю, а нулевой возраст навсегда запрещает включение по троттлингу.
  • Запись об остановке содержит то, на основании чего решение принято: фактический ток, состояние трекера и длительность сессии. Это самая важная строка ночи, и именно её не хватало при разборе: «Стоп: машина не дома» выглядело одинаково и когда машина уехала, и когда трекер соврал, пока шло 17.6 А.
  • verdict и stop_reason больше не могут назвать разные причины одной остановки. Уехавшая машина неизбежно уносит кабель, и при разном порядке ветвей журнал винил «кабель не подключён», а трассировка — car_not_home.
  • stop_reason вне остановки пуст (none вместо other): пока гасить нечего, любое значение читается как причина. Добавлены отдельные причины charger_offline и status_unknown — раньше обе попадали в общий other.
  • Флаги команд в diag домножены на should_charge. В 16 прогонах той ночи снимок сообщал «записать ток: да», хотя машина уехала и ветка записи была недостижима, — при разборе это читается как «собирались, но не смогли».
  • В журнале время до конца окна выводится в минутах, а не как 2.4166 ч.

Тесты

  • tests/test_first_night_regression.py — первая ночь на живой установке, воспроизведённая по её собственным трассировкам. Показания взяты из выгрузки, а не выдуманы: недодача тока в 1.4 А, дрейф напряжения 218–221 В, кривая заряда 51 → 83%, SoH 102%, дающий ёмкость 44.88 кВт·ч. Каждый из трёх дефектов закреплён отдельным случаем — откат любого исправления валит тесты. Синтетический сценарий эти баги не поймал бы: они проявились именно на сочетании реальных значений.
  • Тесты зоны нечувствительности больше не полагаются на значение по умолчанию, а задают его явно — иначе смена дефолта тихо превратила бы их в бессодержательные.
  • Из README и CONTRIBUTING убраны точные счётчики тестов и мутаций: они устаревали при каждом изменении набора.

Документация

  • В README подробно описано, чем правило старта отличается от правила продолжения: начать зарядку при машине вне зоны по-прежнему нельзя (защита от чужой машины), но условие перепроверяется на каждом пересчёте всю ночь, поэтому сбой GPS стоит лишь задержки до ближайшего пересчёта, а не всей ночи.
  • IDEAS.md — то, что обдумано, но намеренно не реализовано: присутствие машины по локальному Zigbee-датчику вместо GPS, автоопределение недобора тока станцией, кривая заряда на последних процентах, две машины на одной станции, почасовые тарифы.

1.1.0 — 2026-08-01

Ревизия перед публикацией. Все изменения — исправления найденных дефектов; перенастраивать существующие автоматизации не требуется.

Исправлено

  • Регулятор молча переставал работать. Уставка тока записывалась после блокирующей паузы, а автоматизация работает в режиме restart: любое срабатывание триггера (например, смена статуса станции) обрывало паузу вместе с самой командой. Троттлинг переделан на проверку — запись откладывается до следующего пересчёта. Первая команда сессии больше не ждёт интервал.
  • Вне окна растяжка вырождалась. hours_left считался до ближайшего конца окна и днём достигал 21–23 часов, из-за чего аварийная дозарядка шла почти минимальным током. Вне окна горизонт планирования больше не используется.
  • Зарядка стартовала вслепую при нечитаемом статусе станции. Теперь unknown / unavailable запрещает старт новой сессии, но не прерывает идущую — по аналогии с потерей GPS.
  • Накопительный счётчик энергии вместо счётчика сессии навсегда и без сообщений блокировал зарядку («уже залито 4500 кВт·ч»). Такой сенсор теперь распознаётся как невалидный.
  • Пустой список дней недели отключал автоматизацию навсегда; теперь трактуется как «все дни».
  • Зона без отображаемого имени делала car_home всегда ложным; добавлен откат на идентификатор зоны.
  • Неправдоподобно высокое напряжение (милливольты вместо вольт) занижало ток до минимума — добавлена верхняя граница 280 В, как у вычисленного.
  • Очищенное поле расшифровки статусов превращалось в список из пустой строки и ломало определение подключённого кабеля.
  • Уставка на границе диапазона переписывалась на каждом пересчёте, если станция округляла значение по-своему (просили 28 А, отдаёт 27.9 А).
  • Отключение кабеля во время сессии не гасило выключатель, хотя причина остановки unplugged была объявлена.
  • Уведомление «включено, но тока нет» повторялось на каждом пересчёте до конца ночи; теперь сообщается один раз.

Добавлено

  • Параметр «Запас над аварийным порогом»: аварийная дозарядка больше не останавливается ровно на пороге и не дребезжит вокруг него.
  • source_url — блюпринт можно импортировать по ссылке и обновлять из HACS.

Исправлено во втором раунде ревизии

  • Перепутанные местами минимум и максимум тока поднимали потолок. При «минимум 80, максимум 6» блюпринт выдавал 80 А. Теперь при перепутанных полях побеждает максимум: опустить пол безопасно, поднять потолок — нет.
  • Потолок, не кратный шагу, был недостижим. При максимуме 28 А и шаге 5 А мы просили 28, станция округляла по-своему, и уставка переписывалась на каждом пересчёте. Потолок теперь опускается до кратного шагу.
  • Правильный счётчик сессии отвергался при маленькой цели. Проверка «не больше трёх целей» рвалась на цели в 2 кВт·ч, и зарядка не останавливалась по достигнутой цели. Порог привязан к ёмкости батареи.
  • Режим «доработать начатое» жёг максимум весь день. В сочетании с аварийной дозарядкой и выключенным «гасить в конце окна» ток держался на 28 А до самой цели. Горизонты для этих двух режимов разведены.
  • Watchdog молча отключался без сенсора мощности, хотя описание обещало откат на статус станции.
  • Станция, не принимающая команду включения, получала её на каждом пересчёте, а хук старта слал уведомление каждые полчаса.
  • Пустое значение режима зарядки уходило в станцию как пустая опция: continue_on_error глушил ошибку, и режим не выставлялся никогда.
  • Нулевая зона нечувствительности означала «писать всегда», а не «писать при любом отличии».
  • Триггер приезда машины не знал про откат на идентификатор зоны, хотя сама проверка местоположения его уже использовала.
  • Мелочи: в журнале больше не пишется «н/д (none)»; описания троттлинга, watchdog и режима «доработать начатое» приведены в соответствие с кодом.

Изменено

  • В зарядку, начатую вручную, автоматизация больше не вмешивается. Раньше она подбирала для неё ток, а гасить в конце окна отказывалась. Теперь при заполненном «флаге сессии» чужая сессия не трогается совсем: ни уставка, ни выключение. Исключение — неисправность станции. Кабель, воткнутый заранее при выключенной зарядке, чужой сессией не считается и запускается по обычному расписанию. Без флага сессии поведение прежнее.

Добавлено во втором раунде

  • Параметр «Максимальный возраст показания температуры» (по умолчанию выключен) — защита от залипшего сенсора для режима холодной погоды.

Изменено

  • Дефолт «Минимального изменения тока для записи» снижен с 3 А до 2 А. При шаге станции в 1 А зона в три ампера означала «игнорировать до трети рабочего диапазона». Вместе с новой асимметрией (вверх пишем всегда) параметр теперь сдерживает только избыточные понижения, ради чего и вводился. Уже настроенные автоматизации сохранят своё значение — дефолт действует только на новые.
  • Из pyproject.toml убрана дублирующая версия пакета: репозиторий не Python-пакет, файл нужен лишь для настроек pytest и ruff, а версия блюпринта живёт в одном месте — в его описании. Релизы помечаются git-тегами.

Диагностика

Готовность к отладке на живой установке. Раньше пересчёт, не приведший к команде — самый частый исход и самый трудный для разбора, — не оставлял следа вообще, и вопрос «почему ночью ничего не произошло» ответа не имел.

  • Причина каждого решения (verdict) одной строкой человеческим языком: «станция офлайн», «машина не дома», «ток в норме, отличие меньше зоны нечувствительности», «ток надо менять, но идёт пауза (43 с)». Называется всегда первая по счёту из действующих причин, а не любая из них. Прежний stop_reason для этого не годился: он объясняет только остановку, а вне этого контекста вводил в заблуждение — офлайн-станция давала other, нечитаемый статус — unplugged при воткнутом кабеле.
  • Пересчёты без команд теперь пишутся в журнал с указанием причины.
  • Слепок состояния для трассировки (diag) — все значимые величины одним объектом, сгруппированные по смыслу: решение, команды, ток, план, заряд, станция, окно, место. Трассировка Home Assistant показывает переменные плоским списком из полутора сотен имён, где нужное тонет среди служебного.
  • Записи журнала о старте и остановке начинаются с вердикта.
  • В README описано, как читать трассировку и как поднять лимит stored_traces до 100: по умолчанию Home Assistant хранит 5 трассировок, то есть меньше часа истории при пересчёте раз в 10 минут — к утру от ночной зарядки не остаётся ничего. Напоминание продублировано в «Быстром старте» и в шаблоне issue, чтобы настройка делалась до первой ночи, а не после того, как разбирать станет нечего.

Тесты

  • Набор тестов вырос более чем вдвое относительно 1.0.0, мутаций — более чем вдвое.
  • tests/test_diagnostics.py — вердикт называет верную причину в каждом исходе, слепок не расходится с переменными, и оба переживают недоступные сенсоры: диагностика, падающая на плохих данных, бесполезна ровно тогда, когда нужна.
  • tests/test_actions.py — блок actions: теперь исполняется тестами: состав и порядок команд, троттлинг, хуки, сброс уставки, шаблон журнала. Раньше эта часть не проверялась вообще.
  • Тестовый движок приведён в соответствие с Home Assistant: is_number отвергает inf / nan, states[''] бросает ошибку, round(0) возвращает целое. Раньше движок был мягче реального HA и мог скрыть регрессию.
  • Покрыты множители в сердце расчёта (число фаз, КПД), которых не касался ни один тест, и сценарии ночи: приезд и отъезд машины, неисправность станции, потеря связи, мороз, «замёрзший» процент, watchdog.
  • Добавлен инвариант: should_charge и must_stop не могут быть истинны одновременно (проверяется на 110 комбинациях состояний).
  • validate_with_home_assistant.py починен под актуальный API и теперь проверяет метаданные строгой схемой — именно она поймала неверный селектор.

1.0.0 — 2026-08-01

Первый публичный выпуск.

Возможности

  • Окно зарядки с переходом через полночь и выбором дней недели.
  • Динамический подбор тока с пересчётом раз в 10–30 минут и обратной связью по фактическому проценту заряда.
  • Жёсткие границы тока, настраиваемая дискретность, зона нечувствительности.
  • Целевой процент заряда до 100 включительно.
  • Альтернативный план по энергии сессии — работает без интеграции с автомобилем.
  • Трёхуровневая проверка достоверности данных о заряде: недоступность сущности, устаревание по времени последней связи, детектор «замёрзшего» процента.
  • Зонная логика с немедленным стартом при появлении машины в зоне и защитой идущей сессии от сбоев GPS.
  • Троттлинг команд станции по last_changed сущности уставки.
  • Приоритетная цепочка источников напряжения: сенсор, вычисление из мощности и фактического тока, запасное значение.
  • Аварийная дозарядка ниже критического процента вне окна.
  • Режим холодной погоды.
  • Контроль исправности: неисправность станции, «включено, но тока нет», уточнение диагноза по сенсору зарядки со стороны автомобиля.
  • Флаг «сессия начата автоматизацией» — ручная дневная зарядка не прерывается окончанием ночного окна.
  • Три хука для уведомлений: старт, завершение, неисправность.
  • Запись хода зарядки в журнал.

Тестирование

  • 243 теста: схема документа, все расчётные переменные, шаблонные триггеры и сквозная симуляция ночи в замкнутом контуре.
  • Мутационное тестирование: 16 намеренных поломок блюпринта, каждая соответствует обещанию из README; все обнаруживаются набором.
  • CI на Python 3.11, 3.12 и 3.13, линт YAML и Python, информационная валидация настоящим схемным валидатором Home Assistant.

Найдено и исправлено при разработке тестов

  • Зона нечувствительности могла помешать току выйти на максимум: холодной ночью он застревал в паре ампер от потолка и цель не достигалась. Обнаружено сквозной симуляцией; добавлено исключение на границах диапазона.
  • Зажим тока применялся дважды, и внутренний маскировал внешний. Проявлялось, только когда максимум не делится на дискретность (например 28 А при шаге 5 А). Обнаружено мутационным тестированием.

Где лежит исходник

Эта страница собирается из CHANGELOG.md в корне репозитория — правки вносите туда.