Skip to content

fix(charge): панель и меню берут порог заряда из прошивки (XIC-17) - #15

Merged
Oksion merged 5 commits into
mainfrom
oksion/xic-17-charge-limit-live-read
Jul 29, 2026
Merged

fix(charge): панель и меню берут порог заряда из прошивки (XIC-17)#15
Oksion merged 5 commits into
mainfrom
oksion/xic-17-charge-limit-live-read

Conversation

@Oksion

@Oksion Oksion commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Если порог заряда менял кто-то снаружи (Xiaomi PC Manager, чужая утилита), наша быстрая панель продолжала показывать старое значение — она рисовала из конфига и прошивку не перечитывала вовсе. При этом клик по пилюле исходил из живого состояния: подпись говорила одно, поведение — другое.

Закрывает XIC-17.

Что сделано

AppController.SyncCareFromFirmware() — примирение конфига с прошивкой в командном слое. Зовётся из QuickPanelForm.RefreshState() (до Show(), рядом с существующими железными чтениями) и из TrayMenuBuilder.Build() при пересборке меню. Фонового опроса не добавилось — читаем только когда UI открывают; частота WMI-чтений не выросла (меню и раньше читало GetChargeLimit на каждый Opening).

Заодно исправлена та же ошибка в меню, которой в issue не было: галочка считалась от живого значения, а процент в подписи брался из конфига — меню могло показать «беречь 60 %» с галочкой при 50 % в железе.

Правило примирения (важно)

Принимается только валидный порог < 100. Показание «100» не принимается — и это защита от найденной на ревью гонки:

EC теряет лимит после сна/смены питания, а ChargeGuard переармирует его с дебаунсом 1.5 с. В этом окне прошивка честно читается как 100 — неотличимо от внешнего «выключить». Приняв 100, мы сбросили бы ChargeCare в конфиге, а гард читает желаемый порог из конфига — то есть панель, открытая сразу после resume или выдёргивания зарядника, молча разоружала бы защиту навсегда.

Другой валидный уровень EC самопроизвольно не породит — это всегда чей-то осознанный SET, его принимать безопасно. Внешнее «выключить» не отражаем вовсе: гард по своей документированной задаче перебивает его на следующем событии питания, поэтому UI показывает намерение пользователя, а не мгновенное состояние EC.

Остальные грани: прошивка молчит (null) → конфиг не трогаем; при активном «В дорогу» примирение выключено (там 100 % держится намеренно, сброс ChargeCare сломал бы режим); CareChanged не дёргается (иначе OSD-всплывашка на каждое открытие панели); конфиг пишется только при фактическом отличии.

Проверка

  • Сборка 0 предупреждений / 0 ошибок, тесты 177 зелёных (+7): подхват уровня, включение защиты снаружи, молчащая прошивка, «в дорогу», неизвестный уровень, идемпотентность, Reads100_DoesNotDisarm пиняет гонку.
  • Логика в AppController → покрыта на фейках; QuickPanelForm остался тупым view.
  • Проверено вживую на TM2424 рядом с Xiaomi PC Manager: смена порога в OEM-селекторе подхватывается при открытии нашей панели и меню; «В дорогу» не слетает; лаг открытия панели не ощущается.
  • На старте гонки нет: _charge.Reapply() в Startup() синхронный и идёт до Prime() меню.

Мелочь заодно

Вычищены устаревшие «80 %» из статических текстов: с XIC-4 порог выбирается пользователем, поэтому конкретное число в подписях врёт всем, кто выбрал не 80.

ключ было стало
settings.api.cmd.care.desc POST /care — «беречь ~80%» вкл/выкл. POST /care — лимит заряда вкл/выкл.
settings.act.charge Заряд 80 / 100 % Лимит заряда вкл/выкл
settings.key.settings.desc …всегда заряд 80/100. …всегда переключает лимит заряда.

Процент убран, а не подставляется живым значением: это описания эндпоинта и действия, а не индикаторы текущей настройки — тянуть туда фактический порог значило бы завести ещё одно место, где он может разойтись с железом. Названия выровнены по соседям (Тачпад вкл/выкл), settings.key.settings.desc в ru — по уже корректному en/zh.

То же самое вычищено из README обоих языков — по пять мест в каждом: бейдж качества БП («иконка показывает лимит 80/100» → «текущий лимит заряда»), двойной клик Mi, удержание Mi, пилюли в разделе «В дорогу» («80/100» → «порог/100»), список действий переназначения.

Законные упоминания процентов оставлены: набор пресетов 40/50/60/70/80/100%, «В дорогу» до 100 %, уровни подсветки клавиатуры, пример запоминания яркости, hex-статус 0x80 и «скажем, 80 %» в туториале «В дорогу» (там процент явно подан примером и помогает понять сценарий). Таблица эндпоинтов правок не требовала — там уже «настроенный порог» / «the configured threshold».

Вне скоупа

  • Тултип трея — живёт на 30-секундном опросе, правдивость потребовала бы фонового WMI-вызова.
  • ToggleCharge (решение по живому чтению в момент клика) — не менялся.

🤖 Generated with Claude Code

Oksion and others added 5 commits July 29, 2026 22:51
Если порог менял кто-то снаружи (Xiaomi PC Manager, чужая утилита), быстрая
панель продолжала показывать старое значение: она рисовала из конфига и прошивку
не перечитывала вовсе. При этом клик по пилюле читал живое состояние — подпись
говорила одно, поведение исходило из другого.

