Compare commits

...
2 Commits
Author SHA1 Message Date
petr.polezhaev a32e2f0bfc ha(ci): Gitea Actions — сборка и публикация pyfglair в PyPI-реестр пакетов Gitea
- .gitea/workflows/publish-pyfglair.yaml: ручной workflow_dispatch —
  toolchain, python -m build (wheel+sdist), smoke-тест wheel, twine upload
  в /api/packages/<owner>/pypi (PAT в секрете GITEA_PYPI_TOKEN; встроенный
  GITEA_TOKEN пакеты публиковать не умеет); вход bundled_mbedtls
- setup.py: FGL_BUNDLED_MBEDTLS=0 уважается при сборке wheel (системный
  mbedtls), по умолчанию — встроенный (проверено readelf для обоих)
- RELEASE_HA переписан: §1 внутренняя установка (Gitea Packages + команда
  pip install для HA), §2 PyPI (публично), §3 GitHub/HACS (Custom
  Repositories только на GitHub), §4-5 без изменений
- README компонента: внутренняя установка pyfglair из Gitea Packages
2026-09-29 15:44:56 +03:00
petr.polezhaev 34cdb7fef8 ha(HACS): комплект файлов для Custom Repositories
- info.md (страница HACS), LICENSE (MIT), codeowners @petr.polezhaev
- brand: icon/icon@2x/logo + dark-варианты (сгенерированные плейсхолдеры
  с силуэтом кондиционера)
- .github/workflows/validate-hacs.yaml и hassfest.yaml (включатся на GitHub)
- pyproject: license=MIT, license-files; MANIFEST: LICENSE в sdist
- README/RELEASE_HA: HACS работает только с публичным GitHub-зеркалом
  (Custom Repositories), точные команды mirror и настройки GitHub
