etm-powersync-plugins-modbus/sunspec/integrationpluginsunspec.h
Patrick Schurig 0954f66725 sunspec: let createConnection() serve a secondary connection
Takes the slave ID as an argument instead of reading it from the thing,
and takes whether it is the primary connection of that thing. Only the
primary one follows the network monitor, writes the connected state of
the thing and lands in m_sunSpecConnections; a secondary one lands in a
hash of its own.

Child things are updated per connection rather than per parent, resolved
from the model each child is bound to. With a single connection that is
every child of the parent, so nothing changes here yet.

No caller creates a secondary connection at this point: behaviour is
unchanged for all three vendors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 08:53:28 +02:00

176 lines
7.5 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins-modbus.
*
* nymea-plugins-modbus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins-modbus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins-modbus. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef INTEGRATIONPLUGINSUNSPEC_H
#define INTEGRATIONPLUGINSUNSPEC_H
#include <integrations/integrationplugin.h>
#include <network/networkdevicemonitor.h>
#include <plugintimer.h>
#include <sunspecconnection.h>
#include <models/sunspecmodelfactory.h>
#include "sunspecthing.h"
#include "sunspecstoragelogic.h"
#include "froniusrules.h"
#include "extern-plugininfo.h"
#include <QUuid>
class IntegrationPluginSunSpec: public IntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginsunspec.json")
Q_INTERFACES(IntegrationPlugin)
public:
explicit IntegrationPluginSunSpec();
void init() override;
void discoverThings(ThingDiscoveryInfo *info) override;
void setupThing(ThingSetupInfo *info) override;
void postSetupThing(Thing *thing) override;
void thingRemoved(Thing *thing) override;
void executeAction(ThingActionInfo *info) override;
private:
// SunSpec Connection params map
QHash<ThingClassId, ParamTypeId> m_connectionDiscoverySlaveIdParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_connectionMacAddressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_connectionAddressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_connectionHostNameParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_connectionPortParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_connectionSlaveIdParamTypeIds;
// SunSpec thing params map
QHash<ThingClassId, ParamTypeId> m_modelIdParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_modbusAddressParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_manufacturerParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_deviceModelParamTypeIds;
QHash<ThingClassId, ParamTypeId> m_serialNumberParamTypeIds;
PluginTimer *m_refreshTimer = nullptr;
QHash<Thing *, NetworkDeviceMonitor *> m_monitors;
/* The primary connection of a thing: the one that carries its identity, its
* connected state and its timeout and retry settings. Every vendor has
* exactly one. */
QHash<ThingId, SunSpecConnection *> m_sunSpecConnections;
/* Second, optional connection towards the Smart Meter of a Fronius GEN24,
* on the same host and port as the primary one but on another unit ID.
* Indexed on the same parent ThingId.
*
* Kept apart from m_sunSpecConnections rather than turning that one into a
* hash of lists: this way not a single call site of the generic and
* solarEdge vendors changes, including the reverse lookup
* m_sunSpecConnections.key() the SolarEdge battery search relies on. */
QHash<ThingId, SunSpecConnection *> m_meterConnections;
QHash<Thing *, SunSpecThing *> m_sunSpecThings;
QHash<Thing *, SunSpecModel *> m_sunSpecInverters;
QHash<Thing *, SunSpecModel *> m_sunSpecMeters;
QHash<Thing *, SunSpecModel *> m_sunSpecStorages;
/* Connections having completed at least one successful discovery. The
* orphan sweep needs to know whether it is looking at the whole picture or
* at half of it. Entries are removed when the connection is destroyed. */
QSet<SunSpecConnection *> m_discoveredConnections;
Thing *getThingForSunSpecModel(uint modelId, uint modbusAddress, const ThingId &parentId);
bool sunspecThingAlreadyAdded(uint modelId, uint modbusAddress, const ThingId &parentId);
void processDiscoveryResult(Thing *thing, SunSpecConnection *connection);
// SunSpec things
SunSpecConnection *createConnection(Thing *thing, uint slaveId, bool isPrimary);
/* Every connection of a parent thing, primary first. One entry for the
* generic and solarEdge vendors, one or two for Fronius. */
QList<SunSpecConnection *> connectionsFor(const ThingId &parentId) const;
QList<SunSpecConnection *> allConnections() const;
/* The connection a child thing reads its data from, or null while it is not
* bound to a model yet. Resolved from the model the child is bound to
* rather than from a persisted unit ID: a child stores only its model ID
* and start register, and the two connections of a Fronius thing never
* announce the same model. */
SunSpecConnection *connectionForChild(Thing *child) const;
/* Children of a parent that read from one given connection. A child not
* bound to any model yet follows the primary connection, which is what
* makes the parent reachable at all. */
QList<Thing *> childThingsOn(Thing *parent, SunSpecConnection *connection) const;
// Custom types
void setupSolarEdgeBattery(ThingSetupInfo *info);
// Discover SolarEdge batteries
void searchSolarEdgeBatteries(SunSpecConnection *connection);
void searchSolarEdgeBattery(SunSpecConnection *connection, const ThingId &parentThingId, quint16 startRegister);
// SolarEdge
double calculateSolarEdgePvProduction(Thing *thing, double acPower, double dcPower);
void autocreateSunSpecModelThing(const ThingClassId &thingClassId, const QString &thingName, const ThingId &parentId, SunSpecModel *model);
QString getInverterStateString(quint16 state);
QString getInverterErrorString(quint32 flag);
double fixValueSign(double targetValue, double powerValue);
bool hasManufacturer(const QStringList &manufacturers, const QString &manufacturer);
void markThingStatesDisconnected(Thing *thing);
// Fronius
bool isFroniusChild(const ThingId &parentId);
int effectiveUpdateInterval();
// Storage setpoints
void executeSetpointPlan(ThingActionInfo *info, SunSpecStorageModel *storage, const SunSpecStorageLogic::SetpointPlan &plan);
void executeSetpointWrite(ThingActionInfo *info, SunSpecStorageModel *storage, const SunSpecStorageLogic::SetpointPlan &plan, int index);
QModbusReply *writeSetpointRegister(SunSpecStorageModel *storage, const SunSpecStorageLogic::Write &write);
double readSetpointRegister(SunSpecStorageModel *storage, SunSpecStorageLogic::Register reg);
void refreshStorageBlock(ThingActionInfo *info, SunSpecStorageModel *storage, const std::function<void()> &continuation);
void verifySetpointPlan(ThingActionInfo *info, SunSpecStorageModel *storage, const SunSpecStorageLogic::SetpointPlan &plan);
private slots:
void onRefreshTimer();
void onPluginConfigurationChanged(const ParamTypeId &paramTypeId, const QVariant &value);
void onInverterBlockUpdated();
void onMeterBlockUpdated();
void onStorageBlockUpdated();
void onSolarEdgeBatteryBlockUpdated();
void evaluateEnergyProducedValue(Thing *inverterThing, float energyProduced);
};
#endif // INTEGRATIONPLUGINSUNSPEC_H