Pas de souci, pas d’urgence !
Pour moi, la PR2 - protocole 3.5 est donc bien fonctionnelle. Je n’ai pas testé sur ma prod mais je peux si besoin.
Merci @GBoulvin ,
J’ai mis à jour l’image docker, qui prend désormais en charge le 3.4 aussi : c’est maintenant routé vers le fork @demirdeniz/tuyapi-newgen (le même qui gère le 3.5), et un parser version 3.4 a été ajouté au scan UDP local entre les parsers 3.1 et 3.5. Tu peux retester quand tu veux avec la nouvelle image.
Si possible tester également le scan UDP (indépendamment) pour être sur que tout fonctionne. Merci par avance
De mon côté normalement pas besoin : les changements ajoutent un routage strictement déclenché par protocolVersion === '3.4' ou '3.5'. Les devices 3.1 et 3.3 continuent d’utiliser la lib tuyapi legacy sans aucune modification du chemin d’appel — donc pas de risque de régression sur ce qui marchait déjà.
Cela dit, si tu veux profiter de l’occasion pour valider sur ta prod, c’est tout indiqué : tes devices 3.4 (qui ne marchaient pas) devraient maintenant être fonctionnels, et tes devices 3.1/3.3/3.5 doivent rester identiques à avant. Aucune obligation, c’est juste un bonus si tu as 5 min.
EDIT :
Pour info @pierre-gilles , j’ai renommé la PR en nommant l’ajout explicite 3.4 en plus de 3.5, les 2 passant par la même lib. PR un peu plus gorsse mais on reste sous les 600 lignes de changes, ce sont surtout les tests qui sont conséquent (représente la moitié des lignes ajoutées)
Bête question peut-être mais, l’image est toujours sur le même tag (pr1) ?
Oui toujours ^^ Vaut mieux demander ^^
Hello !
Testé la version dispo et le protocole 3.4 est détecté et fonctionne direct. La détection de l’adresse IP locale également (pour le 3.4, oui mais pas pour le 3.5. J’avais déjà remarqué mais mon installation réseau n’est pas la meilleure pour tester ça donc difficile d’affirmer que c’est dû au protocole).
Bon pour moi donc ! Merci !!
Les 2 PR suivantes sont en attente de review :
Ajout de la prise en charge des protocoles 3.4 et 3.5 :
master ← Terdious:tuya-local-protocol-3.5
ouvert 02:40AM - 21 Feb 26 UTC
### Pull Request check-list
To ensure your Pull Request can be accepted as fa… st as possible, make sure to review and check all of these items:
- [x] If your changes affect the code, did you write the tests?
- [x] Are tests passing? (`npm test` on both front/server)
- [x] Is the linter passing? (`npm run eslint` on both front/server)
- [x] Did you run prettier? (`npm run prettier` on both front/server)
- [x] If you are adding a new feature/service, did you run the integration comparator? (`npm run compare-translations` on front)
- [x] Did you test this pull request in real life? With real devices? If this development is a big feature or a new service, we recommend that you provide a Docker image to the community ([forum](https://community.gladysassistant.com/)) for testing before merging.
- [ ] If your changes modify the API (REST or Node.js), did you modify the API documentation? (Documentation is based on comments in code)
- [ ] If you are adding a new features/services which needs explanation, did you modify the user documentation? See [the GitHub repo](https://github.com/GladysAssistant/v4-website) and the [website](https://gladysassistant.com).
- [ ] Did you add fake requests data for the demo mode (`front/src/config/demo.js`) so that the demo website is working without a backend? (if needed) See [https://demo.gladysassistant.com](https://demo.gladysassistant.com).
NOTE: these things are not required to open a PR and can be done afterwards / while the PR is open.
### Description of change
## Summary
Adds support for **Tuya local protocols 3.4 and 3.5** to the Tuya integration. Stacks on top of [PR #2434](https://github.com/GladysAssistant/Gladys/pull/2434) which is now merged into `master`.
The official `tuyapi@7.2.0` only supports protocols 3.1 and 3.3 — it has **no cipher or parser** for 3.4 (the "3.4" dropdown option was pointing to a non-functional path) and obviously none for 3.5. A second client (`@demirdeniz/tuyapi-newgen`) is added and used **only** when `protocolVersion === '3.4'` or `'3.5'`. Protocols 3.1 and 3.3 keep using the original `tuyapi` client and are not impacted.
## Details
**Protocol routing**
- `tuya.localPoll.js` and `tuya.setValue.js` pick the local API class based on an `isNewGenProtocol` flag (`isProtocol34 || isProtocol35`).
- For 3.5 only: `keepAlive` is disabled and `socketTimeout` is floored at 5 s because the 3.5 handshake takes longer than the others.
- For 3.5 only: a small fallback chain (`{schema:true}` → `{schema:true, dps:[1]}` → `{}`) because some firmwares reject a bare schema get on first contact. 3.4 uses a single schema attempt.
- Protocol 3.4 reuses the existing `tuyapi-newgen` `_decrypt34` cipher and `0x6699/0x9966` UDP framing.
**UDP local scan**
- `tuya.localScan.js` now parses incoming packets with three `MessageParser` instances in order `[3.1, 3.4, 3.5]` and keeps the first one that succeeds.
- `localScan(input)` accepts either a number or an object so existing callers passing `{ timeoutSeconds: N }` still work.
- `timeoutSeconds` is sanitized into `[1, 30] s`.
**Robustness improvements (touched along the way)**
- `localPoll.timeoutMs` is sanitized into `[500, 30000] ms` to prevent immediate timeouts or pathologically long hangs.
- `setValue` now throws `BadParameters` when the external_id has no command instead of silently falling back to the cloud.
**Front (`TuyaDeviceBox.jsx`)**
- Protocol 3.5 is enabled in the dropdown (3.4 was already listed but non-functional before this PR).
- `pollLocal()` adds 3.5 to the fallback list and captures `currentDevice` once at the start of the async loop to avoid stale `this.state.device` reads across protocol attempts.
- Obsolete i18n keys `protocol35OptionUnsupported` and `protocolVersionRequired` removed (en/fr/de).
**Dependency**
- `@demirdeniz/tuyapi-newgen` is **pinned** to exact version `8.1.5` (no caret), since it is a single-maintainer scoped fork. License MIT, integrity hash present in lockfile.
## Scope
Computed against `master` (PR #2434 already merged).
- **server (prod)** : ~150 lines (3 files: `tuya.localPoll.js`, `tuya.localScan.js`, `tuya.setValue.js`)
- **server (tests)** : ~325 lines (3 files: `tuya.localPoll.test.js`, `tuya.localScan.test.js`, `tuya.setValue.test.js`)
- **front (prod)** : ~29 lines (`TuyaDeviceBox.jsx`)
- **translations** : ~18 lines (`en.json`, `fr.json`, `de.json`)
- **packages / lockfiles** : ~75 lines (`package.json`, `package-lock.json`)
Total : ~597 lines across 12 files.
## Validation
- `npm run test-service --service=tuya` → **159 passing, 0 failing**
- `eslint server/services/tuya server/test/services/tuya` → clean
- `eslint front/src/routes/integration/all/tuya` → clean
## Summary by CodeRabbit
* **New Features**
* Local device polling with multi-protocol fallback, UDP local discovery, connection status, manual disconnect, reconnect logic, and cloud/local mode controls.
* **UI**
* Richer Tuya setup UI with new credential fields and connection states; device view shows masked IP, product/device identifiers, protocol/IP controls, local-poll actions, GitHub issue shortcut, and improved discovery ordering/status.
* **Localization**
* Expanded DE/EN/FR translations for setup, discovery, devices, statuses, errors, and guidance.
* **Tests**
* Extensive new and updated tests for local poll/scan, discovery, config, status, setValue, and lifecycle.
Ajout des premières briques de mapping cloud / local et notamment la découverte UDP locale :
master ← Terdious:tuya-local-mapping-core
ouvert 12:04PM - 27 Feb 26 UTC
### Pull Request check-list
To ensure your Pull Request can be accepted as fa… st as possible, make sure to review and check all of these items:
- [x] If your changes affect the code, did you write the tests?
- [x] Are tests passing? (`npm test` on both front/server)
- [x] Is the linter passing? (`npm run eslint` on both front/server)
- [x] Did you run prettier? (`npm run prettier` on both front/server)
- [x] If you are adding a new feature/service, did you run the integration comparator? (`npm run compare-translations` on front)
- [x] Did you test this pull request in real life? With real devices? If this development is a big feature or a new service, we recommend that you provide a Docker image to the community ([forum](https://community.gladysassistant.com/)) for testing before merging.
- [ ] If your changes modify the API (REST or Node.js), did you modify the API documentation? (Documentation is based on comments in code)
- [ ] If you are adding a new features/services which needs explanation, did you modify the user documentation? See [the GitHub repo](https://github.com/GladysAssistant/v4-website) and the [website](https://gladysassistant.com).
- [ ] Did you add fake requests data for the demo mode (`front/src/config/demo.js`) so that the demo website is working without a backend? (if needed) See [https://demo.gladysassistant.com](https://demo.gladysassistant.com).
NOTE: these things are not required to open a PR and can be done afterwards / while the PR is open.
### Description of change
This PR introduces a mapping core for Tuya devices to make local/cloud handling more consistent and future device integrations easier.
## Summary
Brings PR3 in sync with master + PR1/PR2 review fixes (PR1 was squash-merged
in `bf8bbff1`; PR2 carries the master merge and its own review-feedback
batch). PR3's own contribution — Tuya mapping core, `convertDevice` /
`convertFeature` refactor, `tuya.deviceMapping` / `tuya.localMapping`,
poll hardening, and Smart Socket reference implementation — is preserved
on top of the aligned base.
## Details
Merge of `tuya-local-protocol-3.5` (PR2) into PR3 brings forward:
- master alignment via PR2's merge commit (`0c457670`): Gladys 4.73.0,
Matter.js 0.16.11, Airplay sender rewrite, energy/i18n updates
- PR1 review fixes baked-in via the master squash (single
`/configuration` endpoint, reconnect prototype refactor moved to
`lib/tuya.reconnect.js` with proper wiring in `lib/index.js`,
`stopReconnect()` call in `lib/tuya.disconnect.js`, `addSelector`
in `convertDevice` / `convertFeature`, `MarkupText` invalid-paragraph
fix in `SetupTab.jsx`, `discoverDevices` JSDoc fix, `bridge minimal`
`services/tuya/index.js`)
- PR2 review feedback (`6b760790`): exact `@demirdeniz/tuyapi-newgen`
pin to `8.1.5`, `socketTimeout`/`keepAlive` documented for protocol
3.5, DPS fallback chain documented, dropped redundant `localKey`
guard in `updateDiscoveredDeviceAfterLocalPoll`, dropped redundant
double-disconnect in `localPoll` outer catch
- PR2 protocol 3.4 newgen routing (`2679dba6`): `localScan` ordered
parsers `[3.1, 3.4, 3.5]`, `localPoll` / `setValue` route 3.4 via
`isNewGenProtocol`
Conflict resolutions (manual, then validated via tests + lint):
- `tuya.localPoll.js`: PR2 base (proto 3.4 routing + comments) +
PR3 add-on reposed (`logDps`, `addFallbackBinaryFeature`, `dps` passing)
- `tuya.poll.js`: PR3 refactor kept; obsolete PR2 baseline snippet dropped
- `tuya.convertFeature.js`: PR3 imports + PR1 `addSelector` combined
- `SetupTab.jsx` and `i18n/fr.json`: PR3 pre-PR1-fix workarounds dropped
- `tuya.poll.test.js`: PR3 tests kept (cover PR2 baseline by equivalence)
- `tuya.localScan.test.js`: PR2 added test
`should instantiate parsers for protocols 3.1, 3.4 and 3.5` kept
- `tuya.convertFeature.test.js`: selector expectation harmonised to
the slugified form (consistent with the rest of the file)
Validation:
- `npm run test-service --service=tuya`: **209 passing**
- `npx nyc --include='services/tuya/**'`: **99.59% lines / 99.60% stmts /
99.39% functions / 88.94% branches**
- `eslint server/services/tuya server/test/services/tuya`: clean
- `eslint front/src/routes/integration/all/tuya`: clean
## Scope
Scope vs the immediate parent branch in the stack
(`tuya-local-protocol-3.5`, PR #2438):
| Area | Insertions | Deletions |
|---|---:|---:|
| `server/services/tuya` (prod) | +987 | -178 |
| `server/test/services/tuya` (tests) | +1313 | -26 |
| `front/src/routes/integration/all/tuya` (prod) | +13 | -8 |
| **Total** | **+2313** | **-212** |
No translation, package or lockfile changes (the `localModeLimitInfo`
restoration in commit `865e2cc3` was a fix for a key dropped during the
merge of PR2, with zero net delta vs PR2).
27 files changed, 0 leak outside the Tuya scope.
## Summary by CodeRabbit
* **New Features**
* Local UDP discovery and local polling for Tuya devices; cloud/local mode toggle, manual cloud disconnect, and explicit Save → Connect flow.
* Real-time connection status and disconnect controls with improved reconnect handling.
* **Improvements**
* Updated setup copy and field labels (Client ID/Client Secret, App UID, App Username) with validation and user-friendly error messages.
* Device panel shows identifiers, IP/protocol, product info, local-poll controls/progress, success/error indicators and clearer "unknown" value labels.
@GBoulvin , si tu veux tester la PR3, j’ai créé une nouvelle image terdious/gladys:tuya-PR3, notamment :
en partant avec ta base du dernier test,
puis en réalisant un scan local auto UDP
puis en supprimant les appareils déjà enregistrés, en te déconnectant, puis en te reconnectant et en lançant de nouveau un scan local auto UDP, pour être sûr que tout est bien retrouvé.
Merci par avance.
C’est bon pour moi pour la PR2, c’est mergé et ça partira dans la prochaine version de Gladys
Pour la PR3, il y a des conflits du coup suite au merge, dis moi dès que je peux regarder !
Merci pour le développement !
Salut @Terdious ,
J’aimerais bien merger la PR 3 , mais il y a des conflits :
Dès que tu peux lancer Claude dessus pour les résoudre, dis moi et je mergerais
Salut @pierre-gilles ,
Je m’y remet rapidement et je regarde ça !!
Merci
EDIT : C’est bon pour moi.
Super merci @Terdious ! C’est mergé !
La PR 4 est prête ?
Salut @pierre-gilles ,
Merci pour le merge de la PR3
Pour la PR4 #2467 , elle est prête. Je viens de merger master dedans pour résoudre les 3 conflits que GitHub remontait
C’est bon pour moi pour la PR4, c’est mergé et ça partira dans la prochaine version de Gladys
Merci pour la PR
Top, merci beaucoup pour le merge de la PR4 et tout les précédent
Côté PR5, je viens de mettre à jour master dedans aussi, elle est prête à review : #2470
Petit point d’attention : tu peux prendre le temps pour celle-ci, je préfèrerais qu’on attende un retour de @GBoulvin sur l’image Docker Terdious:tuya-smart-meter (en cours de build) avant le merge. C’est la première intégration Tuya en gestion cloud + local complète, et la stratégie cloud Thing Model est nouvelle — un test en conditions réelles sur son compteur ne fait pas de mal avant que ça parte en release même si il l’a déjà testé précédemment, on a fait quelques changements dans les 3 premières PR (normalement non impactantes)
Salut @GBoulvin
La PR5 est prête côté code et l’image Docker Terdious:tuya-smart-meter va être disponible dans une 30aine de minutes (build en cours).
Est-ce que tu pourrais la tester sur ton Smart Meter quand tu auras un moment ? Le but est de valider en conditions réelles :
la détection du device en cloud
la lecture des valeurs (puissance instantanée, énergie totale, voltage, courant)
la stabilité du polling cloud sur la durée
la détection du device en local
la lecture des valeurs (puissance instantanée, énergie totale, voltage, courant) en local
la stabilité du polling local sur la durée
Ton retour conditionnera le merge côté Pierre-Gilles. Merci d’avance
Edit : De mon côté testé et fonctionnel (mais pas testé précisément avec un smart meter ^^)
Salut @Terdious ,
Petit souci de droit d’accès à ton image de test (pas publique ?)… Si tu sais y regarder… Désolé pour mon manque de réactivité !
Edit, j’ai trouve : terdious/gladys:tuya-smart-meter
Voici mon retour. J’ai testé et il semble que tout fonctionne. C’est stable, j’ai cliqué comme un sauvage et mis des zéros partout et tout a tenu Les unités sont bonnes, le polling local et cloud tournent sans souci.
Juste une chose sans trop d’importance :
Il est écrit forword au lieu de forward. Même pas sur que ça vienne de la PR o:-)
Question comptabilisation énergie, les calculs « toutes les 30 minutes » doivent encore être implémentés ou c’est déjà prévu ?
Je laisse tourner ce weekend pour voir ce que ça donne mais pour moi, c’est tout bon !
Merci @Terdious !
Oups !! ^^ Desolé pour le mauvais nom d’image.
Merci beaucoup à toi pour les tests @GBoulvin
Bien vu ^^ en effet, le code DP renvoyé par l’API Tuya pour ces compteurs est littéralement orthographié energy_forword_a / energy_forword_b, donc c’est une erreur de leur coté que j’ai repercuté par cohérence avec la documentation API … mais en effet ca me chagrine également
@pierre-gilles , qu’en penses-tu ? Conservation de l’erreur ou correction côté Gladys pour le nommage de la feature ?
Sinon, suite aux tests convaincant, je te laisse la main pour la review de la PR quand tu pourras
Correction côté Gladys Ce serait dommage de perpétuer une erreur dans nos propres fonctionnalités.
Après 48h de test, pas de bug, déconnexion ou qu’est-ce !
Salut @pierre-gilles ,
C’est fait, renommage des features. Merci à toi.
C’est bon pour moi pour lancer la review sur la PR5 - Ajoute de la prise en charge des Smart Meter, je te l’ai assignée :
master ← Terdious:tuya-v2-smart-meter-support
ouvert 01:16PM - 28 Feb 26 UTC
### Pull Request check-list
To ensure your Pull Request can be accepted as fa… st as possible, make sure to review and check all of these items:
- [x] If your changes affect the code, did you write the tests?
- [x] Are tests passing? (`npm test` on both front/server)
- [x] Is the linter passing? (`npm run eslint` on both front/server)
- [x] Did you run prettier? (`npm run prettier` on both front/server)
- [x] If you are adding a new feature/service, did you run the integration comparator? (`npm run compare-translations` on front)
- [x] Did you test this pull request in real life? With real devices? If this development is a big feature or a new service, we recommend that you provide a Docker image to the community ([forum](https://community.gladysassistant.com/)) for testing before merging.
- [ ] If your changes modify the API (REST or Node.js), did you modify the API documentation? (Documentation is based on comments in code)
- [ ] If you are adding a new features/services which needs explanation, did you modify the user documentation? See [the GitHub repo](https://github.com/GladysAssistant/v4-website) and the [website](https://gladysassistant.com).
- [ ] Did you add fake requests data for the demo mode (`front/src/config/demo.js`) so that the demo website is working without a backend? (if needed) See [https://demo.gladysassistant.com](https://demo.gladysassistant.com).
NOTE: these things are not required to open a PR and can be done afterwards / while the PR is open.
### Description of change
## Summary
- Add support for Tuya Smart Meter devices, based on issue #2464 (`product_id: bbcg1hrkrj5rifsd`).
- Add both cloud and local mappings for Smart Meter features.
- Route Tuya cloud reads through the Thing Model shadow API when devices do not expose legacy status specifications.
- Introduce a fixture-based Tuya test harness so adding new device types becomes more industrialized and easier to maintain.
- Curate user-facing feature names so Tuya code typos (`energy_forword_a`, `energy_reserse_b`) never surface in the UI: the Tuya code stays the API key (`external_id`) while the display name is taken from the mapping.
## Scope
Scope vs the immediate parent branch in the stack
(`tuya-local-github-issues`, PR #2467):
| Area | Insertions | Deletions |
|---|---:|---:|
| `server/services/tuya` (prod) | +515 | -146 |
| `server/test/services/tuya` (tests) | +2304 | -5 |
| `front/src/routes/integration/all/tuya` (prod) | +14 | -1 |
| `front/src/config/i18n` (translations) | +6 | -3 |
| **Total** | **+2839** | **-155** |
No package or lockfile changes. The bulk of the test delta comes from the
fixture-based onboarding harness (Smart Meter and Smart Socket reference
fixtures, per-device expected payloads, cloud/local events, mapping
sanity tests). Front production stays minimal because the GitHub issue
flow refactor already lives in PR4; PR5 only touches TuyaDeviceBox to
surface the cloud read strategy.
48 files changed, 0 leak outside the Tuya scope.
## Details
- Add Smart Meter device detection through Tuya mapping index.
- Add dedicated cloud mapping for Smart Meter values such as:
- total power
- forward energy total
- reverse energy total
- voltage
- current
- Add dedicated local DPS mapping for Smart Meter.
- Persist the cloud read strategy at device conversion time so runtime polling can directly choose the right Tuya cloud route.
- Support both Tuya cloud read strategies:
- legacy `/v1.0/iot-03/devices/{id}/status`
- thing shadow `/v2.0/cloud/thing/{id}/shadow/properties`
- Extend Tuya device readers to support Smart Meter values safely even when `specifications.status` is empty and the device only exposes a Thing Model.
- Add fixture-based tests covering:
- device conversion
- local mapping
- polling
- setValue flows
- Add reusable fixture helpers and example fixture structure so future Tuya devices can be integrated with less ad hoc test code.
- Feature naming (`convertFeature.js` + `mappings/cloud/smart-meter.js`):
- `convertFeature` now prefers a curated `mapping.name` over the raw Tuya code for the display name; device types without a mapping name keep the code as name (unchanged behaviour).
- The smart-meter cloud mapping defines clean English names for its 12 features (e.g. `energy_forword_a` -> "Forward energy A").
- Unit tests cover both the mapping-name override (typo kept only as the API key) and the code fallback.
## Why this matters
This PR does more than add one device.
It also introduces a repeatable fixture-based approach for Tuya device integration:
- take the raw Tuya device payload
- define the expected Gladys device output
- define expected poll/setValue behavior
- validate everything through reusable tests
It also closes an important gap for Tuya devices that are cloud-readable through the Thing Model shadow API but do not expose legacy cloud status specifications.
This makes onboarding future Tuya devices more industrialized, faster, and safer.
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added support for smart meter devices with multi-phase energy monitoring (power, voltage, current, energy export/import indices).
* Enabled local device polling for improved responsiveness and reduced cloud dependency.
* Enhanced device discovery and cloud read strategy optimization.
* **Documentation**
* Updated translations for energy sensor "Export index" feature in German, English, and French.
La PR6, PR regroupant principalement des correctifs, sera également prête pour la review :
master ← Terdious:tuya-follow-up-hardening
ouvert 02:55PM - 01 Mar 26 UTC
### Pull Request check-list
To ensure your Pull Request can be accepted as fa… st as possible, make sure to review and check all of these items:
- [ ] If your changes affect the code, did you write the tests?
- [ ] Are tests passing? (`npm test` on both front/server)
- [ ] Is the linter passing? (`npm run eslint` on both front/server)
- [ ] Did you run prettier? (`npm run prettier` on both front/server)
- [ ] Did you test this pull request in real life? With real devices? If this development is a big feature or a new service, we recommend that you provide a Docker image to the community ([forum](https://community.gladysassistant.com/)) for testing before merging.
NOTE: these things are not required to open a PR and can be done afterwards / while the PR is open.
### Description of change
## Summary
- Add an in-memory **degraded-local backoff** (`degradedDevices` map on the `TuyaHandler` instance): when a device repeatedly fails local communication with network errors (`ECONNRESET` / `ETIMEDOUT` / `EHOSTUNREACH` / ...), Gladys stops attempting local for 5 minutes and silently falls back to cloud, avoiding the cascading failure pattern observed on some Tuya 3.3 firmwares that enter a protective state after stress.
- New GET `/api/v1/service/tuya/local-status` endpoint and warning badge in `TuyaLocalPollSection` so the user knows when a device is in fallback mode and when it will recover. Clicking "Lecture locale des DP" resets the backoff for that device.
- Save-time fix on the Devices page: `tuya_mapping` (and other Tuya transient enrichment) is now preserved after `POST /api/v1/device`, so the "partial support" banner no longer appears erroneously between save and refresh.
- Final hardening pass: clearer local-poll error reporting, null-safety on cloud detail loading, sparse local-scan packet preservation, and a defensive guard in the Tuya cloud account validation flow.
- Coverage on `services/tuya/**` brought to 99.8% lines / 93.23% branches / 100% functions.
## Scope
Scope vs the immediate parent branch in the stack
(`tuya-v2-smart-meter-support`, PR #2470):
| Area | Insertions | Deletions |
|---|---:|---:|
| `server/services/tuya` (prod) | +190 | -13 |
| `server/test/services/tuya` (tests) | +1152 | -10 |
| `front/src/routes/integration/all/tuya` (prod) | +77 | -7 |
| `front/src/config/i18n` (translations) | +3 | 0 |
| **Total** | **+1422** | **-30** |
No package or lockfile changes.
31 files changed, 0 leak outside the Tuya scope.
## Details
### Degraded-local backoff (new)
- New module `server/services/tuya/lib/utils/tuya.degraded.js`:
- `isDegradingError(error)` — recognizes `ECONNRESET` / `ECONNABORTED` / `ECONNREFUSED` / `ETIMEDOUT` / `EHOSTUNREACH` / `EHOSTDOWN` / `ENETUNREACH` either via `.code` or via message substring
- `recordLocalFailure(map, deviceId, error, now?)` — accumulates failure timestamps in a sliding 30 s window, flips the entry to `status: 'degraded'` once 3 failures hit and pushes `until = now + 5 min`
- `recordLocalSuccess(map, deviceId)` and `resetLocalStatus(map, deviceId)` — clear the entry
- `getLocalStatus(map, deviceId, now?)` — returns `{status, until}` or `null`, auto-clearing expired entries
- `isLocalSkipNeeded(map, deviceId, now?)` — convenience boolean used by callers
- `getAllDegraded(map, now?)` — used by the front-facing endpoint
- `this.degradedDevices = {}` initialized in the `TuyaHandler` constructor (`server/services/tuya/lib/index.js`)
- `server/services/tuya/lib/tuya.poll.js`:
- skips the local branch with `fallback=device_degraded` when an active backoff exists
- calls `recordLocalSuccess` on a successful local poll (full or partial) and `recordLocalFailure` in the `catch (e)` of the local block
- `server/services/tuya/lib/tuya.setValue.js`:
- same logic in `runLocalSet`: skip when degraded, record success/failure based on outcome
- `server/services/tuya/api/tuya.controller.js`:
- new `GET /api/v1/service/tuya/local-status` returning either `{deviceId, status}` (when `?deviceId=X` is provided) or `{devices: {...}}` (full map of active entries)
- the existing `POST /local-poll` now resets the degraded entry up-front so the manual user-triggered test is never blocked by the backoff
### UI indicator
- `front/src/routes/integration/all/tuya/TuyaLocalPollSection.jsx`:
- `componentDidMount` and `componentDidUpdate` (on device change) refresh the local status via the new endpoint
- `pollLocal` re-fetches the status on both success and failure so the badge always reflects post-action state
- warning badge `alert alert-warning` rendered when `status === 'degraded'`, with the expected recovery time formatted as `HH:MM`
- 3 new i18n keys (`integration.tuya.device.localStatusDegraded`) for FR / EN / DE, with `{{time}}` interpolation
### Save-time `tuya_mapping` preservation
- `front/src/routes/integration/all/tuya/TuyaDeviceBox.jsx`:
- `saveDevice` merges the saved response with the previously-known transient fields (`tuya_mapping`, `specifications`, `tuya_report`) since `POST /api/v1/device` does not persist them
- `componentWillReceiveProps` adds `tuya_mapping` to the same carry-forward set already used for `specifications` and `tuya_report`
- Fixes the cosmetic "partial support" banner that appeared on the Devices page right after saving a Smart Socket with intentionally-ignored DPs (the banner disappeared on page refresh — the bug was purely transient state loss).
### Reliability hardening (already in this branch)
- Local poll diagnostics (`tuya.localPoll.js`):
- new `formatSocketError(err)` helper that maps `EHOSTUNREACH` / `EHOSTDOWN` / `ENETUNREACH` / `ECONNREFUSED` / `ETIMEDOUT` to `Local device unreachable at <ip>:6668` instead of the generic `Local poll socket error`
- Cloud device detail loading (`tuya.loadDeviceDetails.js`):
- null-safe access on fulfilled Tuya responses before reading `.result`, so a successful settled result with a missing `value` no longer throws
- Local scan resilience (`tuya.localScan.js`):
- duplicate device packets no longer overwrite previously discovered `ip` / `version` / `productKey` with sparse values (the richer prior entry is preserved)
- Cloud account validation (`tuya.connect.js`):
- empty response from the Tuya validation endpoint now maps to `TUYA_APP_ACCOUNT_UID_INVALID` instead of crashing on `.success`
### Tests & coverage
- 29 tests added for the degraded backoff:
- 18 unit tests on `tuya.degraded.js` (every public helper, every threshold, both fallback branches)
- 6 integration tests across `tuya.poll`, `tuya.setValue`, `TuyaController` (skip when degraded, record on failure, clear on success, reset on manual poll, `GET /local-status` in all 4 modes)
- 12 coverage-cleanup tests for `tuya.config.buildConfigHash`, `tuya.report.mergeTuyaReport` carry-forward paths, `TuyaController.saveConfiguration` and `req.body` defaults
- Final coverage on `services/tuya/**`: **99.8% lines / 93.23% branches / 100% functions / 100% statements**
## Notes
- This is the follow-up hardening PR on top of `tuya-v2-smart-meter-support` (#2470). The previously broader scope (cloud shadow reads, Smart Meter detection, GitHub issue flow, etc.) has been absorbed by the upstream stack now that PR3, PR4 and PR5 carry their own review fixes baked in.
- The degraded-local map lives only in process memory — Gladys restart resets all backoffs (intentional: a power-cycled or unplugged device should not be permanently blocked from local mode by a stale entry).
- The current implementation exposes a single `'degraded'` status. The API field `status` is forward-compatible: an `'unreachable'` value can be added later for devices that fail recovery across multiple backoff cycles, without API break. Same goes for per-user-tunable thresholds (currently constants in `tuya.degraded.js`) and persistence across restarts (could go through the Tuya gladys variables namespace if real-world need emerges).
- The 3 uncovered lines on `tuya.connect.js:56-58` are a defensive guard in `validateAppAccount`. The upstream check at line 89 already throws `ServiceNotConfiguredError` for empty `appAccountId`, so the inner guard is unreachable in runtime — kept for safety but not covered by tests.
## Summary by CodeRabbit
* **New Features**
* Local network discovery, polling and direct control for Tuya devices with protocol selection.
* GitHub issue creation workflow for devices with partial support.
* Expanded cloud vs. local setup UI with new labels, help text and validation messages (DE/EN/FR).
* **Improvements**
* Richer device metadata & feature mappings, including improved handling of partial/unknown features.
* Enhanced connection status reporting, manual disconnect and automatic reconnect behavior.
* Expanded test coverage and fixtures for Tuya flows.
Merci !!