2026-09-29 15:40:23 +03:00
18 changed files with 312 additions and 68 deletions
+95
View File
@@ -0,0 +1,95 @@
# Сборка и публикация pyfglair в PyPI-реестр пакетов Gitea.
# Запуск: Actions → Publish pyfglair → Run workflow (ручной, workflow_dispatch).
#
# Секреты репозитория (Settings → Actions → Secrets):
# GITEA_PYPI_TOKEN — personal access token со scope write:package
# GITEA_PYPI_USER — (опционально) имя пользователя Gitea для upload;
# по умолчанию — актор запуска.
#
# Примечания Gitea Actions:
# * встроенный GITEA_TOKEN не может публиковать пакеты — нужен PAT;
# * actions/checkout и actions/setup-python тянутся с github.com
# (если инстанс изолирован — замените на абсолютные URL своих зеркал);
# * сборка wheel с bundled mbedtls требует доступ к github.com
# (FetchContent); при BUNDLED=0 ставится системный libmbedtls-dev.
name: Publish pyfglair
on:
workflow_dispatch:
inputs:
bundled_mbedtls:
description: "1 — встроить mbedtls, 0 — использовать системный"
required: false
default: "1"
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Toolchain
run: |
if ! command -v cmake >/dev/null || ! command -v ninja >/dev/null \
|| ! command -v g++ >/dev/null; then
sudo apt-get update
sudo apt-get install -y --no-install-recommends cmake ninja-build g++
fi
if [ "${{ inputs.bundled_mbedtls }}" = "0" ]; then
sudo apt-get update
sudo apt-get install -y --no-install-recommends libmbedtls-dev
fi
cmake --version | head -1
g++ --version | head -1
- name: Build wheel + sdist
env:
BUNDLED: ${{ inputs.bundled_mbedtls }}
run: |
if [ "$BUNDLED" = "0" ]; then
export FGL_BUNDLED_MBEDTLS=0
else
export FGL_BUNDLED_MBEDTLS=1
fi
pip install --upgrade build
python -m build
ls -l dist
- name: Smoke test wheel
run: |
python -m venv /tmp/wheeltest
/tmp/wheeltest/bin/pip install --quiet dist/*.whl
/tmp/wheeltest/bin/python -c "import pyfglair.session as s; print('pyfglair:', s.__file__)"
/tmp/wheeltest/bin/python -c "from pyfglair import Template, Prop, convert_to_display; assert convert_to_display(Template.A, Prop.DISPLAY_TEMPERATURE, 7000) == 200"
- name: Publish to Gitea package registry (PyPI)
env:
TOKEN: ${{ secrets.GITEA_PYPI_TOKEN }}
SECRET_USER: ${{ secrets.GITEA_PYPI_USER }}
run: |
if [ -z "$TOKEN" ]; then
echo "::error::Задайте секрет GITEA_PYPI_TOKEN (PAT со scope write:package)"
exit 1
fi
SERVER="${GITHUB_SERVER_URL:-}"
if [ -z "$SERVER" ]; then
SERVER="$(git remote get-url origin | sed -E 's#^git@([^:]+):.*#https://\1#; s#^(https?://[^/]+)/.*#\1#')"
fi
REPO="${GITHUB_REPOSITORY:-$(git remote get-url origin | sed -E 's#.*[:/]([^/]+/[^/]+?)(\.git)?$#\1#')}"
OWNER="${REPO%%/*}"
GITEA_USER="${SECRET_USER:-${GITHUB_ACTOR:-git}}"
pip install --upgrade twine
python -m twine upload \
--repository-url "$SERVER/api/packages/$OWNER/pypi" \
--username "$GITEA_USER" \
--password "$TOKEN" \
--skip-existing \
--non-interactive \
dist/*
echo "Опубликовано: $SERVER/api/packages/$OWNER/pypi/simple/pyfglair/"
+19
View File
@@ -0,0 +1,19 @@
name: Validate (hassfest)
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions: {}
jobs:
validate-hassfest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: hassfest
uses: home-assistant/actions/hassfest@master
+21
View File
@@ -0,0 +1,21 @@
name: Validate (HACS)
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
permissions: {}
jobs:
validate-hacs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: HACS validation
uses: hacs/action@main
with:
category: integration
+26
View File
@@ -0,0 +1,26 @@
MIT License
Copyright (c) 2026 Petr Polezhaev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
Вендоренный парсер JSON `third_party/jsmn` распространяется под лицензией
MIT (см. `third_party/jsmn/LICENSE`).
+1 -1
View File
@@ -1,4 +1,4 @@
include CMakeLists.txt README.md pyproject.toml setup.py include CMakeLists.txt LICENSE README.md pyproject.toml setup.py
recursive-include include *.h *.hpp recursive-include include *.h *.hpp
recursive-include src *.cpp *.hpp recursive-include src *.cpp *.hpp
recursive-include third_party * recursive-include third_party *
+3 -2
View File
@@ -126,5 +126,6 @@ converted from the module's 0.01 °C+5000 format.
## License / provenance ## License / provenance
Protocol documentation and code reconstructed from the FGLair 3.4.3 APK for MIT — see [`LICENSE`](LICENSE). Protocol documentation and code reconstructed
interoperability; `third_party/jsmn` is MIT. from the FGLair 3.4.3 APK for interoperability; vendored `third_party/jsmn`
is MIT.
+5 -3
View File
@@ -11,9 +11,11 @@
## Требования ## Требования
* Home Assistant ≥ 2025.1 (Linux x86_64/aarch64). * Home Assistant ≥ 2025.1 (Linux x86_64/aarch64).
* Пакет `pyfglair` (манифест ставит его автоматически из PyPI). * Пакет `pyfglair`. Манифест ставит его из PyPI; при внутренней установке
До публикации на PyPI установите wheel вручную в python-окружение HA: (Gitea, без публичного PyPI) поставьте вручную из PyPI-реестра пакетов
`pip install pyfglair-*.whl` (см. [`docs/RELEASE_HA.md`](../../docs/RELEASE_HA.md)). Gitea:
`pip install --index-url https://<user>:<token>@git.ratigorsk-12.ru/api/packages/<owner>/pypi/simple --no-deps pyfglair`
(подробности и сборка wheel — [`docs/RELEASE_HA.md`](../../docs/RELEASE_HA.md)).
* Модуль кондиционера в той же LAN. Модуль поддерживает **2 LAN-сессии**: * Модуль кондиционера в той же LAN. Модуль поддерживает **2 LAN-сессии**:
телефон с FGLair и HA уживаются; третья (например, ESPHome) получит 503. телефон с FGLair и HA уживаются; третья (например, ESPHome) получит 503.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 832 B

After

Width:  |  Height:  |  Size: 1.5 KiB

+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"domain": "fglair", "domain": "fglair",
"name": "FGLair (Fujitsu General)", "name": "FGLair (Fujitsu General)",
"codeowners": [], "codeowners": ["@petr.polezhaev"],
"config_flow": true, "config_flow": true,
"documentation": "https://git.ratigorsk-12.ru/esphome/fgl-aircon", "documentation": "https://git.ratigorsk-12.ru/esphome/fgl-aircon",
"integration_type": "device", "integration_type": "device",
+99 -59
View File
@@ -1,80 +1,120 @@
# Чек-лист релиза HA-интеграции (H5) # Релиз и установка HA-интеграции
## 1. Публикация pyfglair (PyPI) ## 1. Внутренняя сборка и установка (Gitea, текущий режим)
Манифест компонента требует `pyfglair>=1.0.0`; штатный installer HA Внутренний репозиторий — Gitea (`git.ratigorsk-12.ru`), поэтому:
резолвит требования только через PyPI. PyPI (warehouse) принимает только
`manylinux*`/`musllinux*` платформенные теги — локальный * HACS к Gitea **не подключается** (только публичный GitHub) — для
`py3-none-linux_x86_64` будет отклонён (`HTTP 400 unsupported platform внутреннего использования интеграция ставится копированием
tag`), поэтому wheel нужно «починить» auditwheel'ом в контейнере со старой `custom_components/fglair/` в `<config>/custom_components/`;
glibc (manylinux): * зависимость `pyfglair` публикуется в **PyPI-реестр пакетов Gitea** и
ставится оттуда.
### 1.1. Сборка и публикация wheel (Gitea Actions, ручной запуск)
Workflow: `.gitea/workflows/publish-pyfglair.yaml`.
1. Создайте personal access token Gitea со scope `write:package`
(Settings → Applications → Generate token).
2. Settings репозитория → Actions → Secrets:
* `GITEA_PYPI_TOKEN` — этот токен (обязательно);
* `GITEA_PYPI_USER` — имя пользователя Gitea (опционально; иначе актор
запуска).
3. Actions → **Publish pyfglair** → Run workflow (вход `bundled_mbedtls`:
`1` — встроить mbedtls, `0` — системный `libmbedtls-dev`).
4. Артефакт появится в: `https://git.ratigorsk-12.ru/api/packages/<owner>/pypi/simple/pyfglair/`
(`<owner>` — владелец репозитория, напр. `esphome`).
Workflow собирает `wheel` + `sdist`, прогоняет smoke-тест установки и
публикует через `twine upload --skip-existing`. Повторная публикация той же
версии пропускается; для новой версии поднимите `version` в
`pyproject.toml`.
### 1.2. Установка pyfglair в Home Assistant
Пакет обязателен (cffi-биндинги). Варианты:
* **Вручную в окружение HA** (проще всего для теста; HA увидит
установленный дистрибутив и не будет вызывать pip для `pyfglair>=1.0.0`):
```sh
# в контейнере/venv Home Assistant (Advanced SSH & Web Terminal, docker exec и т.п.)
pip install --index-url https://<user>:<token>@git.ratigorsk-12.ru/api/packages/<owner>/pypi/simple \
--no-deps pyfglair
```
* **Через индекс pip в окружении HA** (`pip.conf`/`PIP_EXTRA_INDEX_URL`):
тогда HA сам поставит `pyfglair` из манифеста. Учтите риск dependency
confusion при `--extra-index-url` — для внутреннего стенда допустимо.
* **Публичный PyPI** — см. §2 (для распространения вне сети).
### 1.3. Обновление интеграции
`custom_components/fglair/` обновляется из Gitea (git pull / копирование),
затем перезапуск HA. `pyfglair` — переустановкой из пакетов Gitea.
## 2. Публикация pyfglair на PyPI (если понадобится публично)
PyPI (warehouse) принимает только `manylinux*`/`musllinux*` теги, поэтому
локальный `py3-none-linux_x86_64` нужно чинить auditwheel'ом в контейнере
со старой glibc:
```sh ```sh
pip install build auditwheel twine pip install build auditwheel twine
# для каждой архитектуры (linux x86_64/aarch64) в manylinux-контейнере # для каждой архитектуры (linux x86_64/aarch64) в manylinux-контейнере:
# (например, quay.io/pypa/manylinux2014_<arch>): python -m build --wheel
python -m build --wheel # .so без внешнего mbedcrypto auditwheel repair dist/pyfglair-*.whl --plat manylinux2014_x86_64 -w dist/manylinux/
auditwheel repair dist/pyfglair-*.whl \ rm -f dist/pyfglair-*-linux_*.whl
--plat manylinux2014_x86_64 -w dist/manylinux/ # или manylinux_2_28 python -m build --sdist
rm -f dist/pyfglair-*-linux_*.whl # linux-тег PyPI отклонит
python -m build --sdist # sdist с C++-исходниками (MANIFEST.in)
twine upload dist/manylinux/* dist/*.tar.gz twine upload dist/manylinux/* dist/*.tar.gz
``` ```
Проверки перед публикацией: Проверки: `scripts/py-ci.sh`, `scripts/ci.sh`, `readelf -d` по `.so`
(нет внешней `libmbedcrypto`), установка wheel в чистый venv.
* `scripts/py-ci.sh` — pyfglair + acceptance self-test + components; ## 3. GitHub-зеркало и Custom Repositories (HACS)
* `scripts/ci.sh` — ядро (gcc/clang, ASan/UBSan) 11/11;
* `readelf -d build-pyfglair/libfgl-aircon.so*` (или распакованный wheel:
`pyfglair/libfgl-aircon.so*`) — нет внешней `libmbedcrypto` (встроен);
* `auditwheel show` для repaired-wheel — тег manylinux и только
базовые системные библиотеки;
* установка wheel в чистый venv вне репозитория:
`python -c "import pyfglair.session"`;
* aarch64: сборка в соответствующем manylinux-контейнере (QEMU или
нативная), иначе пользователи ARM не смогут поставить `pyfglair`.
Альтернатива — cibuildwheel (`pip install cibuildwheel`) с `auditwheel
repair` внутри, но текущий `bdist_wheel`-хук отдаёт тег
`py3-none-<plat>`, поэтому проверяйте итоговые теги вручную.
## 2. GitHub-зеркало и HACS
HACS устанавливает интеграции только с публичных **GitHub**-репозиториев HACS устанавливает интеграции только с публичных **GitHub**-репозиториев
(GitLab/Gitea не поддерживаются). Шаги: (GitLab/Gitea не поддерживаются:
https://hacs.xyz/docs/faq/other_git_providers/). Публикация в
default-репозиторий HACS не требуется — достаточно Custom Repositories, но
хостинг обязан быть GitHub.
* создать GitHub-зеркало (push mirror) и убедиться, что оно содержит ```sh
`custom_components/fglair/`, `hacs.json`, тег релиза; git remote add github git@github.com:<owner>/fgl-aircon.git
* заполнить `codeowners` в `custom_components/fglair/manifest.json` git push --mirror github
(GitHub-хендлы; сейчас пустой список); ```
* проверить `documentation`/`issue_tracker` в манифесте;
* обновить `version` манифеста под номер тега (сейчас `0.1.0`, pyfglair —
`1.0.0`; синхронизируйте по вкусу — компонент и пакет версионируются
отдельно);
* заменить заглушки `custom_components/fglair/screenshots/step-N.png`
реальными скриншотами (описания «что должно быть видно» — в README);
* заменить заглушки `custom_components/fglair/brand/{icon,logo}.png`
фирменными ассетами;
* создать git-тег (semver) в GitHub-зеркале — HACS покажет версию.
## 3. Приёмка на живом стенде После зеркала:
* заполнить на GitHub Description, Topics (`home-assistant`, `hacs`,
`fujitsu`, `air-conditioner`), включить Issues (проверяет HACS Action);
* проверить `codeowners` в manifest (`@petr.polezhaev`; для GitHub нужен
реальный GitHub-логин);
* обновить `version` манифеста под тег; заменить заглушки
`screenshots/step-N.png` и `brand/*.png` реальными ассетами;
* создать тег (semver) — HACS покажет версию.
Готовые файлы HACS: `hacs.json`, `info.md`, `LICENSE` (MIT),
`custom_components/fglair/brand/` (icon/icon@2x/logo + dark-варианты),
workflows `.github/workflows/{validate-hacs,hassfest}.yaml`.
## 4. Приёмка на живом стенде
* `tests/acceptance/test_esphome_ha.py quick` — матрица изменений (HA; * `tests/acceptance/test_esphome_ha.py quick` — матрица изменений (HA;
с `--esphome-host` шаги «режим/уставка» идут через ESPHome, проверка — по с `--esphome-host` шаги «режим/уставка» идут через ESPHome, проверка — по
состоянию в HA), возврат к исходному. состоянию в HA), возврат к исходному.
* `long --hours 24 --interval 3600 --report acceptance.csv` — суточный * `long --hours 24 --interval 3600 --report acceptance.csv`.
прогон, отчёт дописывается по ходу. * Сценарии: 503 (два слота), key_error + Repair, offline/восстановление,
* Сценарии: 503 (два слота заняты), key_error + Repair, offline и два устройства, options flow (копирование/смена ключа), reconfigure.
восстановление, два устройства (разные порты прослушивания), options flow
(копирование/смена ключа), reconfigure без потери конверсий.
## 4. Ограничения текущего стенда ## 5. Ограничения стенда
* Реального железа/облака в CI нет: протокол проверен mock-модулем * Реального железа/облака в CI нет: протокол проверен mock-модулем,
(`tests/ayla/mock_ac.py`), облако — мок-сервером; приёмочный скрипт имеет облако — мок-сервером; приёмочный скрипт имеет self-test на моке HA REST.
self-test на моке HA REST.
* Суточный soak, re-key и облачный вход — только на приборе/аккаунте. * Суточный soak, re-key и облачный вход — только на приборе/аккаунте.
* ESPHome-канал приёмки требует установленного `aioesphomeapi` и * Gitea Actions: встроенный `GITEA_TOKEN` не публикует пакеты (нужен PAT);
работающего ESPHome-компонента (PLAN_ESPHOME); без них скрипт работает `actions/checkout`/`setup-python` тянутся с github.com (для изолированного
только со стороны HA. инстанса используйте абсолютные URL зеркал).
+36
View File
@@ -0,0 +1,36 @@
# FGLair — локальное управление кондиционерами Fujitsu General
Интеграция Home Assistant для кондиционеров Fujitsu General (FGLair / Ayla)
**по локальной сети, без облака в рантайме**. Протокол и ядро — C++
(`fgl-aircon`), доступ из HA — через python-пакет `pyfglair` (cffi).
## Возможности
* `climate`: режим (off/cool/dry/fan/heat/auto), скорость вентилятора,
swing (вертикальный и горизонтальный), preset ECO/BOOST, уставка 16–30 °C,
текущая температура;
* `switch`: economy, powerful, coil dry, min heat, outdoor low noise,
human det auto save, Wi-Fi LED, indoor fan control;
* `select`: положение заслонок, `sensor`: температура/код ошибки/состояние
связи, `binary_sensor`: connectivity и флаги op_status;
* настройка через облако FGLair (однократно), вручную или импортом
`config_*.json`; превью шаблона с ручными конверсиями;
* Repair при несовпадении LAN-ключа, диагностика и переводы en/ru.
## Установка
HACS работает только с публичными **GitHub**-репозиториями — используйте
GitHub-зеркало этого репозитория:
1. HACS → ⋮ → **Custom repositories** → URL GitHub-зеркала, категория
**Integration** → Add → Download → перезапуск HA.
2. Либо вручную скопируйте `custom_components/fglair/` в
`<config>/custom_components/`.
Требуется пакет `pyfglair` (манифест ставит его из PyPI) и HA ≥ 2025.1 на
Linux x86_64/aarch64.
![Обзор устройства](custom_components/fglair/screenshots/step-7.png)
Подробная инструкция — в
[`custom_components/fglair/README.md`](custom_components/fglair/README.md).
+3 -1
View File
@@ -1,5 +1,5 @@
[build-system] [build-system]
requires = ["setuptools>=68", "wheel"] requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
@@ -7,6 +7,8 @@ name = "pyfglair"
version = "1.0.0" version = "1.0.0"
description = "Local control of Fujitsu General (FGLair/Ayla) air conditioners: cffi bindings to the fgl-aircon C++ core" description = "Local control of Fujitsu General (FGLair/Ayla) air conditioners: cffi bindings to the fgl-aircon C++ core"
readme = "README.md" readme = "README.md"
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
"cffi>=1.15", "cffi>=1.15",
+3 -1
View File
@@ -1,6 +1,7 @@
"""Сборка wheel pyfglair: вместе с python-пакетом собирается libfgl-aircon.so """Сборка wheel pyfglair: вместе с python-пакетом собирается libfgl-aircon.so
из корня монорепо (cmake). Метаданные — в pyproject.toml; здесь только хуки из корня монорепо (cmake). Метаданные — в pyproject.toml; здесь только хуки
setuptools для сборки нативного ядра.""" setuptools для сборки нативного ядра."""
import os
from importlib.util import module_from_spec, spec_from_file_location from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path from pathlib import Path
@@ -32,7 +33,8 @@ class build_py(_build_py):
dest.mkdir(parents=True, exist_ok=True) dest.mkdir(parents=True, exist_ok=True)
for stale in dest.glob("libfgl-aircon.*"): for stale in dest.glob("libfgl-aircon.*"):
stale.unlink() stale.unlink()
_load_corebuild().build_core(dest=dest, bundled_mbedtls=True) bundled = os.environ.get("FGL_BUNDLED_MBEDTLS", "1") != "0"
_load_corebuild().build_core(dest=dest, bundled_mbedtls=bundled)
super().run() super().run()