AppController.SyncCareFromFirmware() читает GET 0x10/02 и приводит конфиг к
прошивке (по docs/12 источник истины — она). Зовётся из QuickPanelForm.RefreshState()
(до Show(), рядом с чтением режима и тачпада) и из TrayMenuBuilder при пересборке
меню — фонового опроса не добавилось, читаем только когда UI открывают.

Тонкости, без которых это ломается:
- прошивка молчит (null) → конфиг не трогаем, лучше своё значение, чем ноль;
- процент принимаем только валидным пресетом, чужой уровень вслепую не пишем;
- 100 = «защита выключена», а не «порог 100» — выбранный X храним до включения;
- при активном «В дорогу» не примиряем: там 100% держится намеренно, и сброс
  ChargeCare сломал бы режим;
- CareChanged не дёргаем — это не действие пользователя, а подхват чужого, и при
  скрытой панели TrayApp показал бы на него OSD: всплывашка на каждое открытие;
- конфиг пишем только при фактическом отличии.

Заодно та же ошибка в меню трея: галочка считалась от живого значения, а процент
в подписи брался из конфига, поэтому меню могло показать «беречь 60%» с галочкой
при 50% в железе. Теперь панель и меню рисуют из конфига — он приведён к железу.
Тултип трея сознательно оставлен как был: он живёт на 30-секундном опросе, и
правдивость потребовала бы фонового WMI-вызова.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eGuard

Ревью первого коммита нашло дыру: EC читается как 100 и когда защиту выключили
снаружи, и — транзиентно — после сна/смены питания. Лимит на этих событиях
теряется, а ChargeGuard переармирует его с дебаунсом 1.5 с. Отличить случаи
нельзя. Приняв 100, SyncCareFromFirmware сбрасывал ChargeCare в конфиге — а гард
читает желаемый порог из конфига и разоружался навсегда: панель, открытая сразу
после resume или выдёргивания зарядника (естественный жест «посмотреть на
батарею»), молча убивала защиту до ручного включения.

Правило сужено: принимаем только валидный порог < 100. Другой уровень EC сам не
породит — это всегда чей-то осознанный SET, его принимать безопасно. Внешнее
«выключить» не отражаем вовсе: гард по своей документированной задаче перебивает
его на следующем событии питания, поэтому UI показывает намерение пользователя,
а не мгновенное состояние EC (то же самое гард сделал бы с самим EC).

Тест SyncCare_ProtectionOffOutside_KeepsChosenPercent заменён на
SyncCare_Reads100_DoesNotDisarm (пиняет гонку), добавлен
SyncCare_ExternalArming_IsAdopted (включение защиты снаружи принимается).
docs/12 и CHANGELOG приведены к фактическому правилу; заодно секции Unreleased
выровнены по порядку файла (Добавлено → Исправлено).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Подпись команды во вкладке «HTTP API» обещала «беречь ~80%», хотя с XIC-4 порог
выбирается пользователем (40–80). Процент убран совсем, а не подставляется живым
значением: строка описывает эндпоинт, а не текущую настройку, и тянуть сюда
фактический порог значило бы держать ещё одно место, где он может разойтись.
Формулировка выровнена по соседям (POST /mode — переключение режима).

README RU/EN правки не требуют — там уже «настроенный порог».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Те же устаревшие цифры, что и в описании POST /care, но в переназначении клавиш:
«Заряд 80 / 100 %» в списке действий (все три языка) и «…всегда заряд 80/100» в
подсказке к клавише с шестерёнкой (только ru — en/zh уже были без процента).

Действие переименовано по образцу соседних тумблеров («Тачпад вкл/выкл»):
«Лимит заряда вкл/выкл» / «Charge limit on/off» / «充电限制开关». Подсказка ru
выровнена по en — «всегда переключает лимит заряда».

После этого хардкода 80 в локализации не осталось ни в одном языке (grep чист):
порог выбирается пользователем с XIC-4, и упоминать конкретное число в статике
больше негде.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Те же устаревшие «80/100», что вычистили из локализации, жили и в README обоих
языков — по пять мест в каждом. Порог выбирается пользователем с XIC-4, поэтому
конкретное число в описании фич врёт всем, кто выбрал не 80:

  бейдж качества БП    иконка показывает лимит 80/100 → текущий лимит заряда
  двойной клик Mi      заряд 80/100                   → переключение лимита заряда
  удержание Mi         режимы + заряд 80/100          → режимы + лимит заряда
  «В дорогу»           пилюли 80/100                  → пилюли «порог/100»
  переназначение       заряд 80/100                   → лимит заряда вкл/выкл

Legitimate-упоминания оставлены как есть: набор пресетов (40/50/60/70/80/100%),
«В дорогу» до 100%, уровни подсветки клавиатуры, пример запоминания яркости,
hex-статус 0x80 и «скажем, 80%» в туториале «В дорогу» — там процент явно подан
примером («скажем»), и он помогает понять сценарий.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Oksion
Oksion merged commit a4bb0d8 into main Jul 29, 2026
5 checks passed
@Oksion
Oksion deleted the oksion/xic-17-charge-limit-live-read branch July 29, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant