No worries, no rush!
For me, the PR2 - protocol 3.5 is therefore working fine. I haven’t tested it on my production environment, but I can if needed.
Thanks @GBoulvin ,
I’ve updated the Docker image, which now also supports 3.4: it’s now routed to the fork @demirdeniz/tuyapi-newgen (the same one that handles 3.5), and a 3.4 version parser has been added to the local UDP scan between the 3.1 and 3.5 parsers. You can retest whenever you want with the new image.
If possible, also test the UDP scan (independently) to ensure everything works. Thanks in advance
On my side, normally no need: the changes add routing strictly triggered by protocolVersion === '3.4' or '3.5'. Devices 3.1 and 3.3 continue to use the legacy tuyapi library without any modification to the call path — so no risk of regression on what already worked.
That said, if you want to take the opportunity to validate on your production environment, it’s ideal: your 3.4 devices (which didn’t work) should now be functional, and your 3.1/3.3/3.5 devices should remain the same as before. No obligation, it’s just a bonus if you have 5 minutes.
EDIT :
For info @pierre-gilles , I renamed the PR by explicitly naming the 3.4 addition in addition to 3.5, both using the same library. The PR is a bit larger, but we’re still under 600 lines of changes, it’s mainly the tests that are significant (represent half of the added lines)
Dumb question maybe, but is the image still on the same tag (pr1)?
Yes, always ^^ Better to ask ^^
Hello!
I tested the available version and protocol 3.4 is detected and works directly. Local IP address detection also works (for 3.4, yes, but not for 3.5. I had already noticed, but my network setup isn’t the best for testing this, so it’s hard to confirm it’s due to the protocol).
All good for me then! Thanks!!
The following 2 PRs are awaiting review:
Adding support for protocols 3.4 and 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.
Adding the first cloud/local mapping bricks and notably local UDP discovery:
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 , if you want to test PR3, I created a new image terdious/gladys:tuya-PR3, specifically:
starting with your database from the last test,
then performing an auto UDP local scan
then deleting the already registered devices, logging out, then logging back in and running another auto UDP local scan, to make sure everything is properly found.
Thanks in advance.
That’s good for me for PR2, it’s merged and will go out in the next version of Gladys
For PR3, there are conflicts after the merge, let me know when I can take a look!
Thanks for the development!
Hi @Terdious ,
I’d really like to merge the PR 3 , but there are conflicts:
As soon as you can run Claude on it to resolve them, let me know and I’ll merge it
Hi @pierre-gilles ,
I’ll get back to it quickly and take a look!!
Thanks
EDIT: All good for me.
Super thanks @Terdious ! It’s merged!
Is PR 4 ready?
Hi @pierre-gilles ,
Thanks for merging the PR3
For the PR4 #2467 , it’s ready. I just merged master into it to resolve the 3 conflicts that GitHub was reporting
It’s good for me for PR4, it’s merged and it will go out in the next version of Gladys
Thanks for the PR
Top, thanks a lot for merging PR4 and all the previous ones
As for PR5, I just updated master in it as well, it’s ready for review: #2470
Quick note : you can take your time with this one, I’d prefer we wait for feedback from @GBoulvin on the Docker image Terdious:tuya-smart-meter (currently building) before merging. It’s the first complete Tuya integration for cloud + local management, and the cloud Thing Model strategy is new — real-world testing on his meter wouldn’t hurt before it goes into release, even though he’s already tested it before. We made a few changes in the first three PRs (supposedly non-impactful)
Hi @GBoulvin
PR5 is ready on the code side and the Docker image Terdious:tuya-smart-meter will be available in about 30 minutes (build in progress).
Could you test it on your Smart Meter when you have a moment? The goal is to validate in real-world conditions:
cloud device detection
reading values (instantaneous power, total energy, voltage, current)
cloud polling stability over time
local device detection
reading values (instantaneous power, total energy, voltage, current) locally
local polling stability over time
Your feedback will determine the merge on Pierre-Gilles’ side. Thanks in advance
Hi @Terdious ,
Small access rights issue with your test image (not public?). If you know where to look… Sorry for my lack of responsiveness!
Edit, I found it: terdious/gladys:tuya-smart-meter
Here’s my feedback. I tested it and it seems that everything works. It’s stable, I clicked like a madman and put zeros everywhere, and everything held up The units are good, local and cloud polling are running smoothly.
Just one thing of little importance:
It says „forword“ instead of „forward“. I’m not even sure if this comes from the PR o:-)
Regarding energy accounting, are the „every 30 minutes“ calculations still to be implemented or is this already planned?
I’ll let it run this weekend to see how it goes, but for me, it’s all good!
Thanks @Terdious !
Oops!! ^^ Sorry for the wrong image name.
Thanks a lot for the tests @GBoulvin
Well spotted ^^ indeed, the DP code returned by the Tuya API for these meters is literally spelled energy_forword_a / energy_forword_b, so it’s an error on their side that I replicated for consistency with the API documentation… but indeed, it bothers me too
@pierre-gilles , what do you think? Keep the error or correct it in Gladys for the feature naming?
Otherwise, following the convincing tests, I’ll leave you in charge of the PR review when you can
Fix it on Gladys’s side It would be a shame to perpetuate an error in our own features.
After 48 hours of testing, no bugs, disconnections, or anything!
Hi @pierre-gilles ,
It’s done, features renamed. Thanks to you.
I’m good to go for the review on PR5 - Adding support for Smart Meter, I’ve assigned it to you:
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.
PR6, a PR mainly grouping fixes, will also be ready for 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.
Thanks!!