59 Commits

Author SHA1 Message Date
Patrick Schurig
e7b751673d sunspec: record why meterSlaveId cannot be marked advanced
AC-7 asks for the meterSlaveId parameter to be advanced. That property
does not exist on nymea 1.15.2 param types: the metadata verifier
rejects unknown fields, so adding it makes the whole plugin fail to
load rather than merely hiding a parameter.

Measured by loading the plugin JSON, with and without the flag, into a
PluginMetadata linked against the installed libnymea, rather than read
from an upstream tree that may differ from the package.

Also records the three other divergences the multi unit ID work runs
into, two of which predate it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 08:50:18 +02:00
Patrick Schurig
3802807be7 sunspec: dispatch froniusConnection in setupThing()
setupThing() only knew about sunspecConnection and solarEdgeConnection, so a
froniusConnection thing fell into the final Q_ASSERT_X. In a release build the
assert is compiled out, setupThing() returns without calling info->finish(),
and the pairing hangs until the setup times out - with no message at all. In a
debug build nymead dies on the assert instead.

Take the same branch as the two vendors that already work: createConnection()
already reads the slave ID and picks the byte order for froniusConnection, so
no new logic is needed.

postSetupThing() and thingRemoved() are unchanged on purpose. The former only
special cases solarEdgeConnection, for the battery search a Fronius must not
run, and registers the refresh timer for every thing. The latter is keyed on
m_sunSpecConnections, which createConnection() now populates for Fronius as
well.

Makes the four cases of tst_sunspecplugin pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:20:09 +02:00
Patrick Schurig
c2eccd96e5 sunspec: add a failing test that sets up a Fronius connection
The existing suite only exercises free functions and the plugin JSON, so it
cannot see whether a thing class is dispatched at all. A thing class missing
from the dispatch in setupThing() falls into the final Q_ASSERT_X: in a debug
build the process dies, in a release build the assert is compiled out and
setupThing() returns without ever calling info->finish(), leaving the pairing
stuck with no message.

Split the tests into two suites. The logic one keeps its former scope and its
single dependency on libnymea-sunspec. The new one instantiates the plugin
behind the real ThingManager of libnymea-core and a faked HardwareManager, so
the dispatch of setupThing(), postSetupThing() and thingRemoved() is covered.
No network access takes place: the faked monitor is never reachable, so the
plugin never attempts a Modbus connection.

The four new cases fail: froniusConnection is not dispatched yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:18:41 +02:00
Patrick Schurig
372be1644c specs: record acceptance criteria status and remaining gaps 2026-07-31 10:33:49 +02:00
Patrick Schurig
89406495a8 sunspec: add Fronius vendor with its own control rules
Adds a fronius vendor and a froniusConnection thing class alongside the
generic sunspec and solarEdge ones, following the SolarEdge mechanism but
none of its workarounds - no proprietary battery block and no PV
production computed by difference, since the GEN24 exposes model 160.

FroniusRules holds the vendor decisions as pure functions:

  - discovery only keeps devices announcing Fronius as manufacturer;
  - the byte order is the SunSpec default, not the forced big endian that
    is specific to SolarEdge, and no endianness parameter is exposed;
  - meterSlaveId defaults to 200, 0 meaning no meter on this connection;
  - a hard 7 s floor on the poll interval. Polling faster can push the
    inverter offline towards the vendor portal, and recovery needs an
    AC+DC power cycle on site, so this is a guard in the code rather than
    a configurable default;
  - the registers the inverter accepts and then silently ignores are
    refused before anything reaches the wire;
  - the six HEMS battery modes map onto ChaGriSet, StorCtl_Mod, InWRte and
    OutWRte, negative meaning charge;
  - model 160 blocks are classified by IDStr, never by index: on a three
    or four MPPT inverter the battery blocks are not in position 3 and 4.

Storages under a Fronius connection default revertTime to 300 s.

Setpoints now reread the scale factor before writing and read the written
registers back afterwards, reporting a mismatch through a new
controlHealthy state - the only defence against writes that are accepted
and then ignored without a Modbus exception.

Byte order and the revert timer semantics on model 124 are inferences, not
measurements; see specs/fronius-sunspec.md, H-2 and H-3.
2026-07-31 10:33:12 +02:00
Patrick Schurig
c5c3f46f57 sunspec: arm the storage revert timer after every setpoint
setInOutWRteRvrtTms() existed but was never called. InOutWRte_RvrtTms is
the delay after which the inverter drops the setpoint and resumes its
native behaviour. Left at 0, a setpoint stays active indefinitely, HEMS
stopped, network lost or process crashed included - the battery stays
where the last command put it.

Add a per-thing revertTime parameter, 0..28800 s, defaulting to 0 so the
generic vendor keeps its current behaviour, and route every setpoint
through SunSpecStorageLogic::planSetpoint(). The plan orders the writes -
ChaGriSet, StorCtl_Mod, then the rates - and appends the revert timer
last, so the timer never starts counting against a setpoint that has not
been written yet.

Writes are issued sequentially rather than in parallel: the order is part
of the contract with the hardware.
2026-07-31 10:26:01 +02:00
Patrick Schurig
4ec7aa50e8 sunspec: reconcile storage states from the registers actions write
onStorageBlockUpdated() fed chargingRate from wChaGra() and dischargingRate
from wDisChaGra(). Those are the gradients, in percent of WChaMax per
second, and read only on several inverters. The matching actions write
InWRte and OutWRte, in percent of WChaMax. The user wrote one quantity and
read back another.

Feed the two rate states from inWRte() and outWRte(), and keep the
gradients under names that cannot be mistaken for setpoints:
chargeGradient and dischargeGradient, read only.

enableDischarging was never reconciled either, while enableCharging was.
It stayed frozen on the last written value and drifted silently as soon as
the inverter changed StorCtl_Mod on its own. Reconcile both flags.

The mapping moves into SunSpecStorageLogic so it can be exercised on a
register dump without a running nymea.
2026-07-31 10:22:44 +02:00
Patrick Schurig
abae49e058 sunspec: widen storage charge and discharge rate range
chargingRate and dischargingRate map onto InWRte and OutWRte of model 124,
which are int16 setpoints ranging from -100.00 to +100.00 percent of
WChaMax. Declaring them as 0..100 makes a forced charge structurally
inexpressible: nymea rejects the negative action parameter before it ever
reaches the plugin.

Also fixes the default value, which was the boolean false on an int state.
2026-07-31 10:20:05 +02:00
Patrick Schurig
d87a132f23 sunspec: add failing tests for every acceptance criterion
One test per AC of specs/fronius-sunspec.md, named test_AC_<n>_<what>.

Definition criteria (value ranges, thing classes, defaults) are checked by
re-reading integrationpluginsunspec.json. Behaviour criteria run either on
a model 124 register dump fed to the real libnymea-sunspec model, or on the
pure logic units the plugin is about to grow.

All red at this point: sunspecstoragelogic and froniusrules do not exist
yet, so the suite does not compile. That is the expected starting state.
2026-07-31 10:16:12 +02:00
Patrick Schurig
93ecfb4ca1 specs: add acceptance criteria for Fronius SunSpec support
AC-1..AC-4 are the four upstream anomalies of the generic sunspec vendor
(setpoint range, read/write mismatch on the rate states, enableDischarging
never reconciled, revert timer never written).

AC-5..AC-15 are the Fronius-specific behaviour: manufacturer filter, byte
order, connection parameters, polling floor, forbidden writes, the HEMS
intent to register table, scale factor reread, readback verification and
the model 160 block classification.

Points the design contract leaves open are recorded as explicit working
hypotheses (H-1..H-6) rather than blocking on them.
2026-07-31 10:16:06 +02:00
Patrick Schurig
bb842eea55 sunspec: vendor plugin sources from nymea-plugins-modbus 1.15.2
Vendor the sunspec plugin into this repo so the Fronius work and the
upstream storage fixes can live here. Sources only: build artefacts and
generated plugininfo headers are excluded.

libnymea-sunspec is deliberately NOT vendored - the plugin must stay
rebasable on upstream nymea, so it links against libnymea-sunspec-dev.
sunspec.pri resolves it via pkg-config, with a NYMEA_SUNSPEC_PATH
override for a local sysroot, mirroring modbus.pri.

See sunspec/VENDORED.md.
2026-07-31 10:15:59 +02:00
Patrick Schurig
cbfb5df23e abbterra: make ABB Terra AC addable over Modbus TCP (etm2..etm5)
Follow-up to the etm1 fixes, driven by live debugging against a real ABB
Terra AC 7 kW behind a Waveshare RS485->TCP gateway (192.168.1.132, slave 2):

- etm3: disable TCP auto-discovery (createMethods = "user" only). Discovery
  blind-scans the LAN and SIGSEGVs nymead as soon as any Modbus device answers
  :502 with an exception response. The crash is in the generated reachability
  handler (abbterramodbustcpconnection.cpp:2062 dereferences the check-reachable
  reply after it is freed); it could not be fixed from the discovery code alone.
  A gateway's slave id can't be discovered anyway, so manual add is required.
- etm2: still defer/guard AbbTerraTcpDiscovery::cleanupConnection() teardown
  (kept for correctness even though discovery is now off by default).
- etm4: setupTcpThing() never called connectDevice(), so manual setup hung until
  timeout. Start the connection when the network monitor reports the device.
- etm5: decode the serial-number and firmware register fields as BCD per ABB
  spec §5.1 (0x46 = week 46, not 70; 0x22 = 22 kW). The binary reading rejected
  real chargers ("does not match the ABB Terra AC Modbus register map").

Verified end to end: charger adds cleanly (TACW7-4-4624-S0034), states poll and
actions execute, no SIGSEGV. The underlying discovery/generated-code crash is
tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 12:14:44 +02:00
Patrick Schurig
368c80688a abbterra: fix TCP discovery SEGV + restore checkReachableRegister
Two coupled fixes so the ABB Terra AC can be added over Modbus TCP:

- Restore checkReachableRegister ("serialNumber") in the register JSON.
  The libnymea-modbus 1.15 code generator *requires* it — without it the
  connection classes (abbterramodbustcpconnection.h) are never generated and
  the plugin fails to build. Reverts the false lead in b23a9a2. The register
  is valid: mbpoll confirms holding reg 16384 (uint64) responds at slave 2.

- Fix a use-after-free in AbbTerraTcpDiscovery::cleanupConnection() that
  SIGSEGV'd nymead during network discovery whenever any Modbus device on the
  LAN answered on :502 with an exception (e.g. "illegal data address"). The
  method is invoked from several of the connection's own signals; it now
  bails out on the second (re-entrant) call and disconnects the connection's
  signals before disconnectDevice(), so the teardown can no longer re-enter
  while the QModbusReply "finished" handler is still dereferencing the reply.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:48:47 +02:00
Patrick Schurig
b23a9a2d00 abbterra: remove checkReachableRegister - not supported in libnymea-modbus 1.15 2026-07-03 09:55:59 +02:00
Patrick Schurig
c5347df0dd Merge branch 'main' of https://git.etm-powersync.fr/ETM-Schurig/etm-powersync-plugins-modbus 2026-06-25 08:40:32 +02:00
Patrick Schurig
bb4810ebf4 bump to 1.15.2+etm1 - build against nymea 1.15.2 2026-06-25 08:33:10 +02:00
Patrick Schurig
341ea364c9 abbterra: revert to etm6 - upstream blocks not compatible with libnymea-modbus 1.15 2026-06-25 02:19:09 +02:00
Patrick Schurig
03f504d09a abbterra: adopt upstream block structure - init/update/WO registers 2026-06-25 02:09:49 +02:00
Patrick Schurig
fe7c15d0b9 abbterra: split status block to respect 31-byte RT38B01 frame limit
The status block (12 registers × 2 words = 24 words) was read as a
single Modbus request, producing a 53-byte response that overflows the
RT38B01 433 MHz radio module's 31-byte frame limit.

Dissolve the status block into individual registers with
readSchedule:update so queuedRequests sends them one at a time
(max 9 bytes per response). The deviceInfo init block (8 words → 21
bytes) is left untouched.

Bump to 1.15.0+etm6.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 23:33:15 +02:00
Patrick Schurig
a068d5a2cd abbterra: fix RTU setup on slow radio links (433 MHz)
reachableChanged(true) never fires within the nymea setup timeout when
the RTU link is slow (e.g. 433 MHz radio relay). Add a fallback:
trigger initialize() immediately if master is already connected
(QTimer::singleShot 500 ms), and also on master::connectedChanged.
The existing reachableChanged path is kept for TCP-style RTU adapters.

Bump to 1.15.0+etm5.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 23:08:36 +02:00
Patrick Schurig
5d87de1814 abbterra: bump to etm4 - fix readSchedule=no on write-only registers 2026-06-24 22:32:43 +02:00
Patrick Schurig
26531fd58f abbterra: set readSchedule=no on write-only holding registers 2026-06-24 22:23:39 +02:00
Patrick Schurig
4ad670c45d abbterra: fix readSchedule init on write-only holding registers 2026-06-24 22:14:44 +02:00
Patrick Schurig
577535d2b2 Add AGENTS.md 2026-06-13 14:10:51 +02:00
Patrick Schurig
4a2e5ce9e1 eastron: displayName par fonction (Compteur réseau / Onduleur PV / Consommateur) — convention ETM, AGENTS.md §3.4 2026-06-13 14:07:37 +02:00
0f09b647ed modbus-protocol-eastron-V2
description des registres
2026-06-13 11:14:27 +02:00
Patrick Schurig
f814720962 docs(v2c): PORTING_STATUS à jour - cause racine martèlement résolue (connexion persistante), lecture+écriture validées, reste régulation VE 2026-06-13 11:00:22 +02:00
Patrick Schurig
186f195900 v2c: connexion TCP persistante — fin des rafales SYN
Diagnostic tcpdump : le plugin ouvrait/fermait la socket Modbus TCP à
chaque cycle de poll, générant 6-7 paquets SYN consécutifs qui saturaient
l'ESP32 WiFi de la borne (timeouts en boucle). mbpoll avec une connexion
persistante = 0 % d'échec.

Corrections :
- connectDevice() : guard "si déjà connecté → no-op" (élimine le warning
  "already in ConnectedState" et les FIN/RESET parasites).
- onConnectionStateChanged(true) : appelle initialize() directement au lieu
  de setReachable(true) — évite la ré-entrée initialize() via reachableChanged.
- initialize() : setReachable(true) uniquement au succès, après lecture
  MinIntensity/MaxIntensity ; guard || m_updating ajouté.
- update() : guard || m_initializing ajouté (pas de poll concurrent avec init).
- Timer : if (!master->update()) master->connectDevice() — le socket reste
  ouvert tant que la connexion TCP est active ; connectDevice() n'est appelé
  que quand le TCP est réellement coupé.
- initializationFinished(thing) : master->update() immédiat après reconnect.
- reachableChanged handler : suppression de l'appel à initialize() (déplacé).

Résultat attendu tcpdump : 1 seul SYN à l'établissement, puis uniquement
des échanges Modbus (P. length 12/13) toutes les 30 s, aucun FIN/RESET.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 10:37:09 +02:00
Patrick Schurig
189d58d9ce v2c: hw-validated improvements (Trydan fw2.4.6)
- Decode SlaveError (reg 0x0BC5) with full table (codes 0-10) into new
  slaveErrorMessage state; code 04 (WiFi reconnect) also surfaces in
  statusMessage for immediate SAV visibility.
- Add L1/L2/L3 phase powers (regs 0x0BD9-0x0BDB, confirmed hw) as
  diagnostic states polled every 30 s.
- Add RegChargeTime (0x0BC6) to enum for completeness (not yet exposed).
- Collapse debian/changelog to single 1.15.0+etm1 entry (new plugin).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 09:26:53 +02:00
Patrick Schurig
076a0dcae9 feat(v2c): remove NetworkDeviceMonitor, add poll-based connectivity + statusMessage
The Trydan ESP32 WiFi stack saturates under nymea's periodic ICMP pings
(observed: latency > 14 s, Modbus timeouts, charger drops off).

Changes:
- NetworkDeviceMonitor removed entirely from setupThing() and thingRemoved().
  The charger is expected to have a DHCP-reserved IP (documented in code).
  Address comes from trydanThingAddressParamTypeId param — already the correct
  path after the previous setup fix.
- Connectivity now derives from Modbus poll success/failure only (reachable()
  from TrydanModbusTcpMaster), NOT from network ping.  A ping response does
  not imply Modbus usability on this hardware.
- postSetupThing() timer (30 s): if not reachable, calls connectDevice() for
  reconnect; if reachable, calls update().  The 30 s period IS the backoff —
  no aggressive retry loop.
- Modbus timeout: 2 s → 5 s (tolerates residual WiFi latency spikes).
- Modbus retries per read: 1 → 0 (abort fast on first timeout; full poll
  sequence already aborts at first error via doNextRead).
- k_errorLimit: 5 → 3 (3 × 5 s = 15 s before marking unreachable).
- New state "statusMessage" (QString): set on disconnect with timestamp +
  cause ("timeout Modbus — vérifier signal WiFi de la borne"); cleared on
  successful poll.  Visible in nymea-app; helps SAV without SSH access.
- "networkdevice" removed from JSON interfaces (contract broken without monitor).

Invariants preserved: Big/Big float32 decode, no block read, PauseState+Lock
mirror, PauseDynamic conflict management, writeCompleted address filtering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 18:15:30 +02:00
Patrick Schurig
d838bce331 fix(v2c): set host address before connectDevice() in setupThing()
Bug: ModbusTcpMaster: device ':502' ConnectionError when adding a trydan
by manual IP (createMethod "user") or after nymead restart before the
NetworkDeviceMonitor completes its ARP scan.

Root cause: TrydanModbusTcpMaster was constructed with
monitor->networkDeviceInfo().address(), which is null at first
registerMonitor() — the monitor only populates its cache after an
asynchronous ARP/mDNS resolution. connectDevice() then ran against
QHostAddress() → ":502".

Fix:
- Read the initial IP from the stored address param (always set at
  discovery or user-entry) and use it as the fallback when the monitor
  has not yet resolved the address.
- Guard the networkDeviceInfoChanged callback: only call setHostAddress()
  when the new address is non-null (the monitor emits transient null info
  during its internal refresh cycles).

Order is now: create master with valid address → wire callbacks →
connectDevice(). The monitor update path remains for DHCP renewals.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 17:13:37 +02:00
Patrick Schurig
2ac00e1330 fix(v2c): correct logging category dcV2C + debian packaging + modbus.pri path
Build fixes found during first local compilation (qmake6 + g++ -Werror):

1. Logging category: nymea-plugininfocompiler generates 'dcV2C' (uppercase C)
   from the JSON vendor name "V2C". All three source files used 'dcV2c'
   (lowercase c) — corrected to 'dcV2C' in integrationpluginv2c.cpp,
   trydanmodbustcpmaster.cpp, v2ctcpdiscovery.cpp.

2. modbus.pri: hardcoded include(/usr/include/nymea-modbus/modbus-tool.pri)
   fails when libnymea-modbus-dev is not system-installed. Changed to a
   conditional: checks $$[QT_INSTALL_PREFIX] first, then NYMEA_MODBUS_PATH
   override, with a graceful fallback (warning + manual C++17 CONFIG flag).
   The v2c plugin has MODBUS_CONNECTIONS empty so the tool loop is a no-op
   either way; the fallback is safe.

3. debian/: add powersync-plugin-v2c package entry (control, .install,
   changelog entry 1.15.0+etm4).

Build verified: libnymea_integrationpluginv2c.so links against libnymea.so.1,
libnymea-modbus.so.1, Qt6Network/SerialBus/Core, 0 compiler warnings
(with -Werror -Wall -Wextra -std=c++17).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 15:20:11 +02:00
Patrick Schurig
f0be2d00a2 fix(v2c): use qBound for min/maxIntensity clamp in initialize()
qMin(32, m_maxIntensity) gives 0 when firmware returns 0 for MaxIntensity
(aberrant value).  maxIntensity()==0 then causes every setMaxChargingCurrent
call to be clamped to 0, falling into the <6 A path which pauses charging
silently — the user sees the action succeed but the charger stops.

qBound(6, val, 32) is correct in all cases:
  - firmware reports 16 A (mono Trydan) → 16 (real installation limit kept)
  - firmware reports 0 (aberrant)        →  6 (safe floor, does not claim 32 A)
  - firmware reports 65535 (aberrant)    → 32 (IEC absolute ceiling)

The real installation limit still comes from MaxIntensity read on the device;
the bounds [6, 32] are only IEC guardrails, not hardcoded defaults.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 14:22:13 +02:00
Patrick Schurig
18655452d9 feat(v2c): add integration plugin, JSON metadata, and build plumbing
Étape 1 complete: V2C Trydan nymea plugin (Modbus TCP, status Partial).

Plugin (IntegrationPluginV2c):
  - discoverThings: delegates to V2cTcpDiscovery, maps results to
    ThingDescriptors; createMethod "user" bypasses discovery.
  - setupThing: registers a NetworkDeviceMonitor (IP tracking on DHCP
    renewal), creates TrydanModbusTcpMaster, wires reachableChanged →
    initialize() → state update on initializationFinished(), then starts
    periodic polls on a 30s PluginTimer.
  - updateThingStates: ChargeState 0/1/2 → pluggedIn/charging;
    power = (PauseState==0 AND Lock==0) per evcc trydan.go Enabled().
  - handleDynamicConflict: if Dynamic==1 (V2C internal PV optimizer),
    write PauseDynamic=1 to suppress it.  NEVER write Dynamic=0 (silences
    ChargePower telemetry).  Writes only on state transition to minimize
    flash wear.  cf. github.com/evcc-io/evcc/issues/28047.
  - Action "power": writes PauseState then Lock (always in mirror), then
    fire-and-forgets PauseDynamic if Dynamic==1.  Action handlers filter
    writeCompleted by register address to avoid reacting to concurrent
    background writes.
  - Action "maxChargingCurrent": clamps to [minIntensity, maxIntensity],
    writes Intensity.  Below 6A → pause instead of invalid setpoint.

JSON (integrationpluginv2c.json):
  - ThingClass "trydan" implementing evcharger + connectable + networkdevice.
  - settingsTypes: suspendInternalOptimizer (bool, default true) — allows
    the user to disable Dynamic conflict management if they want the V2C
    PID to remain active alongside the HEMS.
  - State "chargeEnergy" exposed as diagnostic only (NOT sessionEnergy) —
    firmware reliability on this register is unvalidated; cf. evcc #28047.

Build (v2c.pro):
  - MODBUS_CONNECTIONS intentionally empty: the modbus-tool code generator
    assumes non-overlapping block reads; leaving it empty skips generation
    while still linking nymea-modbus via the PKGCONFIG in modbus.pri.

VendorId 56c3e7bb… is new (no existing V2C vendor in the repo).
PluginId  f0692725… is new (generated for this plugin).

PORTING_STATUS_modbus.md documents register sources, the 4 items that
require validation on real hardware, and the "no beta matrix" constraint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 13:49:49 +02:00
Patrick Schurig
947da7e1fc feat(v2c): add HTTP LAN discovery (V2cTcpDiscovery)
Architecture choice: Modbus TCP has no network discovery mechanism, so
discovery piggybacks on nymea's NetworkDeviceDiscovery (ARP/mDNS scan)
and then probes each host via GET /RealTimeData (V2C Trydan HTTP REST API).

The presence of a "Paused" field in the JSON response is used as a Trydan
fingerprint — it matches the charger model and is absent on generic devices.
cf. evcc charger/trydan.go realTimeData struct.

Probes run concurrently (one QNetworkReply per host) but the sequence
terminates via two independent paths that could both fire:
  1. The NetworkDeviceDiscoveryReply::finished callback arms a 3-second
     QTimer::singleShot to give outstanding probes time to complete.
  2. Each probe completion path checks (m_scanFinished && pendingProbes==0)
     and calls finishDiscovery() early if all probes are already done.
A m_finished bool guard prevents double-emission of discoveryFinished()
if both paths fire in quick succession (e.g. all probes complete exactly
as the timer fires).

Manual-entry (createMethod "user") skips discovery entirely; setupThing()
is called directly with the user-supplied IP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 13:49:22 +02:00
Patrick Schurig
14f07cc638 feat(v2c): add Modbus transport abstraction + TCP implementation
Architecture choice: separate the register map / business logic from the
physical transport.  TrydanModbusMaster (abstract QObject) holds:
  - the complete ReadRegister / WriteRegister enums with addresses
  - float32 Big/Big decode helpers (memcpy pattern, matches pymodbus
    BinaryPayloadDecoder byteorder=Endian.Big wordorder=Endian.Big)
  - all last-polled cached values accessible via simple getters

TrydanModbusTcpMaster (concrete, Étape 1) wraps libnymea-modbus
ModbusTcpMaster.  Each register read is a distinct FC3(addr, 2)
transaction via a recursive runReadSequence/doNextRead pattern —
never a block read, because the V2C firmware's register windows
overlap (0x0BC2 and 0x0BC3 each span 2 registers, so a range read
starting at 0x0BC2 for ≥3 registers returns garbage for the second
value).  Writes use FC6 (single uint16, not float).

writeCompleted(quint16 address, bool success) carries the originating
register address so that concurrent background writes (e.g. PauseDynamic
from the conflict manager firing during an action) do not interfere with
action handlers waiting on a specific register's acknowledgement.

0x177E (Dynamic) is intentionally absent from WriteRegister: writing it
to 0 silences ChargePower telemetry even though the charger keeps running.
cf. evcc charger/trydan.go and github.com/evcc-io/evcc/issues/28047.

Étape 2 (RTU) will add TrydanModbusRtuMaster on the same interface;
no changes to plugin or register logic will be required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 13:49:11 +02:00
8f017dbb91 Initial commit V2C Trydan 2026-06-07 07:50:35 +02:00
368fc174bf Revert "Reapply "remove empty placeholder json (plugin en cours)""
This reverts commit d09db4fa1643fbd609517e4d7ad86c624728c800.
2026-06-02 15:22:42 +02:00
d09db4fa16 Reapply "remove empty placeholder json (plugin en cours)"
This reverts commit 1699e56f55fafc46bc84258ead447ffdace75190.
2026-06-02 15:17:48 +02:00
1699e56f55 Revert "remove empty placeholder json (plugin en cours)"
This reverts commit 67836a883770a723c4fb5ef7eb5822d2d5485461.
2026-06-02 15:16:49 +02:00
67836a8837 remove empty placeholder json (plugin en cours) 2026-06-02 15:05:40 +02:00
Patrick Schurig
c82b00ddfa abbterra: communicationTimeoutReadback en 1er niveau (update) pour generer la methode reclamee par applyTimeoutSetting 2026-06-01 09:58:37 +02:00
Patrick Schurig
e0582d1fff abbterra: restaure le .h original (declaration applyTimeoutSetting manquait, .cpp et .h desynchronises) 2026-06-01 09:45:31 +02:00
Patrick Schurig
8086e80f20 abbterra: WO -> RW sur les 4 registres de commande (generateur 1.15 ne gere pas WO pur en 1er niveau) 2026-06-01 09:42:29 +02:00
Patrick Schurig
653418ce30 abbterra: abbterra-registers.json) 2026-06-01 09:39:24 +02:00
Patrick Schurig
95177a417a abbterra: readSchedule:write sur les WO (cle presente, valeur neutre -> ni KeyError ni updateXxxCommand) 2026-06-01 09:16:06 +02:00
Patrick Schurig
09dd698cf6 abbterra: restaure pilotage (cpp+json upstream) + registers.json sans readSchedule vide (compat generateur 1.15) 2026-06-01 09:09:22 +02:00
Patrick Schurig
47257a6a29 abbb2x: registers.json avec cle registers au 1er niveau (vrai contenu, 11 registres) 2026-06-01 08:52:12 +02:00
Patrick Schurig
14a2ae89c1 abbb2x: registers.json - cle 'registers' au 1er niveau (fix KeyError generateur) 2026-06-01 08:48:50 +02:00
Patrick Schurig
3cbce56454 abbterra: retire les registres WO pour build sur generateur 1.15 (lecture seule)
- abbterra-registers.json : suppression des 4 registres WO
  (chargingCurrentLimitCommand, socketLockCommand, startStopChargingSession,
  communicationTimeoutCommand) incompatibles avec le generateur libnymea-modbus 1.15
- integrationpluginabbterra.cpp/.h : suppression de executeAction + applyTimeoutSetting
- integrationpluginabbterra.json : power + maxChargingCurrent passes en lecture seule
  (writable/displayNameAction retires)
La borne sera reconnue et lira les mesures ; le pilotage sera rebranche
quand le generateur 1.16 sera disponible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 08:29:36 +02:00