diff --git a/README.md b/README.md index de5ce99..add6a0f 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,12 @@ The source code in this repository implements the EEBUS protocol, based on the [ ### Supported Use Cases -| Use Case | Actors | Scenarios | Description | -|----------|----------|-----------|---------------------------------| -| LPC | EG, CS | 1, 2, 4 | Limitation of power consumption | -| LPP | EG, CS | 1, 2, 4 | Limitation of power production | -| MPC | MA, MU | 1 - 5 | Monitoring of power consumption | +| Use Case | Actors | Scenarios | Description | +|----------|----------|-----------|--------------------------------------| +| LPC | EG, CS | 1, 2, 4 | Limitation of power consumption | +| LPP | EG, CS | 1, 2, 4 | Limitation of power production | +| MPC | MA, MU | 1 - 5 | Monitoring of power consumption | +| MGCP | MA, GCP | 1 - 7 | Monitoring of grid connection point | ### Limitations diff --git a/examples/heat_pump/hpsrv.c b/examples/heat_pump/hpsrv.c index f53baf0..ec70bd8 100644 --- a/examples/heat_pump/hpsrv.c +++ b/examples/heat_pump/hpsrv.c @@ -38,6 +38,7 @@ #include "src/spine/entity/entity_local.h" #include "src/use_case/actor/cs/lpc/cs_lpc.h" #include "src/use_case/actor/cs/lpp/cs_lpp.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp.h" #include "src/use_case/actor/mu/mpc/mu_mpc.h" static const int8_t kScaleDefault = -2; // Default scale for measurements @@ -57,6 +58,7 @@ struct Hpsrv { CsLpListenerObject* cs_lpp_listener; CsLpUseCaseObject* cs_lpp; MuMpcUseCaseObject* mu_mpc; + GcpMgcpUseCaseObject* gcp_mgcp; EebusCliObject* cli; }; @@ -92,8 +94,31 @@ static const ServiceReaderInterface hpsrv_methods = { }; static EebusError HpsrvConstruct(Hpsrv* self); +static EebusError HpsrvStart(Hpsrv* self, int32_t port, const char* role, TlsCertificateObject* tls_certificate); -EebusError HpsrvConstruct(Hpsrv* self) { +static EebusError AddLpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local); +static EebusError AddLpp(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local); +static EebusError AddMpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local); +static EebusError AddGcpMgcp(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local); + +static EebusError AddHeatPumpApplianceEntity( + Hpsrv* self, + DeviceLocalObject* device_local, + const uint32_t* entity_ids, + size_t entity_id_size +); +static EebusError +AddInverterEntity(Hpsrv* self, DeviceLocalObject* device_local, const uint32_t* entity_ids, size_t entity_id_size); +static EebusError AddGridConnectionPointEntity( + Hpsrv* self, + DeviceLocalObject* device_local, + const uint32_t* entity_ids, + size_t entity_id_size +); + +static EebusError SetMpcData(Hpsrv* self, const MpcData* mpc_data, size_t mpc_data_size); + +static EebusError HpsrvConstruct(Hpsrv* self) { // Override "virtual functions table" SERVICE_READER_INTERFACE(self) = &hpsrv_methods; @@ -104,6 +129,7 @@ EebusError HpsrvConstruct(Hpsrv* self) { self->cs_lpp_listener = NULL; self->cs_lpp = NULL; self->mu_mpc = NULL; + self->gcp_mgcp = NULL; self->cli = NULL; self->cli = EebusCliCreate(); @@ -114,7 +140,7 @@ EebusError HpsrvConstruct(Hpsrv* self) { return kEebusErrorOk; } -EebusError AddLpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { +static EebusError AddLpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { UNUSED(device_local); self->cs_lpc_listener = CsLpcListenerCreate(); @@ -133,7 +159,7 @@ EebusError AddLpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObjec return kEebusErrorOk; } -EebusError AddLpp(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { +static EebusError AddLpp(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { UNUSED(device_local); self->cs_lpp_listener = CsLppListenerCreate(); @@ -152,7 +178,7 @@ EebusError AddLpp(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObjec return kEebusErrorOk; } -EebusError AddMpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { +static EebusError AddMpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { UNUSED(device_local); static const MuMpcMeasurementConfig measurement_default_cfg = { @@ -211,7 +237,7 @@ EebusError AddMpc(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObjec return kEebusErrorOk; } -EebusError AddHeatPumpApplianceEntity( +static EebusError AddHeatPumpApplianceEntity( Hpsrv* self, DeviceLocalObject* device_local, const uint32_t* entity_ids, @@ -264,7 +290,99 @@ AddInverterEntity(Hpsrv* self, DeviceLocalObject* device_local, const uint32_t* return kEebusErrorOk; } -EebusError HpsrvStart(Hpsrv* hpsrv, int32_t port, const char* role, TlsCertificateObject* tls_certificate) { +static EebusError AddGcpMgcp(Hpsrv* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { + UNUSED(device_local); + + static const GcpMgcpMeasurementConfig measurement_default_cfg = { + .value_source = kMeasurementValueSourceTypeMeasuredValue, + }; + + static const GcpMgcpMonitorEnergyConfig energy_cfg = { + .energy_feed_in_cfg = &measurement_default_cfg, + .energy_consumed_cfg = &measurement_default_cfg, + }; + + static const GcpMgcpMonitorCurrentConfig current_cfg = { + .current_phase_a_cfg = &measurement_default_cfg, + .current_phase_b_cfg = &measurement_default_cfg, + .current_phase_c_cfg = &measurement_default_cfg, + }; + + static const GcpMgcpMonitorVoltageConfig voltage_cfg = { + .voltage_phase_a_cfg = &measurement_default_cfg, + .voltage_phase_b_cfg = &measurement_default_cfg, + .voltage_phase_c_cfg = &measurement_default_cfg, + .voltage_phase_ab_cfg = &measurement_default_cfg, + .voltage_phase_bc_cfg = &measurement_default_cfg, + .voltage_phase_ac_cfg = &measurement_default_cfg, + }; + + static const GcpMgcpMonitorFrequencyConfig frequency_cfg = { + .frequency_cfg = {.value_source = kMeasurementValueSourceTypeMeasuredValue}, + }; + + static const GcpMgcpPvCurtailmentConfig pv_curtailment_cfg = {0}; + + static const GcpMgcpConfig cfg = { + .pv_curtailment_cfg = &pv_curtailment_cfg, + .power_cfg = { + .phases = kElectricalConnectionPhaseNameTypeAbc, + .power_total_cfg = {.value_source = kMeasurementValueSourceTypeMeasuredValue}, + }, + .energy_cfg = &energy_cfg, + .current_cfg = ¤t_cfg, + .voltage_cfg = &voltage_cfg, + .frequency_cfg = &frequency_cfg, + }; + + self->gcp_mgcp = GcpMgcpUseCaseCreate(entity_local, kHpsrvElectricalConnectionId, &cfg); + if (self->gcp_mgcp == NULL) { + return kEebusErrorInit; + } + + const ScaledValue zero_power = {.value = 0, .scale = kScaleDefault}; + EebusError err = GcpMgcpSetMeasurementDataCache(self->gcp_mgcp, kGcpPowerTotal, &zero_power, NULL, NULL); + if (err != kEebusErrorOk) { + return err; + } + + err = GcpMgcpUpdate(self->gcp_mgcp); + if (err != kEebusErrorOk) { + return err; + } + + EEBUS_CLI_SET_GCP_MGCP(self->cli, self->gcp_mgcp); + return kEebusErrorOk; +} + +static EebusError AddGridConnectionPointEntity( + Hpsrv* self, + DeviceLocalObject* device_local, + const uint32_t* entity_ids, + size_t entity_id_size +) { + EntityLocalObject* const entity = EntityLocalCreate( + device_local, + kEntityTypeTypeGridConnectionPointOfPremises, + entity_ids, + entity_id_size, + kHeartbeatTimeoutSeconds + ); + + if (entity == NULL) { + return kEebusErrorMemoryAllocate; + } + + if (AddGcpMgcp(self, device_local, entity) != kEebusErrorOk) { + EntityLocalDelete(entity); + return kEebusErrorInit; + } + + DEVICE_LOCAL_ADD_ENTITY(device_local, entity); + return kEebusErrorOk; +} + +static EebusError HpsrvStart(Hpsrv* hpsrv, int32_t port, const char* role, TlsCertificateObject* tls_certificate) { if (tls_certificate == NULL) { return kEebusErrorInputArgument; } @@ -297,6 +415,11 @@ EebusError HpsrvStart(Hpsrv* hpsrv, int32_t port, const char* role, TlsCertifica return kEebusErrorOther; } + uint32_t gcp_entity_ids[1] = {VectorGetSize(DEVICE_LOCAL_GET_ENTITIES(device_local))}; + if (AddGridConnectionPointEntity(hpsrv, device_local, gcp_entity_ids, ARRAY_SIZE(gcp_entity_ids)) != kEebusErrorOk) { + return kEebusErrorOther; + } + EEBUS_SERVICE_START(hpsrv->service); return kEebusErrorOk; @@ -349,6 +472,9 @@ void Destruct(ServiceReaderObject* self) { CsLpcListenerDelete(hpsrv->cs_lpc_listener); hpsrv->cs_lpc_listener = NULL; + UseCaseDelete(USE_CASE_OBJECT(hpsrv->gcp_mgcp)); + hpsrv->gcp_mgcp = NULL; + EebusServiceConfigDelete(hpsrv->cfg); hpsrv->cfg = NULL; } @@ -402,7 +528,7 @@ void HpsrvUnregisterRemoteSki(HpsrvObject* self, const char* ski) { EEBUS_SERVICE_UNREGISTER_REMOTE_SKI(HPSRV(self)->service, ski); } -EebusError SetMpcData(Hpsrv* self, const MpcData* mpc_data, size_t mpc_data_size) { +static EebusError SetMpcData(Hpsrv* self, const MpcData* mpc_data, size_t mpc_data_size) { EebusError err = kEebusErrorOk; for (size_t i = 0; i < mpc_data_size; ++i) { @@ -533,6 +659,121 @@ EebusError HpsrvSetAcFrequency(HpsrvObject* self, int32_t ac_frequency) { return MuMpcUpdate(hpsrv->mu_mpc); } +EebusError HpsrvSetGcpMgcpPowerTotal(HpsrvObject* self, int32_t power_total) { + Hpsrv* const hpsrv = HPSRV(self); + + const ScaledValue value = {.value = power_total, .scale = kScaleDefault}; + EebusError err = GcpMgcpSetMeasurementDataCache(hpsrv->gcp_mgcp, kGcpPowerTotal, &value, NULL, NULL); + if (err != kEebusErrorOk) { + return err; + } + + return GcpMgcpUpdate(hpsrv->gcp_mgcp); +} + +EebusError HpsrvSetGcpMgcpEnergyFeedIn(HpsrvObject* self, int32_t energy_feed_in) { + Hpsrv* const hpsrv = HPSRV(self); + + const ScaledValue value = {.value = energy_feed_in, .scale = kScaleDefault}; + EebusError err = GcpMgcpSetEnergyFeedInCache(hpsrv->gcp_mgcp, &value, NULL, NULL, NULL, NULL); + if (err != kEebusErrorOk) { + return err; + } + + return GcpMgcpUpdate(hpsrv->gcp_mgcp); +} + +EebusError HpsrvSetGcpMgcpEnergyConsumed(HpsrvObject* self, int32_t energy_consumed) { + Hpsrv* const hpsrv = HPSRV(self); + + const ScaledValue value = {.value = energy_consumed, .scale = kScaleDefault}; + EebusError err = GcpMgcpSetEnergyConsumedCache(hpsrv->gcp_mgcp, &value, NULL, NULL, NULL, NULL); + if (err != kEebusErrorOk) { + return err; + } + + return GcpMgcpUpdate(hpsrv->gcp_mgcp); +} + +EebusError HpsrvSetGcpMgcpCurrentPerPhase( + HpsrvObject* self, + int32_t current_phase_a, + int32_t current_phase_b, + int32_t current_phase_c +) { + Hpsrv* const hpsrv = HPSRV(self); + + const GcpMeasurementNameId names[] = {kGcpCurrentPhaseA, kGcpCurrentPhaseB, kGcpCurrentPhaseC}; + const int32_t values[] = {current_phase_a, current_phase_b, current_phase_c}; + + for (size_t i = 0; i < ARRAY_SIZE(names); ++i) { + const ScaledValue sv = {.value = values[i], .scale = kScaleDefault}; + EebusError err = GcpMgcpSetMeasurementDataCache(hpsrv->gcp_mgcp, names[i], &sv, NULL, NULL); + if (err != kEebusErrorOk) { + return err; + } + } + + return GcpMgcpUpdate(hpsrv->gcp_mgcp); +} + +EebusError HpsrvSetGcpMgcpVoltagePerPhase( + HpsrvObject* self, + int32_t voltage_phase_a, + int32_t voltage_phase_b, + int32_t voltage_phase_c, + int32_t voltage_phase_ab, + int32_t voltage_phase_bc, + int32_t voltage_phase_ac +) { + Hpsrv* const hpsrv = HPSRV(self); + + const GcpMeasurementNameId names[] = { + kGcpVoltagePhaseA, + kGcpVoltagePhaseB, + kGcpVoltagePhaseC, + kGcpVoltagePhaseAb, + kGcpVoltagePhaseBc, + kGcpVoltagePhaseAc, + }; + const int32_t values[] = { + voltage_phase_a, + voltage_phase_b, + voltage_phase_c, + voltage_phase_ab, + voltage_phase_bc, + voltage_phase_ac, + }; + + for (size_t i = 0; i < ARRAY_SIZE(names); ++i) { + const ScaledValue sv = {.value = values[i], .scale = kScaleDefault}; + EebusError err = GcpMgcpSetMeasurementDataCache(hpsrv->gcp_mgcp, names[i], &sv, NULL, NULL); + if (err != kEebusErrorOk) { + return err; + } + } + + return GcpMgcpUpdate(hpsrv->gcp_mgcp); +} + +EebusError HpsrvSetGcpMgcpFrequency(HpsrvObject* self, int32_t frequency) { + Hpsrv* const hpsrv = HPSRV(self); + + const ScaledValue value = {.value = frequency, .scale = kScaleDefault}; + EebusError err = GcpMgcpSetMeasurementDataCache(hpsrv->gcp_mgcp, kGcpFrequency, &value, NULL, NULL); + if (err != kEebusErrorOk) { + return err; + } + + return GcpMgcpUpdate(hpsrv->gcp_mgcp); +} + +EebusError HpsrvSetGcpMgcpPvCurtailmentLimitFactor(HpsrvObject* self, const ScaledValue* value) { + Hpsrv* const hpsrv = HPSRV(self); + + return GcpMgcpSetPvCurtailmentLimitFactor(hpsrv->gcp_mgcp, value); +} + void HpsrvHandleCmd(HpsrvObject* self, char* cmd) { Hpsrv* const hpsrv = HPSRV(self); EEBUS_CLI_HANDLE_CMD(hpsrv->cli, cmd); diff --git a/examples/heat_pump/hpsrv.h b/examples/heat_pump/hpsrv.h index 214922c..456b981 100644 --- a/examples/heat_pump/hpsrv.h +++ b/examples/heat_pump/hpsrv.h @@ -27,6 +27,7 @@ #include "src/common/eebus_malloc.h" #include "src/service/api/service_reader_interface.h" #include "src/ship/api/tls_certificate_interface.h" +#include "src/use_case/model/scaled_value.h" typedef struct HpsrvObject HpsrvObject; @@ -148,6 +149,89 @@ EebusError HpsrvSetVoltagePerPhase( */ EebusError HpsrvSetAcFrequency(HpsrvObject* self, int32_t ac_frequency); +/** + * @brief Set the GCP MGCP power total. + * Passing e.g. 50000 will result in setting 500.00W + * @param self Pointer to the HpsrvObject instance + * @param power_total Total active power in W scaled by 10^(-2) + * @return kEebusErrorOk on success or error code on failure + */ +EebusError HpsrvSetGcpMgcpPowerTotal(HpsrvObject* self, int32_t power_total); + +/** + * @brief Set the GCP MGCP grid feed-in energy (Scenario 3). + * Passing e.g. 100000 will result in setting 1000.00Wh + * @param self Pointer to the HpsrvObject instance + * @param energy_feed_in Feed-in energy in Wh scaled by 10^(-2) + * @return kEebusErrorOk on success or error code on failure + */ +EebusError HpsrvSetGcpMgcpEnergyFeedIn(HpsrvObject* self, int32_t energy_feed_in); + +/** + * @brief Set the GCP MGCP grid consumed energy (Scenario 4). + * Passing e.g. 100000 will result in setting 1000.00Wh + * @param self Pointer to the HpsrvObject instance + * @param energy_consumed Consumed energy in Wh scaled by 10^(-2) + * @return kEebusErrorOk on success or error code on failure + */ +EebusError HpsrvSetGcpMgcpEnergyConsumed(HpsrvObject* self, int32_t energy_consumed); + +/** + * @brief Set the GCP MGCP AC current per phase (Scenario 5). + * Passing e.g. 200 will result in setting 2.00A + * @param self Pointer to the HpsrvObject instance + * @param current_phase_a Phase A RMS current in A scaled by 10^(-2) + * @param current_phase_b Phase B RMS current in A scaled by 10^(-2) + * @param current_phase_c Phase C RMS current in A scaled by 10^(-2) + * @return kEebusErrorOk on success or error code on failure + */ +EebusError HpsrvSetGcpMgcpCurrentPerPhase( + HpsrvObject* self, + int32_t current_phase_a, + int32_t current_phase_b, + int32_t current_phase_c +); + +/** + * @brief Set the GCP MGCP AC voltage per phase (Scenario 6). + * Passing e.g. 22000 will result in setting 220.00V + * @param self Pointer to the HpsrvObject instance + * @param voltage_phase_a A to Neutral voltage scaled by 10^(-2) + * @param voltage_phase_b B to Neutral voltage scaled by 10^(-2) + * @param voltage_phase_c C to Neutral voltage scaled by 10^(-2) + * @param voltage_phase_ab A to B voltage scaled by 10^(-2) + * @param voltage_phase_bc B to C voltage scaled by 10^(-2) + * @param voltage_phase_ac C to A voltage scaled by 10^(-2) + * @return kEebusErrorOk on success or error code on failure + */ +EebusError HpsrvSetGcpMgcpVoltagePerPhase( + HpsrvObject* self, + int32_t voltage_phase_a, + int32_t voltage_phase_b, + int32_t voltage_phase_c, + int32_t voltage_phase_ab, + int32_t voltage_phase_bc, + int32_t voltage_phase_ac +); + +/** + * @brief Set the GCP MGCP AC frequency (Scenario 7). + * Passing e.g. 5000 will result in setting 50.00Hz + * @param self Pointer to the HpsrvObject instance + * @param frequency AC grid frequency in Hz scaled by 10^(-2) + * @return kEebusErrorOk on success or error code on failure + */ +EebusError HpsrvSetGcpMgcpFrequency(HpsrvObject* self, int32_t frequency); + +/** + * @brief Set the GCP MGCP PV curtailment limit factor (Scenario 1). + * Passing e.g. a ScaledValue with value=75 scale=0 will result in setting 75% + * @param self Pointer to the HpsrvObject instance + * @param value PV curtailment limit factor in % as a ScaledValue + * @return kEebusErrorOk on success or error code on failure + */ +EebusError HpsrvSetGcpMgcpPvCurtailmentLimitFactor(HpsrvObject* self, const ScaledValue* value); + /** * @brief Handle command line input * @param self HEMS instance to handle the command for diff --git a/examples/hems/CMakeLists.txt b/examples/hems/CMakeLists.txt index 484ea17..09d2ab5 100644 --- a/examples/hems/CMakeLists.txt +++ b/examples/hems/CMakeLists.txt @@ -27,6 +27,7 @@ target_sources(${PROJECT_NAME} hems.c eg_lpc_listener.c eg_lpp_listener.c + ma_mgcp_listener.c ma_mpc_listener.c ) diff --git a/examples/hems/hems.c b/examples/hems/hems.c index 0ce498c..c0fc2c6 100644 --- a/examples/hems/hems.c +++ b/examples/hems/hems.c @@ -29,6 +29,7 @@ #include "examples/hems/eg_lpc_listener.h" #include "examples/hems/eg_lpp_listener.h" +#include "examples/hems/ma_mgcp_listener.h" #include "examples/hems/ma_mpc_listener.h" #include "src/cli/eebus_cli.h" #include "src/common/eebus_arguments.h" @@ -37,6 +38,7 @@ #include "src/spine/entity/entity_local.h" #include "src/use_case/actor/eg/lpc/eg_lpc.h" #include "src/use_case/actor/eg/lpp/eg_lpp.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp.h" #include "src/use_case/actor/ma/mpc/ma_mpc.h" /** EEBUS Home Energy Manager Service type definition */ @@ -55,6 +57,8 @@ struct Hems { EgLpListenerObject* eg_lpp_listener; MaMpcUseCaseObject* ma_mpc; MaMpcListenerObject* ma_mpc_listener; + MaMgcpUseCaseObject* ma_mgcp; + MaMgcpListenerObject* ma_mgcp_listener; EebusCliObject* cli; }; @@ -86,15 +90,17 @@ EebusError HemsConstruct(Hems* self) { // Override "virtual functions table" SERVICE_READER_INTERFACE(self) = &hpsrv_methods; - self->cfg = NULL; - self->service = NULL; - self->eg_lpc = NULL; - self->eg_lpc_listener = NULL; - self->eg_lpp = NULL; - self->eg_lpp_listener = NULL; - self->ma_mpc = NULL; - self->ma_mpc_listener = NULL; - self->cli = NULL; + self->cfg = NULL; + self->service = NULL; + self->eg_lpc = NULL; + self->eg_lpc_listener = NULL; + self->eg_lpp = NULL; + self->eg_lpp_listener = NULL; + self->ma_mpc = NULL; + self->ma_mpc_listener = NULL; + self->ma_mgcp = NULL; + self->ma_mgcp_listener = NULL; + self->cli = NULL; self->cli = EebusCliCreate(); if (self->cli == NULL) { @@ -158,6 +164,24 @@ EebusError AddMaMpc(Hems* self, DeviceLocalObject* device_local, EntityLocalObje return kEebusErrorOk; } +EebusError AddMaMgcp(Hems* self, DeviceLocalObject* device_local, EntityLocalObject* entity_local) { + UNUSED(device_local); + + self->ma_mgcp_listener = MaMgcpListenerCreate(HEMS_OBJECT(self)); + if (self->ma_mgcp_listener == NULL) { + return kEebusErrorMemoryAllocate; + } + + self->ma_mgcp = MaMgcpUseCaseCreate(entity_local, self->ma_mgcp_listener); + if (self->ma_mgcp == NULL) { + MaMgcpListenerDelete(self->ma_mgcp_listener); + self->ma_mgcp_listener = NULL; + return kEebusErrorInit; + } + + return kEebusErrorOk; +} + EebusError HemsStart(Hems* hems, int32_t port, const char* role, TlsCertificateObject* tls_certificate) { if (tls_certificate == NULL) { return kEebusErrorInputArgument; @@ -205,6 +229,12 @@ EebusError HemsStart(Hems* hems, int32_t port, const char* role, TlsCertificateO return err; } + err = AddMaMgcp(hems, device_local, entity); + if (err != kEebusErrorOk) { + EntityLocalDelete(entity); + return err; + } + DEVICE_LOCAL_ADD_ENTITY(device_local, entity); EEBUS_SERVICE_START(hems->service); @@ -243,6 +273,12 @@ void Destruct(ServiceReaderObject* self) { hems->service = NULL; } + UseCaseDelete(USE_CASE_OBJECT(hems->ma_mgcp)); + hems->ma_mgcp = NULL; + + MaMgcpListenerDelete(hems->ma_mgcp_listener); + hems->ma_mgcp_listener = NULL; + UseCaseDelete(USE_CASE_OBJECT(hems->ma_mpc)); hems->ma_mpc = NULL; @@ -261,12 +297,6 @@ void Destruct(ServiceReaderObject* self) { EgLpcListenerDelete(hems->eg_lpc_listener); hems->eg_lpc_listener = NULL; - MaMpcUseCaseDelete(hems->ma_mpc); - hems->ma_mpc = NULL; - - MaMpcListenerDelete(hems->ma_mpc_listener); - hems->ma_mpc_listener = NULL; - EebusServiceConfigDelete(hems->cfg); hems->cfg = NULL; } @@ -353,6 +383,17 @@ void HemsSetMaMpcRemoteEntity(HemsObject* self, const EntityAddressType* entity_ EEBUS_CLI_SET_MA_MPC(hems->cli, ma_mpc, entity_addr); } +void HemsSetMaMgcpRemoteEntity(HemsObject* self, const EntityAddressType* entity_addr) { + Hems* const hems = HEMS(self); + + if (hems->cli == NULL) { + return; + } + + MaMgcpUseCaseObject* const ma_mgcp = (entity_addr == NULL) ? NULL : hems->ma_mgcp; + EEBUS_CLI_SET_MA_MGCP(hems->cli, ma_mgcp, entity_addr); +} + void HemsHandleCmd(HemsObject* self, char* cmd) { Hems* const hems = HEMS(self); EEBUS_CLI_HANDLE_CMD(hems->cli, cmd); diff --git a/examples/hems/hems.h b/examples/hems/hems.h index 0520804..b49a244 100644 --- a/examples/hems/hems.h +++ b/examples/hems/hems.h @@ -89,6 +89,13 @@ void HemsSetEgLppRemoteEntity(HemsObject* self, const EntityAddressType* entity_ */ void HemsSetMaMpcRemoteEntity(HemsObject* self, const EntityAddressType* entity_addr); +/** + * @brief Set the MA MGCP remote entity address + * @param self HEMS instance to set the remote entity for + * @param entity_addr Pointer to the entity address (NULL = disconnected) + */ +void HemsSetMaMgcpRemoteEntity(HemsObject* self, const EntityAddressType* entity_addr); + /** * @brief Handle command line input * @param self HEMS instance to handle the command for diff --git a/examples/hems/ma_mgcp_listener.c b/examples/hems/ma_mgcp_listener.c new file mode 100644 index 0000000..c323984 --- /dev/null +++ b/examples/hems/ma_mgcp_listener.c @@ -0,0 +1,139 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Ma Mgcp Listener implementation + */ + +#include + +#include "examples/hems/hems.h" +#include "examples/hems/ma_mgcp_listener.h" +#include "src/common/eebus_arguments.h" +#include "src/common/eebus_malloc.h" +#include "src/use_case/api/ma_mgcp_listener_interface.h" +#include "src/use_case/model/mgcp_types.h" +#include "src/use_case/model/scaled_value.h" + +typedef struct MaMgcpListener MaMgcpListener; + +struct MaMgcpListener { + /** Implements the Ma Mgcp Listener Interface */ + MaMgcpListenerObject obj; + + /* Pointer to the HEMS instance */ + HemsObject* hems; +}; + +#define MA_MGCP_LISTENER(obj) ((MaMgcpListener*)(obj)) + +static void Destruct(MaMgcpListenerObject* self); +static void OnRemoteEntityConnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr); +static void OnRemoteEntityDisconnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr); +static void OnMeasurementReceive( + MaMgcpListenerObject* self, + GcpMeasurementNameId name_id, + const ScaledValue* measurement_value, + const EntityAddressType* remote_entity_addr +); +static void OnPvCurtailmentLimitFactorReceive( + MaMgcpListenerObject* self, + const ScaledValue* value, + const EntityAddressType* remote_entity_addr +); + +static const MaMgcpListenerInterface ma_mgcp_listener_methods = { + .destruct = Destruct, + .on_remote_entity_connect = OnRemoteEntityConnect, + .on_remote_entity_disconnect = OnRemoteEntityDisconnect, + .on_measurement_receive = OnMeasurementReceive, + .on_pv_curtailment_limit_factor_receive = OnPvCurtailmentLimitFactorReceive, +}; + +static EebusError MaMgcpListenerConstruct(MaMgcpListener* self, HemsObject* hems); + +EebusError MaMgcpListenerConstruct(MaMgcpListener* self, HemsObject* hems) { + // Override "virtual functions table" + MA_MGCP_LISTENER_INTERFACE(self) = &ma_mgcp_listener_methods; + + self->hems = hems; + + return kEebusErrorOk; +} + +MaMgcpListenerObject* MaMgcpListenerCreate(HemsObject* hems) { + MaMgcpListener* const ma_mgcp_listener = (MaMgcpListener*)EEBUS_MALLOC(sizeof(MaMgcpListener)); + if (ma_mgcp_listener == NULL) { + return NULL; + } + + if (MaMgcpListenerConstruct(ma_mgcp_listener, hems) != kEebusErrorOk) { + MaMgcpListenerDelete(MA_MGCP_LISTENER_OBJECT(ma_mgcp_listener)); + return NULL; + } + + return MA_MGCP_LISTENER_OBJECT(ma_mgcp_listener); +} + +void Destruct(MaMgcpListenerObject* self) { + UNUSED(self); + + // Nothing to be deallocated yet +} + +void OnRemoteEntityConnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr) { + MaMgcpListener* const ma_mgcp_listener = MA_MGCP_LISTENER(self); + + HemsSetMaMgcpRemoteEntity(ma_mgcp_listener->hems, entity_addr); +} + +void OnRemoteEntityDisconnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr) { + MaMgcpListener* const ma_mgcp_listener = MA_MGCP_LISTENER(self); + + HemsSetMaMgcpRemoteEntity(ma_mgcp_listener->hems, NULL); + UNUSED(entity_addr); +} + +void OnMeasurementReceive( + MaMgcpListenerObject* self, + GcpMeasurementNameId name_id, + const ScaledValue* measurement_value, + const EntityAddressType* remote_entity_addr +) { + UNUSED(self); + + const char* name = GcpMgcpMeasurementGetName(name_id); + if (name == NULL) { + printf("MA MGCP Measurement received: Unknown Measurement ID %d\n", (int)name_id); + return; + } + + printf("MA MGCP Measurement received: %s = ", name); + ScaledValuePrint("%s,", measurement_value); + EntityAddressPrint(" from entity: %s\n", remote_entity_addr); +} + +void OnPvCurtailmentLimitFactorReceive( + MaMgcpListenerObject* self, + const ScaledValue* value, + const EntityAddressType* remote_entity_addr +) { + UNUSED(self); + + printf("MA MGCP PV curtailment limit factor received: "); + ScaledValuePrint("%s,", value); + EntityAddressPrint(" from entity: %s\n", remote_entity_addr); +} diff --git a/examples/hems/ma_mgcp_listener.h b/examples/hems/ma_mgcp_listener.h new file mode 100644 index 0000000..aa7c560 --- /dev/null +++ b/examples/hems/ma_mgcp_listener.h @@ -0,0 +1,47 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Ma Mgcp Listener implementation declarations + */ + +#ifndef EXAMPLES_HEMS_MA_MGCP_LISTENER_H_ +#define EXAMPLES_HEMS_MA_MGCP_LISTENER_H_ + +#include + +#include "examples/hems/hems.h" +#include "src/common/eebus_malloc.h" +#include "src/use_case/api/ma_mgcp_listener_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +MaMgcpListenerObject* MaMgcpListenerCreate(HemsObject* hems); + +static inline void MaMgcpListenerDelete(MaMgcpListenerObject* ma_mgcp_listener) { + if (ma_mgcp_listener != NULL) { + MA_MGCP_LISTENER_DESTRUCT(ma_mgcp_listener); + EEBUS_FREE(ma_mgcp_listener); + } +} + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // EXAMPLES_HEMS_MA_MGCP_LISTENER_H_ diff --git a/sources.cmake b/sources.cmake index dc2b958..ef9a49a 100644 --- a/sources.cmake +++ b/sources.cmake @@ -7,6 +7,8 @@ set(SOURCES src/cli/eebus_cli_eg_lp.c src/cli/eebus_cli_ma_mpc.c src/cli/eebus_cli_mu_mpc.c + src/cli/eebus_cli_ma_mgcp.c + src/cli/eebus_cli_gcp_mgcp.c src/common/debug.c src/common/eebus_device_info.c src/common/eebus_math/eebus_math.c @@ -82,6 +84,10 @@ set(SOURCES src/spine/node_management/node_management_subscription.c src/spine/node_management/node_management_usecase.c src/spine/subscription/subscription_manager.c + src/use_case/actor/common/eebus_measurement_base.c + src/use_case/actor/common/eebus_monitor_base.c + src/use_case/actor/common/eebus_monitor_container.c + src/use_case/actor/common/eebus_monitor_features.c src/use_case/actor/common/load_control.c src/use_case/actor/cs/lpc/cs_lpc.c src/use_case/actor/cs/lpp/cs_lpp.c @@ -93,6 +99,8 @@ set(SOURCES src/use_case/actor/eg/eg_lp_events.c src/use_case/actor/eg/eg_lp_public.c src/use_case/actor/eg/eg_lp.c + src/use_case/actor/ma/ma_events.c + src/use_case/actor/ma/ma_measurement_base.c src/use_case/actor/ma/mpc/ma_mpc.c src/use_case/actor/ma/mpc/ma_mpc_events.c src/use_case/actor/ma/mpc/ma_mpc_measurement.c @@ -101,7 +109,16 @@ set(SOURCES src/use_case/actor/mu/mpc/mu_mpc_measurement.c src/use_case/actor/mu/mpc/mu_mpc_monitor.c src/use_case/actor/mu/mpc/mu_mpc_public.c + src/use_case/actor/ma/mgcp/ma_mgcp.c + src/use_case/actor/ma/mgcp/ma_mgcp_events.c + src/use_case/actor/ma/mgcp/ma_mgcp_measurement.c + src/use_case/actor/ma/mgcp/ma_mgcp_public.c + src/use_case/actor/gcp/mgcp/gcp_mgcp.c + src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.c + src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.c + src/use_case/actor/gcp/mgcp/gcp_mgcp_public.c src/use_case/model/load_limit_types.c + src/use_case/model/mgcp_types.c src/use_case/model/mpc_types.c src/use_case/model/scaled_value.c src/use_case/specialization/device_configuration/device_configuration_client.c @@ -135,6 +152,8 @@ set(HEADERS src/cli/eebus_cli_interface.h src/cli/eebus_cli_ma_mpc.h src/cli/eebus_cli_mu_mpc.h + src/cli/eebus_cli_ma_mgcp.h + src/cli/eebus_cli_gcp_mgcp.h src/common/api/eebus_mutex_interface.h src/common/api/eebus_queue_interface.h src/common/api/eebus_timer_interface.h @@ -336,7 +355,14 @@ set(HEADERS src/spine/node_management/node_management_internal.h src/spine/node_management/node_management_remote.h src/spine/subscription/subscription_manager.h + src/use_case/api/ma_measurement_interface.h src/use_case/api/ma_mpc_listener_interface.h + src/use_case/api/ma_mgcp_listener_interface.h + src/use_case/api/eebus_monitor_interface.h + src/use_case/actor/common/eebus_measurement_base.h + src/use_case/actor/common/eebus_monitor_base.h + src/use_case/actor/common/eebus_monitor_container.h + src/use_case/actor/common/eebus_monitor_features.h src/use_case/actor/common/load_control.h src/use_case/actor/cs/cs_lp_events.h src/use_case/actor/cs/cs_lp_internal.h @@ -348,6 +374,8 @@ set(HEADERS src/use_case/actor/eg/eg_lp_events.h src/use_case/actor/eg/eg_lp_internal.h src/use_case/actor/eg/eg_lp.h + src/use_case/actor/ma/ma_events.h + src/use_case/actor/ma/ma_measurement_base.h src/use_case/actor/ma/mpc/ma_mpc.h src/use_case/actor/ma/mpc/ma_mpc_events.h src/use_case/actor/ma/mpc/ma_mpc_internal.h @@ -356,6 +384,15 @@ set(HEADERS src/use_case/actor/mu/mpc/mu_mpc_internal.h src/use_case/actor/mu/mpc/mu_mpc_monitor.h src/use_case/actor/mu/mpc/mu_mpc_measurement.h + src/use_case/actor/ma/mgcp/ma_mgcp.h + src/use_case/actor/ma/mgcp/ma_mgcp_events.h + src/use_case/actor/ma/mgcp/ma_mgcp_internal.h + src/use_case/actor/ma/mgcp/ma_mgcp_measurement.h + src/use_case/actor/gcp/mgcp/gcp_mgcp.h + src/use_case/actor/gcp/mgcp/gcp_mgcp_internal.h + src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.h + src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h + src/use_case/model/mgcp_types.h src/use_case/model/mpc_types.h src/use_case/model/scaled_value.h src/use_case/model/load_limit_types.h diff --git a/src/cli/commands_examples.md b/src/cli/commands_examples.md new file mode 100644 index 0000000..2955b33 --- /dev/null +++ b/src/cli/commands_examples.md @@ -0,0 +1,42 @@ +# Commands examples + +## CS LPC +> cs_lpc set power_limit 3500.5 false true +> cs_lpc get power_limit +> cs_lpc set failsafe_limit 3500.5 true +> cs_lpc get failsafe_limit +> cs_lpc set failsafe_duration PT3H02M3S true +> cs_lpc get failsafe_duration +> cs_lpc start heartbeat +> cs_lpc stop heartbeat + +## MU MPC + +MU MPC commands format: + +> mu_mpc get +> mu_mpc set + +The list of available measurement names: +* power_total +* power_phase_a +* power_phase_b +* power_phase_c +* energy_consumed +* energy_produced +* current_phase_a +* current_phase_b +* current_phase_c +* voltage_phase_a +* voltage_phase_b +* voltage_phase_c +* voltage_phase_ab +* voltage_phase_bc +* voltage_phase_ac +* frequency + +For power_total use e.g.: + +> mu_mpc get power_total +> mu_mpc set power_total 1200.0 + diff --git a/src/cli/eebus_cli.c b/src/cli/eebus_cli.c index a3223f2..74140cd 100644 --- a/src/cli/eebus_cli.c +++ b/src/cli/eebus_cli.c @@ -23,6 +23,8 @@ #include "src/cli/eebus_cli.h" #include "src/cli/eebus_cli_cs_lp.h" #include "src/cli/eebus_cli_eg_lp.h" +#include "src/cli/eebus_cli_gcp_mgcp.h" +#include "src/cli/eebus_cli_ma_mgcp.h" #include "src/cli/eebus_cli_ma_mpc.h" #include "src/cli/eebus_cli_mu_mpc.h" #include "src/common/array_util.h" @@ -48,6 +50,10 @@ struct EebusCli { EebusCliHandlerObject* ma_mpc_cli; /** MU MPC CLI instance to deal with */ EebusCliHandlerObject* mu_mpc_cli; + /** GCP MGCP CLI instance to deal with */ + EebusCliHandlerObject* gcp_mgcp_cli; + /** MA MGCP CLI instance to deal with */ + EebusCliHandlerObject* ma_mgcp_cli; }; #define EEBUS_CLI(obj) ((EebusCli*)(obj)) @@ -62,17 +68,22 @@ static void SetEgLpp(EebusCliObject* self, EgLpUseCaseObject* eg_lpp_use_case, const EntityAddressType* remote_entity_address); static void SetMaMpc(EebusCliObject* self, MaMpcUseCaseObject* ma_mpc_use_case, const EntityAddressType* remote_entity_address); +static void SetGcpMgcp(EebusCliObject* self, GcpMgcpUseCaseObject* gcp_mgcp_use_case); +static void +SetMaMgcp(EebusCliObject* self, MaMgcpUseCaseObject* ma_mgcp_use_case, const EntityAddressType* remote_entity_address); static void HandleCmd(const EebusCliObject* self, char* cmd); static const EebusCliInterface eebus_cli_methods = { - .destruct = Destruct, - .set_cs_lpc = SetCsLpc, - .set_cs_lpp = SetCsLpp, - .set_eg_lpc = SetEgLpc, - .set_eg_lpp = SetEgLpp, - .set_mu_mpc = SetMuMpc, - .set_ma_mpc = SetMaMpc, - .handle_cmd = HandleCmd, + .destruct = Destruct, + .set_cs_lpc = SetCsLpc, + .set_cs_lpp = SetCsLpp, + .set_eg_lpc = SetEgLpc, + .set_eg_lpp = SetEgLpp, + .set_mu_mpc = SetMuMpc, + .set_ma_mpc = SetMaMpc, + .set_gcp_mgcp = SetGcpMgcp, + .set_ma_mgcp = SetMaMgcp, + .handle_cmd = HandleCmd, }; static EebusError EebusCliConstruct(EebusCli* self); @@ -81,12 +92,14 @@ EebusError EebusCliConstruct(EebusCli* self) { // Override "virtual functions table" EEBUS_CLI_INTERFACE(self) = &eebus_cli_methods; - self->cs_lpc_cli = NULL; - self->cs_lpp_cli = NULL; - self->eg_lpc_cli = NULL; - self->eg_lpp_cli = NULL; - self->mu_mpc_cli = NULL; - self->ma_mpc_cli = NULL; + self->cs_lpc_cli = NULL; + self->cs_lpp_cli = NULL; + self->eg_lpc_cli = NULL; + self->eg_lpp_cli = NULL; + self->mu_mpc_cli = NULL; + self->ma_mpc_cli = NULL; + self->gcp_mgcp_cli = NULL; + self->ma_mgcp_cli = NULL; return kEebusErrorOk; } @@ -108,6 +121,12 @@ EebusCliObject* EebusCliCreate(void) { void Destruct(EebusCliObject* self) { EebusCli* const eebus_cli = EEBUS_CLI(self); + MaMgcpCliDelete(eebus_cli->ma_mgcp_cli); + eebus_cli->ma_mgcp_cli = NULL; + + GcpMgcpCliDelete(eebus_cli->gcp_mgcp_cli); + eebus_cli->gcp_mgcp_cli = NULL; + MaMpcCliDelete(eebus_cli->ma_mpc_cli); eebus_cli->ma_mpc_cli = NULL; @@ -130,7 +149,9 @@ void Destruct(EebusCliObject* self) { void SetCsLpc(EebusCliObject* self, CsLpUseCaseObject* cs_lpc_use_case) { EebusCli* const eebus_cli = EEBUS_CLI(self); - // Release the previously created CLI instance and create a new one + // Free the existing CLI instance before creating a new one — this function may be + // called multiple times (e.g. on use case re-registration), so the old instance + // must be released first to prevent a memory leak. CsLpCliDelete(eebus_cli->cs_lpc_cli); eebus_cli->cs_lpc_cli = CsLpCliCreate(kEnergyDirectionTypeConsume, cs_lpc_use_case); } @@ -138,7 +159,9 @@ void SetCsLpc(EebusCliObject* self, CsLpUseCaseObject* cs_lpc_use_case) { void SetCsLpp(EebusCliObject* self, CsLpUseCaseObject* cs_lpp_use_case) { EebusCli* const eebus_cli = EEBUS_CLI(self); - // Release the previously created CLI instance and create a new one + // Free the existing CLI instance before creating a new one — this function may be + // called multiple times (e.g. on use case re-registration), so the old instance + // must be released first to prevent a memory leak. CsLpCliDelete(eebus_cli->cs_lpp_cli); eebus_cli->cs_lpp_cli = CsLpCliCreate(kEnergyDirectionTypeProduce, cs_lpp_use_case); } @@ -150,7 +173,10 @@ void SetEgLpc( ) { EebusCli* const eebus_cli = EEBUS_CLI(self); - // Release the previously created CLI instance + // Always tear down the existing CLI instance — this function may be called + // multiple times (e.g. on reconnection), so the old instance must be released + // first to prevent a memory leak. A NULL remote address signals disconnection, + // in which case no new instance will be created below. EgLpCliDelete(eebus_cli->eg_lpc_cli); eebus_cli->eg_lpc_cli = NULL; @@ -163,7 +189,9 @@ void SetEgLpc( static void SetMuMpc(EebusCliObject* self, MuMpcUseCaseObject* mu_mpc_use_case) { EebusCli* const eebus_cli = EEBUS_CLI(self); - // Release the previously created CLI instance and create a new one + // Free the existing CLI instance before creating a new one — this function may be + // called multiple times (e.g. on use case re-registration), so the old instance + // must be released first to prevent a memory leak. MuMpcCliDelete(eebus_cli->mu_mpc_cli); eebus_cli->mu_mpc_cli = MuMpcCliCreate(mu_mpc_use_case); } @@ -175,7 +203,10 @@ void SetEgLpp( ) { EebusCli* const eebus_cli = EEBUS_CLI(self); - // Release the previously created CLI instance + // Always tear down the existing CLI instance — this function may be called + // multiple times (e.g. on reconnection), so the old instance must be released + // first to prevent a memory leak. A NULL remote address signals disconnection, + // in which case no new instance will be created below. EgLpCliDelete(eebus_cli->eg_lpp_cli); eebus_cli->eg_lpp_cli = NULL; @@ -192,7 +223,10 @@ void SetMaMpc( ) { EebusCli* const eebus_cli = EEBUS_CLI(self); - // Release the previously created CLI instance + // Always tear down the existing CLI instance — this function may be called + // multiple times (e.g. on reconnection), so the old instance must be released + // first to prevent a memory leak. A NULL remote address signals disconnection, + // in which case no new instance will be created below. MaMpcCliDelete(eebus_cli->ma_mpc_cli); eebus_cli->ma_mpc_cli = NULL; @@ -202,6 +236,33 @@ void SetMaMpc( } } +static void SetGcpMgcp(EebusCliObject* self, GcpMgcpUseCaseObject* gcp_mgcp_use_case) { + EebusCli* const eebus_cli = EEBUS_CLI(self); + + // Free the existing CLI instance before creating a new one — this function may be + // called multiple times (e.g. on use case re-registration), so the old instance + // must be released first to prevent a memory leak. + GcpMgcpCliDelete(eebus_cli->gcp_mgcp_cli); + eebus_cli->gcp_mgcp_cli = GcpMgcpCliCreate(gcp_mgcp_use_case); +} + +static void +SetMaMgcp(EebusCliObject* self, MaMgcpUseCaseObject* ma_mgcp_use_case, const EntityAddressType* remote_entity_address) { + EebusCli* const eebus_cli = EEBUS_CLI(self); + + // Always tear down the existing CLI instance — this function may be called + // multiple times (e.g. on reconnection), so the old instance must be released + // first to prevent a memory leak. A NULL remote address signals disconnection, + // in which case no new instance will be created below. + MaMgcpCliDelete(eebus_cli->ma_mgcp_cli); + eebus_cli->ma_mgcp_cli = NULL; + + // Create a new CLI instance if remote entity address is not NULL + if (remote_entity_address != NULL) { + eebus_cli->ma_mgcp_cli = MaMgcpCliCreate(ma_mgcp_use_case, remote_entity_address); + } +} + void HandleCmd(const EebusCliObject* self, char* cmd) { const EebusCli* const eebus_cli = EEBUS_CLI(self); @@ -237,6 +298,10 @@ void HandleCmd(const EebusCliObject* self, char* cmd) { handler = eebus_cli->mu_mpc_cli; } else if (strcmp(tokens[0], "ma_mpc") == 0) { handler = eebus_cli->ma_mpc_cli; + } else if (strcmp(tokens[0], "gcp_mgcp") == 0) { + handler = eebus_cli->gcp_mgcp_cli; + } else if (strcmp(tokens[0], "ma_mgcp") == 0) { + handler = eebus_cli->ma_mgcp_cli; } else { printf("Unknown command: %s\n", tokens[0]); return; diff --git a/src/cli/eebus_cli.h b/src/cli/eebus_cli.h index dd72753..ea61d0e 100644 --- a/src/cli/eebus_cli.h +++ b/src/cli/eebus_cli.h @@ -98,6 +98,49 @@ * // ma_mpc get * // For power_total use e.g.: * ma_mpc get power_total + * + * GCP MGCP: + * // GCP MGCP commands format: + * // gcp_mgcp get + * // gcp_mgcp set + * // The list of available measurement names: + * // power_total + * // energy_feed_in + * // energy_consumed + * // current_phase_a + * // current_phase_b + * // current_phase_c + * // voltage_phase_a + * // voltage_phase_b + * // voltage_phase_c + * // voltage_phase_ab + * // voltage_phase_bc + * // voltage_phase_ac + * // frequency + * // For power_total use e.g.: + * gcp_mgcp get power_total + * gcp_mgcp set power_total 1200.0 + * + * MA MGCP: + * // MA MGCP commands format: + * // ma_mgcp get + * // The list of available measurement names: + * // power_total + * // energy_feed_in + * // energy_consumed + * // current_phase_a + * // current_phase_b + * // current_phase_c + * // voltage_phase_a + * // voltage_phase_b + * // voltage_phase_c + * // voltage_phase_ab + * // voltage_phase_bc + * // voltage_phase_ac + * // frequency + * // For power_total use e.g.: + * ma_mgcp get power_total + * */ #ifndef SRC_CLI_EEBUS_CLI_H_ diff --git a/src/cli/eebus_cli_gcp_mgcp.c b/src/cli/eebus_cli_gcp_mgcp.c new file mode 100644 index 0000000..b881da0 --- /dev/null +++ b/src/cli/eebus_cli_gcp_mgcp.c @@ -0,0 +1,194 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief EEBUS CLI GCP MGCP commands handling implementation + */ + +#include +#include + +#include "src/cli/eebus_cli_gcp_mgcp.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp.h" +#include "src/use_case/model/mgcp_types.h" +#include "src/use_case/model/scaled_value.h" + +typedef struct GcpMgcpCli GcpMgcpCli; + +struct GcpMgcpCli { + /** Implements the Eebus Cli Handler Interface */ + EebusCliHandlerObject obj; + + /** GCP MGCP instance to deal with */ + GcpMgcpUseCaseObject* gcp_mgcp; +}; + +#define GCP_MGCP_CLI(obj) ((GcpMgcpCli*)(obj)) + +static void Destruct(EebusCliHandlerObject* self); +static void HandleCmd(const EebusCliHandlerObject* self, const char* const* tokens, size_t num_tokens); + +static const EebusCliHandlerInterface gcp_mgcp_cli_methods = { + .destruct = Destruct, + .handle_cmd = HandleCmd, +}; + +static EebusError GcpMgcpCliConstruct(GcpMgcpCli* self, GcpMgcpUseCaseObject* gcp_mgcp); + +static void HandleCmdSet(const GcpMgcpCli* self, const char* const* tokens, size_t num_tokens); +static void HandleCmdGet(const GcpMgcpCli* self, const char* const* tokens, size_t num_tokens); + +static EebusError GcpMgcpCliConstruct(GcpMgcpCli* self, GcpMgcpUseCaseObject* gcp_mgcp) { + EEBUS_CLI_HANDLER_INTERFACE(self) = &gcp_mgcp_cli_methods; + + self->gcp_mgcp = gcp_mgcp; + if (gcp_mgcp == NULL) { + return kEebusErrorInputArgumentNull; + } + + return kEebusErrorOk; +} + +EebusCliHandlerObject* GcpMgcpCliCreate(GcpMgcpUseCaseObject* gcp_mgcp) { + GcpMgcpCli* const gcp_mgcp_cli = (GcpMgcpCli*)EEBUS_MALLOC(sizeof(GcpMgcpCli)); + if (gcp_mgcp_cli == NULL) { + return NULL; + } + + if (GcpMgcpCliConstruct(gcp_mgcp_cli, gcp_mgcp) != kEebusErrorOk) { + GcpMgcpCliDelete(EEBUS_CLI_HANDLER_OBJECT(gcp_mgcp_cli)); + return NULL; + } + + return EEBUS_CLI_HANDLER_OBJECT(gcp_mgcp_cli); +} + +static void Destruct(EebusCliHandlerObject* self) { + // Nothing to deallocate + (void)self; +} + +//-------------------------------------------------------------------------------------------// +// +// GCP MGCP Setters Handling +// +//-------------------------------------------------------------------------------------------// +static void HandleCmdSet(const GcpMgcpCli* self, const char* const* tokens, size_t num_tokens) { + if (num_tokens != 4) { + printf("Insufficient arguments for gcp_mgcp set command\n"); + return; + } + + const char* const name = tokens[2]; + + if (strcmp(name, "pv_curtailment_limit_factor") == 0) { + ScaledValue value = {0}; + if (ScaledValueParse(tokens[3], &value) != kEebusErrorOk) { + printf("Parsing GCP MGCP pv_curtailment_limit_factor value failed\n"); + return; + } + + if (GcpMgcpSetPvCurtailmentLimitFactor(self->gcp_mgcp, &value) != kEebusErrorOk) { + printf("Setting GCP MGCP pv_curtailment_limit_factor failed\n"); + return; + } + + printf("Setting GCP MGCP pv_curtailment_limit_factor succeeded\n"); + return; + } + + const GcpMeasurementNameId* const name_id = GcpMgcpMeasurementGetNameId(name); + if (name_id == NULL) { + printf("Unknown measurement name for gcp_mgcp set: %s\n", name); + return; + } + + ScaledValue value = {0}; + if (ScaledValueParse(tokens[3], &value) != kEebusErrorOk) { + printf("Parsing GCP MGCP measurement value failed\n"); + return; + } + + if (GcpMgcpSetMeasurementDataCache(self->gcp_mgcp, *name_id, &value, NULL, NULL) != kEebusErrorOk) { + printf("Setting GCP MGCP measurement cache failed\n"); + return; + } + + if (GcpMgcpUpdate(self->gcp_mgcp) != kEebusErrorOk) { + printf("Updating GCP MGCP failed\n"); + return; + } + + printf("Setting GCP MGCP measurement data succeeded\n"); +} + +//-------------------------------------------------------------------------------------------// +// +// GCP MGCP Getters Handling +// +//-------------------------------------------------------------------------------------------// +static void HandleCmdGet(const GcpMgcpCli* self, const char* const* tokens, size_t num_tokens) { + if (num_tokens != 3) { + printf("Insufficient arguments for gcp_mgcp get command\n"); + return; + } + + const char* const name = tokens[2]; + + if (strcmp(name, "pv_curtailment_limit_factor") == 0) { + ScaledValue value = {0}; + if (GcpMgcpGetPvCurtailmentLimitFactor(self->gcp_mgcp, &value) != kEebusErrorOk) { + printf("Getting GCP MGCP pv_curtailment_limit_factor failed\n"); + return; + } + + printf("GCP MGCP pv_curtailment_limit_factor: "); + ScaledValuePrint("value=%s\n", &value); + return; + } + + const GcpMeasurementNameId* const name_id = GcpMgcpMeasurementGetNameId(name); + if (name_id == NULL) { + printf("Unknown measurement name for gcp_mgcp get: %s\n", name); + return; + } + + ScaledValue value = {0}; + if (GcpMgcpGetMeasurementData(self->gcp_mgcp, *name_id, &value) != kEebusErrorOk) { + printf("Getting GCP MGCP measurement value failed\n"); + return; + } + + printf("GCP MGCP measurement %s: ", name); + ScaledValuePrint("value=%s\n", &value); +} + +static void HandleCmd(const EebusCliHandlerObject* self, const char* const* tokens, size_t num_tokens) { + const GcpMgcpCli* const gcp_mgcp_cli = GCP_MGCP_CLI(self); + + if (num_tokens < 2) { + printf("Insufficient arguments for gcp_mgcp command\n"); + return; + } + + if (strcmp(tokens[1], "set") == 0) { + HandleCmdSet(gcp_mgcp_cli, tokens, num_tokens); + } else if (strcmp(tokens[1], "get") == 0) { + HandleCmdGet(gcp_mgcp_cli, tokens, num_tokens); + } else { + printf("Unknown subcommand for gcp_mgcp: %s\n", tokens[1]); + } +} diff --git a/src/cli/eebus_cli_gcp_mgcp.h b/src/cli/eebus_cli_gcp_mgcp.h new file mode 100644 index 0000000..d66546c --- /dev/null +++ b/src/cli/eebus_cli_gcp_mgcp.h @@ -0,0 +1,47 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief EEBUS CLI GCP MGCP commands handling + */ + +#ifndef SRC_CLI_EEBUS_CLI_GCP_MGCP_H_ +#define SRC_CLI_EEBUS_CLI_GCP_MGCP_H_ + +#include + +#include "src/cli/eebus_cli_handler_interface.h" +#include "src/common/eebus_malloc.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +EebusCliHandlerObject* GcpMgcpCliCreate(GcpMgcpUseCaseObject* gcp_mgcp); + +static inline void GcpMgcpCliDelete(EebusCliHandlerObject* gcp_mgcp_cli) { + if (gcp_mgcp_cli != NULL) { + EEBUS_CLI_HANDLER_DESTRUCT(gcp_mgcp_cli); + EEBUS_FREE(gcp_mgcp_cli); + } +} + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_CLI_EEBUS_CLI_GCP_MGCP_H_ diff --git a/src/cli/eebus_cli_interface.h b/src/cli/eebus_cli_interface.h index f6e0407..6576cc2 100644 --- a/src/cli/eebus_cli_interface.h +++ b/src/cli/eebus_cli_interface.h @@ -25,6 +25,8 @@ #include "src/spine/model/entity_types.h" #include "src/use_case/actor/cs/cs_lp.h" #include "src/use_case/actor/eg/eg_lp.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp.h" #include "src/use_case/actor/ma/mpc/ma_mpc.h" #include "src/use_case/actor/mu/mpc/mu_mpc.h" @@ -105,6 +107,23 @@ struct EebusCliInterface { MaMpcUseCaseObject* ma_mpc_use_case, const EntityAddressType* remote_entity_address ); + /** + * @brief Set the GCP MGCP use case instance to be used by the CLI handler + * @param self Pointer to the EEBUS CLI handler instance + * @param gcp_mgcp_use_case GCP MGCP use case instance to be used by the CLI handler + */ + void (*set_gcp_mgcp)(EebusCliObject* self, GcpMgcpUseCaseObject* gcp_mgcp_use_case); + /** + * @brief Set the MA MGCP use case instance to be used by the CLI handler + * @param self Pointer to the EEBUS CLI handler instance + * @param ma_mgcp_use_case MA MGCP use case instance to be used by the CLI handler + * @param remote_entity_address MA MGCP remote entity address to be used by the CLI handler + */ + void (*set_ma_mgcp)( + EebusCliObject* self, + MaMgcpUseCaseObject* ma_mgcp_use_case, + const EntityAddressType* remote_entity_address + ); /** * @brief Handle the command passed as a string * @param self Pointer to the EEBUS CLI handler instance @@ -169,6 +188,17 @@ struct EebusCliObject { #define EEBUS_CLI_SET_MA_MPC(obj, ma_mpc_use_case, remote_entity_address) \ (EEBUS_CLI_INTERFACE(obj)->set_ma_mpc(obj, ma_mpc_use_case, remote_entity_address)) +/** + * @brief EEBUS CLI Set GCP MGCP caller definition + */ +#define EEBUS_CLI_SET_GCP_MGCP(obj, gcp_mgcp_use_case) (EEBUS_CLI_INTERFACE(obj)->set_gcp_mgcp(obj, gcp_mgcp_use_case)) + +/** + * @brief EEBUS CLI Set MA MGCP caller definition + */ +#define EEBUS_CLI_SET_MA_MGCP(obj, ma_mgcp_use_case, remote_entity_address) \ + (EEBUS_CLI_INTERFACE(obj)->set_ma_mgcp(obj, ma_mgcp_use_case, remote_entity_address)) + /** * @brief EEBUS CLI Handle Cmd caller definition */ diff --git a/src/cli/eebus_cli_ma_mgcp.c b/src/cli/eebus_cli_ma_mgcp.c new file mode 100644 index 0000000..41cb530 --- /dev/null +++ b/src/cli/eebus_cli_ma_mgcp.c @@ -0,0 +1,149 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief EEBUS CLI MA MGCP commands handling implementation + */ + +#include +#include + +#include "src/cli/eebus_cli_ma_mgcp.h" +#include "src/use_case/model/mgcp_types.h" +#include "src/use_case/model/scaled_value.h" + +typedef struct MaMgcpCli MaMgcpCli; + +struct MaMgcpCli { + /** Implements the Eebus Cli Handler Interface */ + EebusCliHandlerObject obj; + + /** MA MGCP instance to deal with */ + MaMgcpUseCaseObject* ma_mgcp; + /** MA MGCP remote entity address to communicate with */ + const EntityAddressType* entity_addr; +}; + +#define MA_MGCP_CLI(obj) ((MaMgcpCli*)(obj)) + +static void Destruct(EebusCliHandlerObject* self); +static void HandleCmd(const EebusCliHandlerObject* self, const char* const* tokens, size_t num_tokens); + +static const EebusCliHandlerInterface ma_mgcp_cli_methods = { + .destruct = Destruct, + .handle_cmd = HandleCmd, +}; + +static EebusError +MaMgcpCliConstruct(MaMgcpCli* self, MaMgcpUseCaseObject* ma_mgcp, const EntityAddressType* entity_addr); + +static void HandleCmdGet(const MaMgcpCli* self, const char* const* tokens, size_t num_tokens); + +static EebusError +MaMgcpCliConstruct(MaMgcpCli* self, MaMgcpUseCaseObject* ma_mgcp, const EntityAddressType* entity_addr) { + EEBUS_CLI_HANDLER_INTERFACE(self) = &ma_mgcp_cli_methods; + + self->ma_mgcp = ma_mgcp; + self->entity_addr = NULL; + + if ((ma_mgcp == NULL) || (entity_addr == NULL)) { + return kEebusErrorInputArgumentNull; + } + + self->entity_addr = EntityAddressCopy(entity_addr); + if (self->entity_addr == NULL) { + return kEebusErrorMemoryAllocate; + } + + return kEebusErrorOk; +} + +EebusCliHandlerObject* MaMgcpCliCreate(MaMgcpUseCaseObject* ma_mgcp, const EntityAddressType* entity_addr) { + MaMgcpCli* const ma_mgcp_cli = (MaMgcpCli*)EEBUS_MALLOC(sizeof(MaMgcpCli)); + if (ma_mgcp_cli == NULL) { + return NULL; + } + + if (MaMgcpCliConstruct(ma_mgcp_cli, ma_mgcp, entity_addr) != kEebusErrorOk) { + MaMgcpCliDelete(EEBUS_CLI_HANDLER_OBJECT(ma_mgcp_cli)); + return NULL; + } + + return EEBUS_CLI_HANDLER_OBJECT(ma_mgcp_cli); +} + +static void Destruct(EebusCliHandlerObject* self) { + MaMgcpCli* const ma_mgcp_cli = MA_MGCP_CLI(self); + + EntityAddressDelete((EntityAddressType*)ma_mgcp_cli->entity_addr); + ma_mgcp_cli->entity_addr = NULL; +} + +//-------------------------------------------------------------------------------------------// +// +// MA MGCP Getters Handling +// +//-------------------------------------------------------------------------------------------// +static void HandleCmdGet(const MaMgcpCli* self, const char* const* tokens, size_t num_tokens) { + if (num_tokens != 3) { + printf("Insufficient arguments for ma_mgcp get command\n"); + return; + } + + const char* const name = tokens[2]; + + if (strcmp(name, "pv_curtailment_limit_factor") == 0) { + ScaledValue value = {0}; + if (MaMgcpGetPvCurtailmentLimitFactor(self->ma_mgcp, self->entity_addr, &value) != kEebusErrorOk) { + printf("Getting MA MGCP pv_curtailment_limit_factor failed\n"); + return; + } + + printf("MA MGCP pv_curtailment_limit_factor: "); + ScaledValuePrint("value=%s\n", &value); + return; + } + + const GcpMeasurementNameId* const name_id = GcpMgcpMeasurementGetNameId(name); + if (name_id == NULL) { + printf("Unknown measurement name for ma_mgcp get: %s\n", name); + return; + } + + ScaledValue value = {0}; + if (MaMgcpGetMeasurementData(self->ma_mgcp, *name_id, self->entity_addr, &value) != kEebusErrorOk) { + printf("Getting MA MGCP measurement value failed\n"); + return; + } + + printf("MA MGCP measurement %s: ", name); + ScaledValuePrint("value=%s\n", &value); +} + +static void HandleCmd(const EebusCliHandlerObject* self, const char* const* tokens, size_t num_tokens) { + const MaMgcpCli* const ma_mgcp_cli = MA_MGCP_CLI(self); + + if (num_tokens < 2) { + printf("Insufficient arguments for ma_mgcp command\n"); + return; + } + + if (strcmp(tokens[1], "get") == 0) { + HandleCmdGet(ma_mgcp_cli, tokens, num_tokens); + } else { + printf("Unknown subcommand for ma_mgcp: %s\n", tokens[1]); + } +} diff --git a/src/cli/eebus_cli_ma_mgcp.h b/src/cli/eebus_cli_ma_mgcp.h new file mode 100644 index 0000000..8e7f72a --- /dev/null +++ b/src/cli/eebus_cli_ma_mgcp.h @@ -0,0 +1,49 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief EEBUS CLI MA MGCP commands handling + */ + +#ifndef SRC_CLI_EEBUS_CLI_MA_MGCP_H_ +#define SRC_CLI_EEBUS_CLI_MA_MGCP_H_ + +#include + +#include "src/cli/eebus_cli_handler_interface.h" +#include "src/common/eebus_malloc.h" +#include "src/spine/model/common_data_types.h" +#include "src/spine/model/entity_types.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +EebusCliHandlerObject* MaMgcpCliCreate(MaMgcpUseCaseObject* ma_mgcp, const EntityAddressType* entity_addr); + +static inline void MaMgcpCliDelete(EebusCliHandlerObject* ma_mgcp_cli) { + if (ma_mgcp_cli != NULL) { + EEBUS_CLI_HANDLER_DESTRUCT(ma_mgcp_cli); + EEBUS_FREE(ma_mgcp_cli); + } +} + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_CLI_EEBUS_CLI_MA_MGCP_H_ diff --git a/src/use_case/actor/mu/mpc/mu_mpc_measurement.c.tmp b/src/use_case/actor/common/eebus_measurement.c.tmp similarity index 90% rename from src/use_case/actor/mu/mpc/mu_mpc_measurement.c.tmp rename to src/use_case/actor/common/eebus_measurement.c.tmp index 9be0d8b..443724c 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc_measurement.c.tmp +++ b/src/use_case/actor/common/eebus_measurement.c.tmp @@ -1,6 +1,6 @@ -MuMpcMeasurement +EebusMeasurement void Destruct() -MeasurementNameId GetName() const +EebusMeasurementNameId GetName() const EebusError GetDataValue(MeasurementServer* msrv, ScaledValue* measurement_value) const const MeasurementConstraintsDataType* GetConstraints() const EebusError Configure(MeasurementServer* msrv, ElectricalConnectionServer* ecsrv, ElectricalConnectionIdType electrical_connection_id) diff --git a/src/use_case/actor/common/eebus_measurement_base.c b/src/use_case/actor/common/eebus_measurement_base.c new file mode 100644 index 0000000..b675b97 --- /dev/null +++ b/src/use_case/actor/common/eebus_measurement_base.c @@ -0,0 +1,419 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared measurement base implementation for MU MPC and GCP MGCP use cases + */ + +#include "src/use_case/actor/common/eebus_measurement_base.h" + +#include "src/common/eebus_malloc.h" +#include "src/spine/model/absolute_or_relative_time.h" +#include "src/spine/model/measurement_types.h" + +struct EebusMeasurementBase { + EebusMeasurementObject obj; + + EebusMeasurementNameId name; + MeasurementIdType id; + ScopeTypeType scope; + ElectricalConnectionPhaseNameType phases; + MeasurementValueSourceType value_source; + MeasurementConstraintsDataType* constraints; + EebusMeasurementConfigureStrategy cfg_strategy; + MeasurementDataType* measurement_data; +}; + +#define BASE(obj) ((EebusMeasurementBase*)(obj)) + +static void Destruct(EebusMeasurementObject* self); +static EebusMeasurementNameId GetName(const EebusMeasurementObject* self); +static EebusError GetDataValue(const EebusMeasurementObject* self, MeasurementServer* msrv, ScaledValue* value); +static const MeasurementConstraintsDataType* GetConstraints(const EebusMeasurementObject* self); +static EebusError Configure( + EebusMeasurementObject* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +); +static EebusError SetDataCache( + EebusMeasurementObject* self, + const ScaledValue* value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +); +static MeasurementDataType* ReleaseDataCache(EebusMeasurementObject* self); + +static const EebusMeasurementInterface measurement_base_methods = { + .destruct = Destruct, + .get_name = GetName, + .get_data_value = GetDataValue, + .get_constraints = GetConstraints, + .configure = Configure, + .set_data_cache = SetDataCache, + .release_data_cache = ReleaseDataCache, +}; + +static EebusError Construct( + EebusMeasurementBase* self, + EebusMeasurementNameId name, + ScopeTypeType scope, + ElectricalConnectionPhaseNameType phases, + const EebusMeasurementBaseConfig* cfg, + EebusMeasurementConfigureStrategy strategy +); +static ElectricalConnectionPhaseNameType VoltagePhaseFrom(ElectricalConnectionPhaseNameType phases); +static ElectricalConnectionPhaseNameType VoltagePhaseTo(ElectricalConnectionPhaseNameType phases); + +static EebusError Construct( + EebusMeasurementBase* self, + EebusMeasurementNameId name, + ScopeTypeType scope, + ElectricalConnectionPhaseNameType phases, + const EebusMeasurementBaseConfig* cfg, + EebusMeasurementConfigureStrategy strategy +) { + EEBUS_MEASUREMENT_INTERFACE(self) = &measurement_base_methods; + + self->name = name; + self->id = 0; + self->scope = scope; + self->phases = phases; + self->value_source = 0; + self->constraints = NULL; + self->cfg_strategy = strategy; + self->measurement_data = NULL; + + if (cfg == NULL) { + return kEebusErrorInputArgumentNull; + } + + self->value_source = cfg->value_source; + + if (cfg->constraints != NULL) { + self->constraints = MeasurementConstraintsDataCopy(cfg->constraints); + if (self->constraints == NULL) { + return kEebusErrorMemoryAllocate; + } + } + + return kEebusErrorOk; +} + +EebusMeasurementObject* EebusMeasurementBaseCreate( + EebusMeasurementNameId name, + ScopeTypeType scope, + ElectricalConnectionPhaseNameType phases, + const EebusMeasurementBaseConfig* cfg, + EebusMeasurementConfigureStrategy strategy +) { + EebusMeasurementBase* const m = (EebusMeasurementBase*)EEBUS_MALLOC(sizeof(EebusMeasurementBase)); + if (m == NULL) { + return NULL; + } + + if (Construct(m, name, scope, phases, cfg, strategy) != kEebusErrorOk) { + EEBUS_MEASUREMENT_DESTRUCT(EEBUS_MEASUREMENT_OBJECT(m)); + EEBUS_FREE(m); + return NULL; + } + + return EEBUS_MEASUREMENT_OBJECT(m); +} + +static void Destruct(EebusMeasurementObject* self) { + EebusMeasurementBase* const m = BASE(self); + MeasurementConstraintsDataDelete(m->constraints); + m->constraints = NULL; + MeasurementDataDelete(m->measurement_data); + m->measurement_data = NULL; +} + +static EebusMeasurementNameId GetName(const EebusMeasurementObject* self) { + return BASE(self)->name; +} + +static EebusError GetDataValue(const EebusMeasurementObject* self, MeasurementServer* msrv, ScaledValue* value) { + const EebusMeasurementBase* const m = BASE(self); + if ((msrv == NULL) || (value == NULL)) { + return kEebusErrorInputArgumentNull; + } + + const MeasurementDataType* const data = MeasurementCommonGetMeasurementWithId(&msrv->measurement_common, m->id); + if (data == NULL) { + return kEebusErrorNoChange; + } + + return ScaledValueInitWithScaledNumber(value, data->value); +} + +static const MeasurementConstraintsDataType* GetConstraints(const EebusMeasurementObject* self) { + return BASE(self)->constraints; +} + +static EebusError Configure( + EebusMeasurementObject* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +) { + EebusMeasurementBase* const m = BASE(self); + if (m->cfg_strategy == NULL) { + return kEebusErrorInit; + } + + EebusError err = m->cfg_strategy(m, msrv, ecsrv, ec_id); + if (err != kEebusErrorOk) { + return err; + } + + if (m->constraints != NULL) { + err = MeasurementConstraintsSetId(m->constraints, m->id); + } + + return err; +} + +static EebusError SetDataCache( + EebusMeasurementObject* self, + const ScaledValue* measured_value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +) { + EebusMeasurementBase* const m = BASE(self); + + if (measured_value == NULL) { + return kEebusErrorInputArgumentNull; + } + + MeasurementDataDelete(m->measurement_data); + + const AbsoluteOrRelativeTimeType* const start_tmp = ABSOLUTE_OR_RELATIVE_TIME_PTR(start_time); + const AbsoluteOrRelativeTimeType* const end_tmp = ABSOLUTE_OR_RELATIVE_TIME_PTR(end_time); + + const TimePeriodType* const eval_period = ((start_tmp != NULL) && (end_tmp != NULL)) + ? &(TimePeriodType){.start_time = start_tmp, .end_time = end_tmp} + : NULL; + + const MeasurementDataType new_data = { + .measurement_id = &m->id, + .value_type = &(MeasurementValueTypeType){kMeasurementValueTypeTypeValue}, + .timestamp = ABSOLUTE_OR_RELATIVE_TIME_PTR(timestamp), + .value = &(ScaledNumberType){.number = &measured_value->value, .scale = &measured_value->scale}, + .evaluation_period = eval_period, + .value_source = &m->value_source, + .value_tendency = NULL, + .value_state = value_state, + }; + + m->measurement_data = MeasurementDataCopy(&new_data); + if (m->measurement_data == NULL) { + return kEebusErrorMemoryAllocate; + } + + return kEebusErrorOk; +} + +static MeasurementDataType* ReleaseDataCache(EebusMeasurementObject* self) { + EebusMeasurementBase* const m = BASE(self); + MeasurementDataType* const data = m->measurement_data; + m->measurement_data = NULL; + return data; +} + +// --------------------------------------------------------------------------- +// Configure strategies +// --------------------------------------------------------------------------- + +EebusError EebusMeasurementBaseConfigurePower( + EebusMeasurementBase* m, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +) { + const MeasurementDescriptionDataType meas_desc = { + .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypePower}, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeW}, + .scope_type = &m->scope, + }; + + EebusError err = MeasurementServerAddDescription(msrv, &meas_desc, &m->id); + if (err != kEebusErrorOk) { + return err; + } + + const ElectricalConnectionParameterDescriptionDataType param_desc = { + .electrical_connection_id = &ec_id, + .measurement_id = &m->id, + .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), + .ac_measured_phases = &m->phases, + .ac_measured_in_reference_to = &ELECTRICAL_CONNECTION_PHASE_NAME(Neutral), + .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Real), + .ac_measurement_variant = &ELECTRICAL_CONNECTION_MEASURAND_VARIANT(Rms), + }; + + ElectricalConnectionParameterIdType param_id; + return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶m_desc, ¶m_id); +} + +EebusError EebusMeasurementBaseConfigureEnergy( + EebusMeasurementBase* m, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +) { + const MeasurementDescriptionDataType meas_desc = { + .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeEnergy}, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeWh}, + .scope_type = &m->scope, + }; + + EebusError err = MeasurementServerAddDescription(msrv, &meas_desc, &m->id); + if (err != kEebusErrorOk) { + return err; + } + + const ElectricalConnectionParameterDescriptionDataType param_desc = { + .electrical_connection_id = &ec_id, + .measurement_id = &m->id, + .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), + .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Real), + }; + + ElectricalConnectionParameterIdType param_id; + return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶m_desc, ¶m_id); +} + +EebusError EebusMeasurementBaseConfigureCurrent( + EebusMeasurementBase* m, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +) { + const MeasurementDescriptionDataType meas_desc = { + .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeCurrent}, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeA}, + .scope_type = &m->scope, + }; + + EebusError err = MeasurementServerAddDescription(msrv, &meas_desc, &m->id); + if (err != kEebusErrorOk) { + return err; + } + + const ElectricalConnectionParameterDescriptionDataType param_desc = { + .electrical_connection_id = &ec_id, + .measurement_id = &m->id, + .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), + .ac_measured_phases = &m->phases, + .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Real), + .ac_measurement_variant = &ELECTRICAL_CONNECTION_MEASURAND_VARIANT(Rms), + }; + + ElectricalConnectionParameterIdType param_id; + return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶m_desc, ¶m_id); +} + +static ElectricalConnectionPhaseNameType VoltagePhaseFrom(ElectricalConnectionPhaseNameType phases) { + switch (phases) { + case kElectricalConnectionPhaseNameTypeA: return kElectricalConnectionPhaseNameTypeA; + case kElectricalConnectionPhaseNameTypeB: return kElectricalConnectionPhaseNameTypeB; + case kElectricalConnectionPhaseNameTypeC: return kElectricalConnectionPhaseNameTypeC; + case kElectricalConnectionPhaseNameTypeAb: return kElectricalConnectionPhaseNameTypeA; + case kElectricalConnectionPhaseNameTypeBc: return kElectricalConnectionPhaseNameTypeB; + case kElectricalConnectionPhaseNameTypeAc: return kElectricalConnectionPhaseNameTypeC; + default: return 0; + } +} + +static ElectricalConnectionPhaseNameType VoltagePhaseTo(ElectricalConnectionPhaseNameType phases) { + switch (phases) { + case kElectricalConnectionPhaseNameTypeA: return kElectricalConnectionPhaseNameTypeNeutral; + case kElectricalConnectionPhaseNameTypeB: return kElectricalConnectionPhaseNameTypeNeutral; + case kElectricalConnectionPhaseNameTypeC: return kElectricalConnectionPhaseNameTypeNeutral; + case kElectricalConnectionPhaseNameTypeAb: return kElectricalConnectionPhaseNameTypeB; + case kElectricalConnectionPhaseNameTypeBc: return kElectricalConnectionPhaseNameTypeC; + case kElectricalConnectionPhaseNameTypeAc: return kElectricalConnectionPhaseNameTypeA; + default: return 0; + } +} + +EebusError EebusMeasurementBaseConfigureVoltage( + EebusMeasurementBase* m, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +) { + const MeasurementDescriptionDataType meas_desc = { + .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeVoltage}, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeV}, + .scope_type = &m->scope, + }; + + EebusError err = MeasurementServerAddDescription(msrv, &meas_desc, &m->id); + if (err != kEebusErrorOk) { + return err; + } + + const ElectricalConnectionParameterDescriptionDataType param_desc = { + .electrical_connection_id = &ec_id, + .measurement_id = &m->id, + .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), + .ac_measured_phases = &(ElectricalConnectionPhaseNameType){VoltagePhaseFrom(m->phases)}, + .ac_measured_in_reference_to = &(ElectricalConnectionPhaseNameType){VoltagePhaseTo(m->phases)}, + .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Apparent), + .ac_measurement_variant = &ELECTRICAL_CONNECTION_MEASURAND_VARIANT(Rms), + }; + + ElectricalConnectionParameterIdType param_id; + return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶m_desc, ¶m_id); +} + +EebusError EebusMeasurementBaseConfigureFrequency( + EebusMeasurementBase* m, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +) { + const MeasurementDescriptionDataType meas_desc = { + .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeFrequency}, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeHz}, + .scope_type = &m->scope, + }; + + EebusError err = MeasurementServerAddDescription(msrv, &meas_desc, &m->id); + if (err != kEebusErrorOk) { + return err; + } + + const ElectricalConnectionParameterDescriptionDataType param_desc = { + .electrical_connection_id = &ec_id, + .measurement_id = &m->id, + .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), + }; + + ElectricalConnectionParameterIdType param_id; + return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶m_desc, ¶m_id); +} diff --git a/src/use_case/actor/common/eebus_measurement_base.h b/src/use_case/actor/common/eebus_measurement_base.h new file mode 100644 index 0000000..447f127 --- /dev/null +++ b/src/use_case/actor/common/eebus_measurement_base.h @@ -0,0 +1,157 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared measurement base for MU MPC and GCP MGCP server-side use cases + */ + +#ifndef SRC_USE_CASE_ACTOR_COMMON_EEBUS_MEASUREMENT_BASE_H_ +#define SRC_USE_CASE_ACTOR_COMMON_EEBUS_MEASUREMENT_BASE_H_ + +#include "src/use_case/api/eebus_measurement_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct EebusMeasurementBase EebusMeasurementBase; + +/** + * @brief Strategy function pointer type used to register a measurement's SPINE + * description and electrical connection parameter description during + * use-case initialisation. + */ +typedef EebusError (*EebusMeasurementConfigureStrategy)( + EebusMeasurementBase* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +); + +typedef struct EebusMeasurementBaseConfig { + MeasurementValueSourceType value_source; + MeasurementConstraintsDataType* constraints; /**< May be NULL */ +} EebusMeasurementBaseConfig; + +/** + * @brief Allocate and initialise a measurement object. + * @param name Measurement name identifier (used to look up this object later). + * @param scope SPINE scope type (e.g. acPowerTotal, acCurrent). + * @param phases Electrical connection phase(s) this measurement applies to. + * @param cfg Value source and optional constraints — must not be NULL. + * @param strategy Configuration strategy called during use-case initialisation + * to register the SPINE measurement and parameter descriptions. + * @return Pointer to the created object, or NULL on allocation failure. + */ +EebusMeasurementObject* EebusMeasurementBaseCreate( + EebusMeasurementNameId name, + ScopeTypeType scope, + ElectricalConnectionPhaseNameType phases, + const EebusMeasurementBaseConfig* cfg, + EebusMeasurementConfigureStrategy strategy +); + +/** + * @brief Configure strategy: registers a power measurement description and + * the corresponding electrical connection parameter description. + * Intended to be passed as the @p strategy argument of EebusMeasurementBaseCreate(). + * @param self Measurement object being configured. + * @param msrv Measurement server used to add the measurement description. + * @param ecsrv Electrical connection server used to add the parameter description. + * @param ec_id Electrical connection ID to associate with the parameter description. + * @return kEebusErrorOk on success, or an error code on failure. + */ +EebusError EebusMeasurementBaseConfigurePower( + EebusMeasurementBase* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +); + +/** + * @brief Configure strategy: registers an energy measurement description and + * the corresponding electrical connection parameter description. + * Intended to be passed as the @p strategy argument of EebusMeasurementBaseCreate(). + * @param self Measurement object being configured. + * @param msrv Measurement server used to add the measurement description. + * @param ecsrv Electrical connection server used to add the parameter description. + * @param ec_id Electrical connection ID to associate with the parameter description. + * @return kEebusErrorOk on success, or an error code on failure. + */ +EebusError EebusMeasurementBaseConfigureEnergy( + EebusMeasurementBase* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +); + +/** + * @brief Configure strategy: registers a current measurement description and + * the corresponding electrical connection parameter description. + * Intended to be passed as the @p strategy argument of EebusMeasurementBaseCreate(). + * @param self Measurement object being configured. + * @param msrv Measurement server used to add the measurement description. + * @param ecsrv Electrical connection server used to add the parameter description. + * @param ec_id Electrical connection ID to associate with the parameter description. + * @return kEebusErrorOk on success, or an error code on failure. + */ +EebusError EebusMeasurementBaseConfigureCurrent( + EebusMeasurementBase* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +); + +/** + * @brief Configure strategy: registers a voltage measurement description and + * the corresponding electrical connection parameter description. + * Phase mapping (from/to) is derived from the phases field set at creation time. + * Intended to be passed as the @p strategy argument of EebusMeasurementBaseCreate(). + * @param self Measurement object being configured. + * @param msrv Measurement server used to add the measurement description. + * @param ecsrv Electrical connection server used to add the parameter description. + * @param ec_id Electrical connection ID to associate with the parameter description. + * @return kEebusErrorOk on success, or an error code on failure. + */ +EebusError EebusMeasurementBaseConfigureVoltage( + EebusMeasurementBase* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +); + +/** + * @brief Configure strategy: registers a frequency measurement description and + * the corresponding electrical connection parameter description. + * Intended to be passed as the @p strategy argument of EebusMeasurementBaseCreate(). + * @param self Measurement object being configured. + * @param msrv Measurement server used to add the measurement description. + * @param ecsrv Electrical connection server used to add the parameter description. + * @param ec_id Electrical connection ID to associate with the parameter description. + * @return kEebusErrorOk on success, or an error code on failure. + */ +EebusError EebusMeasurementBaseConfigureFrequency( + EebusMeasurementBase* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id +); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_COMMON_EEBUS_MEASUREMENT_BASE_H_ diff --git a/src/use_case/actor/common/eebus_monitor.c.tmp b/src/use_case/actor/common/eebus_monitor.c.tmp new file mode 100644 index 0000000..0c4fbff --- /dev/null +++ b/src/use_case/actor/common/eebus_monitor.c.tmp @@ -0,0 +1,6 @@ +EebusMonitor +void Destruct() +EebusMeasurementMonitorNameId GetName() const +EebusError Configure(MeasurementServer* msrv, ElectricalConnectionServer* ecsrv, ElectricalConnectionIdType ec_id, MeasurementConstraintsListDataType* constraints) +EebusMeasurementObject* GetMeasurement(EebusMeasurementNameId name_id) const +EebusError FlushMeasurementCache(MeasurementListDataType* list) diff --git a/src/use_case/actor/common/eebus_monitor_base.c b/src/use_case/actor/common/eebus_monitor_base.c new file mode 100644 index 0000000..8c897dd --- /dev/null +++ b/src/use_case/actor/common/eebus_monitor_base.c @@ -0,0 +1,182 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared monitor base implementation + */ + +#include "src/use_case/actor/common/eebus_monitor_base.h" + +#include "src/common/eebus_malloc.h" + +static void MeasurementDeallocator(void* p) { + EebusMeasurementObject* const m = (EebusMeasurementObject*)p; + if (m != NULL) { + EEBUS_MEASUREMENT_DESTRUCT(m); + EEBUS_FREE(m); + } +} + +static void Destruct(EebusMonitorObject* self) { + EebusMonitorBase* const base = EEBUS_MONITOR_BASE(self); + VectorFreeElements(&base->measurements); + VectorDestruct(&base->measurements); +} + +static EebusMeasurementMonitorNameId GetName(const EebusMonitorObject* self) { + return EEBUS_MONITOR_BASE(self)->name; +} + +static EebusError Configure( + EebusMonitorObject* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id, + MeasurementConstraintsListDataType* constraints +) { + if ((msrv == NULL) || (ecsrv == NULL)) { + return kEebusErrorInputArgumentNull; + } + + EebusMonitorBase* const base = EEBUS_MONITOR_BASE(self); + + for (size_t i = 0; i < VectorGetSize(&base->measurements); ++i) { + EebusMeasurementObject* const m = (EebusMeasurementObject*)VectorGetElement(&base->measurements, i); + + EebusError err = EEBUS_MEASUREMENT_CONFIGURE(m, msrv, ecsrv, ec_id); + if (err != kEebusErrorOk) { + return err; + } + + const MeasurementConstraintsDataType* const c = EEBUS_MEASUREMENT_GET_CONSTRAINTS(m); + if (c != NULL) { + err = MeasurementConstraintsAdd(constraints, c); + if (err != kEebusErrorOk) { + return err; + } + } + } + + return kEebusErrorOk; +} + +static EebusMeasurementObject* GetMeasurement(const EebusMonitorObject* self, EebusMeasurementNameId name_id) { + const EebusMonitorBase* const base = (const EebusMonitorBase*)self; + + if (((uint8_t)name_id & (uint8_t)base->name_id_mask) != (uint8_t)base->name) { + return NULL; + } + + for (size_t i = 0; i < VectorGetSize(&base->measurements); ++i) { + EebusMeasurementObject* const m = (EebusMeasurementObject*)VectorGetElement(&base->measurements, i); + if (EEBUS_MEASUREMENT_GET_NAME(m) == name_id) { + return m; + } + } + + return NULL; +} + +static EebusError FlushMeasurementCache(EebusMonitorObject* self, MeasurementListDataType* list) { + if (list == NULL) { + return kEebusErrorInputArgumentNull; + } + + EebusMonitorBase* const base = EEBUS_MONITOR_BASE(self); + + for (size_t i = 0; i < VectorGetSize(&base->measurements); ++i) { + EebusMeasurementObject* const m = (EebusMeasurementObject*)VectorGetElement(&base->measurements, i); + + MeasurementDataType* const data = EEBUS_MEASUREMENT_RELEASE_DATA_CACHE(m); + if (data != NULL) { + EebusError err = EebusDataListDataAppend((void***)&list->measurement_data, &list->measurement_data_size, data); + if (err != kEebusErrorOk) { + MeasurementDataDelete(data); + return err; + } + } + } + + return kEebusErrorOk; +} + +static const EebusMonitorInterface kEebusMonitorMethods = { + .destruct = Destruct, + .get_name = GetName, + .configure = Configure, + .get_measurement = GetMeasurement, + .flush_measurement_cache = FlushMeasurementCache, +}; + +EebusError EebusMonitorBaseConstruct( + EebusMonitorBase* self, + EebusMeasurementMonitorNameId name, + EebusMeasurementMonitorNameId name_id_mask, + EebusMonitorMeasurementCreator measurement_creator +) { + EEBUS_MONITOR_INTERFACE(self) = &kEebusMonitorMethods; + self->name = name; + self->name_id_mask = name_id_mask; + self->measurement_creator = measurement_creator; + VectorConstructWithDeallocator(&self->measurements, MeasurementDeallocator); + return kEebusErrorOk; +} + +EebusError EebusMonitorBaseAddMeasurements( + EebusMonitorBase* self, + const EebusMonitorMeasurementParam* params, + size_t params_size +) { + for (size_t i = 0; i < params_size; ++i) { + if (params[i].cfg == NULL) { + continue; + } + + EebusMeasurementObject* const m = self->measurement_creator(params[i].name_id, params[i].cfg); + if (m == NULL) { + return kEebusErrorInit; + } + + VectorPushBack(&self->measurements, m); + } + + return kEebusErrorOk; +} + +EebusMonitorObject* EebusMonitorCreate( + EebusMeasurementMonitorNameId name, + EebusMeasurementMonitorNameId name_id_mask, + EebusMonitorMeasurementCreator measurement_creator, + const EebusMonitorMeasurementParam* params, + size_t params_size +) { + EebusMonitorBase* const base = (EebusMonitorBase*)EEBUS_MALLOC(sizeof(EebusMonitorBase)); + if (base == NULL) { + return NULL; + } + + if (EebusMonitorBaseConstruct(base, name, name_id_mask, measurement_creator) != kEebusErrorOk) { + EebusMonitorDelete(EEBUS_MONITOR_OBJECT(base)); + return NULL; + } + + if (EebusMonitorBaseAddMeasurements(base, params, params_size) != kEebusErrorOk) { + EebusMonitorDelete(EEBUS_MONITOR_OBJECT(base)); + return NULL; + } + + return EEBUS_MONITOR_OBJECT(base); +} diff --git a/src/use_case/actor/common/eebus_monitor_base.h b/src/use_case/actor/common/eebus_monitor_base.h new file mode 100644 index 0000000..3f14061 --- /dev/null +++ b/src/use_case/actor/common/eebus_monitor_base.h @@ -0,0 +1,122 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared monitor base for MU MPC and GCP MGCP server-side use cases + */ + +#ifndef SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_BASE_H_ +#define SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_BASE_H_ + +#include + +#include "src/common/eebus_errors.h" +#include "src/common/eebus_malloc.h" +#include "src/common/vector.h" +#include "src/use_case/actor/common/eebus_measurement_base.h" +#include "src/use_case/api/eebus_measurement_interface.h" +#include "src/use_case/api/eebus_monitor_interface.h" +#include "src/use_case/model/eebus_measurement_types.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" +#include "src/use_case/specialization/measurement/measurement_server.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * @brief Function pointer type for measurement object creation used by the monitor. + * + * Concrete implementations (MuMpcMeasurementCreate, GcpMgcpMeasurementCreate) are + * directly compatible because their parameter types are typedef aliases of + * EebusMeasurementNameId and EebusMeasurementBaseConfig. + */ +typedef EebusMeasurementObject* (*EebusMonitorMeasurementCreator)( + EebusMeasurementNameId name_id, + const EebusMeasurementBaseConfig* cfg +); + +/** + * @brief A (name_id, cfg) pair used to batch-create measurements. + * A NULL cfg entry is silently skipped (optional measurement). + */ +typedef struct EebusMonitorMeasurementParam { + EebusMeasurementNameId name_id; + const EebusMeasurementBaseConfig* cfg; +} EebusMonitorMeasurementParam; + +/** + * @brief Shared internal base embedded in every concrete monitor struct. + * + * Embeds EebusMonitorObject as its first field, so any pointer to this struct + * is also a valid EebusMonitorObject pointer (and transitively, any concrete + * monitor whose first field is EebusMonitorBase is castable to EebusMonitorObject). + */ +typedef struct EebusMonitorBase { + EebusMonitorObject obj; /**< Vtable pointer — must be first field */ + EebusMeasurementMonitorNameId name; /**< Monitor group id */ + EebusMeasurementMonitorNameId name_id_mask; /**< Mask to extract the group from a measurement name id */ + Vector measurements; /**< Vector of EebusMeasurementObject* */ + EebusMonitorMeasurementCreator measurement_creator; +} EebusMonitorBase; + +/** + * @brief Cast any compatible pointer to EebusMonitorBase*. + */ +#define EEBUS_MONITOR_BASE(obj) ((EebusMonitorBase*)(obj)) + +/** + * @brief Initialise an embedded EebusMonitorBase. + */ +EebusError EebusMonitorBaseConstruct( + EebusMonitorBase* self, + EebusMeasurementMonitorNameId name, + EebusMeasurementMonitorNameId name_id_mask, + EebusMonitorMeasurementCreator measurement_creator +); + +/** + * @brief Create and append measurements from a param array; skips NULL cfg entries. + */ +EebusError +EebusMonitorBaseAddMeasurements(EebusMonitorBase* self, const EebusMonitorMeasurementParam* params, size_t params_size); + +/** + * @brief Destructs and frees an EebusMonitorObject instance. + */ +static inline void EebusMonitorDelete(EebusMonitorObject* monitor) { + if (monitor != NULL) { + EEBUS_MONITOR_DESTRUCT(monitor); + EEBUS_FREE(monitor); + } +} + +/** + * @brief Allocate, construct, and populate a new monitor; returns NULL on failure. + */ +EebusMonitorObject* EebusMonitorCreate( + EebusMeasurementMonitorNameId name, + EebusMeasurementMonitorNameId name_id_mask, + EebusMonitorMeasurementCreator measurement_creator, + const EebusMonitorMeasurementParam* params, + size_t params_size +); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_BASE_H_ diff --git a/src/use_case/actor/common/eebus_monitor_container.c b/src/use_case/actor/common/eebus_monitor_container.c new file mode 100644 index 0000000..2ec304f --- /dev/null +++ b/src/use_case/actor/common/eebus_monitor_container.c @@ -0,0 +1,172 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared monitor container implementation + */ + +#include "src/use_case/actor/common/eebus_monitor_container.h" + +#include "src/common/eebus_mutex/eebus_mutex.h" + +static void MonitorDeallocator(void* p) { + EebusMonitorDelete((EebusMonitorObject*)p); +} + +EebusError EebusMonitorContainerConstruct(EebusMonitorContainer* self) { + VectorConstructWithDeallocator(&self->monitors, MonitorDeallocator); + + self->mutex = EebusMutexCreate(); + if (self->mutex == NULL) { + VectorDestruct(&self->monitors); + return kEebusErrorMemoryAllocate; + } + + return kEebusErrorOk; +} + +void EebusMonitorContainerDestruct(EebusMonitorContainer* self) { + EebusMutexDelete(self->mutex); + self->mutex = NULL; + + VectorFreeElements(&self->monitors); + VectorDestruct(&self->monitors); +} + +void EebusMonitorContainerAdd(EebusMonitorContainer* self, EebusMonitorObject* monitor) { + VectorPushBack(&self->monitors, monitor); +} + +EebusMeasurementObject* +EebusMonitorContainerGetMeasurement(const EebusMonitorContainer* self, EebusMeasurementNameId name) { + const EebusMeasurementMonitorNameId monitor_name + = (EebusMeasurementMonitorNameId)((uint8_t)name & (uint8_t)kEebusMeasurementMonitorNameIdMask); + + for (size_t i = 0; i < VectorGetSize(&self->monitors); ++i) { + EebusMonitorObject* const monitor = (EebusMonitorObject*)VectorGetElement(&self->monitors, i); + if (EEBUS_MONITOR_GET_NAME(monitor) == monitor_name) { + return EEBUS_MONITOR_GET_MEASUREMENT(monitor, name); + } + } + + return NULL; +} + +static EebusError GetMeasurementDataInternal( + const EebusMonitorContainer* self, + EntityLocalObject* local_entity, + EebusMeasurementNameId name, + ScaledValue* out +) { + EebusMeasurementObject* const measurement = EebusMonitorContainerGetMeasurement(self, name); + if (measurement == NULL) { + return kEebusErrorNotSupported; + } + + MeasurementServer msrv = {0}; + const EebusError err = MeasurementServerConstruct(&msrv, local_entity); + if (err != kEebusErrorOk) { + return err; + } + + return EEBUS_MEASUREMENT_GET_DATA_VALUE(measurement, &msrv, out); +} + +EebusError EebusMonitorContainerGetMeasurementData( + const EebusMonitorContainer* self, + EntityLocalObject* local_entity, + DeviceLocalObject* local_device, + EebusMeasurementNameId name, + ScaledValue* out +) { + DEVICE_LOCAL_LOCK(local_device); + const EebusError err = GetMeasurementDataInternal(self, local_entity, name, out); + DEVICE_LOCAL_UNLOCK(local_device); + + return err; +} + +EebusError EebusMonitorContainerSetMeasurementDataCacheWithTime( + EebusMonitorContainer* self, + EebusMeasurementNameId name, + const ScaledValue* value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +) { + EebusMeasurementObject* const measurement = EebusMonitorContainerGetMeasurement(self, name); + if (measurement == NULL) { + return kEebusErrorNotSupported; + } + + EEBUS_MUTEX_LOCK(self->mutex); + const EebusError err + = EEBUS_MEASUREMENT_SET_DATA_CACHE(measurement, value, timestamp, value_state, start_time, end_time); + EEBUS_MUTEX_UNLOCK(self->mutex); + + return err; +} + +EebusError EebusMonitorContainerSetMeasurementDataCache( + EebusMonitorContainer* self, + EebusMeasurementNameId name, + const ScaledValue* value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state +) { + return EebusMonitorContainerSetMeasurementDataCacheWithTime(self, name, value, timestamp, value_state, NULL, NULL); +} + +EebusError EebusMonitorContainerUpdate( + EebusMonitorContainer* self, + EntityLocalObject* local_entity, + DeviceLocalObject* local_device +) { + MeasurementServer msrv = {0}; + EebusError err = MeasurementServerConstruct(&msrv, local_entity); + if (err != kEebusErrorOk) { + return err; + } + + MeasurementListDataType* const list = MeasurementsCreateEmpty(); + if (list == NULL) { + return kEebusErrorMemoryAllocate; + } + + EEBUS_MUTEX_LOCK(self->mutex); + for (size_t i = 0; i < VectorGetSize(&self->monitors); ++i) { + EebusMonitorObject* const monitor = (EebusMonitorObject*)VectorGetElement(&self->monitors, i); + + err = EEBUS_MONITOR_FLUSH_MEASUREMENT_CACHE(monitor, list); + if (err != kEebusErrorOk) { + EEBUS_MUTEX_UNLOCK(self->mutex); + MeasurementsDelete(list); + return err; + } + } + + EEBUS_MUTEX_UNLOCK(self->mutex); + + if (list->measurement_data_size > 0) { + DEVICE_LOCAL_LOCK(local_device); + err = MeasurementServerUpdateMeasurements(&msrv, list, NULL, NULL); + DEVICE_LOCAL_UNLOCK(local_device); + } + + MeasurementsDelete(list); + return err; +} diff --git a/src/use_case/actor/common/eebus_monitor_container.h b/src/use_case/actor/common/eebus_monitor_container.h new file mode 100644 index 0000000..7a623ff --- /dev/null +++ b/src/use_case/actor/common/eebus_monitor_container.h @@ -0,0 +1,85 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared monitor container for measurement-based use cases (MU MPC, GCP MGCP) + */ + +#ifndef SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_CONTAINER_H_ +#define SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_CONTAINER_H_ + +#include "src/common/api/eebus_mutex_interface.h" +#include "src/common/eebus_errors.h" +#include "src/common/vector.h" +#include "src/spine/api/device_local_interface.h" +#include "src/spine/api/entity_local_interface.h" +#include "src/use_case/actor/common/eebus_monitor_base.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct EebusMonitorContainer { + Vector monitors; + EebusMutexObject* mutex; +} EebusMonitorContainer; + +EebusError EebusMonitorContainerConstruct(EebusMonitorContainer* self); + +void EebusMonitorContainerDestruct(EebusMonitorContainer* self); + +void EebusMonitorContainerAdd(EebusMonitorContainer* self, EebusMonitorObject* monitor); + +EebusMeasurementObject* +EebusMonitorContainerGetMeasurement(const EebusMonitorContainer* self, EebusMeasurementNameId name); + +EebusError EebusMonitorContainerGetMeasurementData( + const EebusMonitorContainer* self, + EntityLocalObject* local_entity, + DeviceLocalObject* local_device, + EebusMeasurementNameId name, + ScaledValue* out +); + +EebusError EebusMonitorContainerSetMeasurementDataCacheWithTime( + EebusMonitorContainer* self, + EebusMeasurementNameId name, + const ScaledValue* value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +); + +EebusError EebusMonitorContainerSetMeasurementDataCache( + EebusMonitorContainer* self, + EebusMeasurementNameId name, + const ScaledValue* value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state +); + +EebusError EebusMonitorContainerUpdate( + EebusMonitorContainer* self, + EntityLocalObject* local_entity, + DeviceLocalObject* local_device +); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_CONTAINER_H_ diff --git a/src/use_case/actor/common/eebus_monitor_features.c b/src/use_case/actor/common/eebus_monitor_features.c new file mode 100644 index 0000000..7396c0f --- /dev/null +++ b/src/use_case/actor/common/eebus_monitor_features.c @@ -0,0 +1,100 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared SPINE feature wiring for measurement-based use cases (MU MPC, GCP MGCP) + */ + +#include "src/use_case/actor/common/eebus_monitor_features.h" + +#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" +#include "src/use_case/specialization/measurement/measurement_server.h" + +EebusError EebusMonitorFeaturesSetup( + EebusMonitorContainer* container, + EntityLocalObject* entity, + ElectricalConnectionIdType ec_id +) { + FeatureLocalObject* const ecfl + = ENTITY_LOCAL_ADD_FEATURE_WITH_TYPE_AND_ROLE(entity, kFeatureTypeTypeElectricalConnection, kRoleTypeServer); + if (ecfl == NULL) { + return kEebusErrorInit; + } + + FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(ecfl, kFunctionTypeElectricalConnectionDescriptionListData, true, false); + FEATURE_LOCAL_SET_FUNCTION_OPERATIONS( + ecfl, + kFunctionTypeElectricalConnectionParameterDescriptionListData, + true, + false + ); + + FeatureLocalObject* const mfl + = ENTITY_LOCAL_ADD_FEATURE_WITH_TYPE_AND_ROLE(entity, kFeatureTypeTypeMeasurement, kRoleTypeServer); + if (mfl == NULL) { + return kEebusErrorInit; + } + + FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(mfl, kFunctionTypeMeasurementDescriptionListData, true, false); + FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(mfl, kFunctionTypeMeasurementListData, true, false); + + MeasurementServer msrv; + EebusError err = MeasurementServerConstruct(&msrv, entity); + if (err != kEebusErrorOk) { + return err; + } + + ElectricalConnectionServer ecsrv; + err = ElectricalConnectionServerConstruct(&ecsrv, entity); + if (err != kEebusErrorOk) { + return err; + } + + if (ElectricalConnectionCommonGetDescriptionWithId(&ecsrv.el_connection_common, ec_id) == NULL) { + const ElectricalConnectionDescriptionDataType ec_description = { + .power_supply_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), + .positive_energy_direction = &(EnergyDirectionType){kEnergyDirectionTypeConsume}, + }; + + err = ElectricalConnectionServerAddDescriptionWithId(&ecsrv, &ec_description, ec_id); + if (err != kEebusErrorOk) { + return err; + } + } + + MeasurementConstraintsListDataType* const measurement_constraints = MeasurementConstraintsCreateEmpty(); + if (measurement_constraints == NULL) { + return kEebusErrorMemoryAllocate; + } + + for (size_t i = 0; i < VectorGetSize(&container->monitors); ++i) { + EebusMonitorObject* const monitor = (EebusMonitorObject*)VectorGetElement(&container->monitors, i); + + err = EEBUS_MONITOR_CONFIGURE(monitor, &msrv, &ecsrv, ec_id, measurement_constraints); + if (err != kEebusErrorOk) { + MeasurementConstraintsDelete(measurement_constraints); + return err; + } + } + + if (measurement_constraints->measurement_constraints_data_size != 0) { + FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(mfl, kFunctionTypeMeasurementConstraintsListData, true, false); + MeasurementServerUpdateMeasurementConstraints(&msrv, measurement_constraints, NULL, NULL); + } + + MeasurementConstraintsDelete(measurement_constraints); + return kEebusErrorOk; +} diff --git a/src/use_case/actor/common/eebus_monitor_features.h b/src/use_case/actor/common/eebus_monitor_features.h new file mode 100644 index 0000000..ef7f495 --- /dev/null +++ b/src/use_case/actor/common/eebus_monitor_features.h @@ -0,0 +1,43 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared SPINE feature wiring for measurement-based use cases (MU MPC, GCP MGCP) + */ + +#ifndef SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_FEATURES_H_ +#define SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_FEATURES_H_ + +#include "src/common/eebus_errors.h" +#include "src/spine/api/entity_local_interface.h" +#include "src/use_case/actor/common/eebus_monitor_container.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_common.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +EebusError EebusMonitorFeaturesSetup( + EebusMonitorContainer* container, + EntityLocalObject* entity, + ElectricalConnectionIdType ec_id +); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_COMMON_EEBUS_MONITOR_FEATURES_H_ diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp.c b/src/use_case/actor/gcp/mgcp/gcp_mgcp.c new file mode 100644 index 0000000..11921d6 --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp.c @@ -0,0 +1,331 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Grid Connection Point MGCP use case implementation + */ + +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp.h" + +#include "src/common/array_util.h" +#include "src/use_case/actor/common/eebus_monitor_features.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_internal.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h" +#include "src/use_case/specialization/device_configuration/device_configuration_server.h" +#include "src/use_case/use_case.h" + +/* Only the MA (Monitoring Appliance) may connect to this use case */ +static const UseCaseActorType valid_actor_types[] = {kUseCaseActorTypeMonitoringAppliance}; + +static void Destruct(UseCaseObject* self); +static bool IsEntityCompatible(const UseCaseObject* self, const EntityRemoteObject* remote_entity); + +static const UseCaseInterface gcp_mgcp_use_case_methods = { + .destruct = Destruct, + .is_entity_compatible = IsEntityCompatible, + .is_use_case_compatible = UseCaseIsUseCaseCompatible, + .get_remote_entity_with_address = UseCaseGetRemoteEntityWithAddress, +}; + +static EebusError AddGcpMgcpScenario1(GcpMgcpUseCase* self) { + self->has_scenario1 = true; + + static const FeatureTypeType scenario1_features[] = {kFeatureTypeTypeDeviceConfiguration}; + + self->use_case_scenarios[self->use_case_scenarios_size++] = (UseCaseScenario){ + .scenario = (UseCaseScenarioSupportType)1, + .mandatory = false, + .server_features = scenario1_features, + .server_features_size = ARRAY_SIZE(scenario1_features), + }; + + return kEebusErrorOk; +} + +static EebusError AddGcpMgcpScenario2(GcpMgcpUseCase* self, const GcpMgcpMonitorPowerConfig* power_cfg) { + EebusMonitorObject* const power_monitor = GcpMgcpMonitorPowerCreate(power_cfg); + if (power_monitor == NULL) { + return kEebusErrorInit; + } + + EebusMonitorContainerAdd(&self->monitor_container, power_monitor); + + static const FeatureTypeType use_case_scenario_2_features[] = { + kFeatureTypeTypeElectricalConnection, + kFeatureTypeTypeMeasurement, + }; + + self->use_case_scenarios[self->use_case_scenarios_size++] = (UseCaseScenario){ + .scenario = (UseCaseScenarioSupportType)2, + .mandatory = true, + .server_features = use_case_scenario_2_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_2_features), + }; + + return kEebusErrorOk; +} + +static EebusError AddGcpMgcpEnergyScenarios(GcpMgcpUseCase* self, const GcpMgcpMonitorEnergyConfig* energy_cfg) { + EebusMonitorObject* const energy_monitor = GcpMgcpMonitorEnergyCreate(energy_cfg); + if (energy_monitor == NULL) { + return kEebusErrorInit; + } + + EebusMonitorContainerAdd(&self->monitor_container, energy_monitor); + + static const FeatureTypeType energy_features[] = { + kFeatureTypeTypeElectricalConnection, + kFeatureTypeTypeMeasurement, + }; + + if (energy_cfg->energy_feed_in_cfg != NULL) { + self->use_case_scenarios[self->use_case_scenarios_size++] = (UseCaseScenario){ + .scenario = (UseCaseScenarioSupportType)3, + .mandatory = false, + .server_features = energy_features, + .server_features_size = ARRAY_SIZE(energy_features), + }; + } + + if (energy_cfg->energy_consumed_cfg != NULL) { + self->use_case_scenarios[self->use_case_scenarios_size++] = (UseCaseScenario){ + .scenario = (UseCaseScenarioSupportType)4, + .mandatory = false, + .server_features = energy_features, + .server_features_size = ARRAY_SIZE(energy_features), + }; + } + + return kEebusErrorOk; +} + +static EebusError AddGcpMgcpScenario5(GcpMgcpUseCase* self, const GcpMgcpMonitorCurrentConfig* current_cfg) { + EebusMonitorObject* const current_monitor = GcpMgcpMonitorCurrentCreate(current_cfg); + if (current_monitor == NULL) { + return kEebusErrorInit; + } + + EebusMonitorContainerAdd(&self->monitor_container, current_monitor); + + static const FeatureTypeType use_case_scenario_5_features[] = { + kFeatureTypeTypeElectricalConnection, + kFeatureTypeTypeMeasurement, + }; + + self->use_case_scenarios[self->use_case_scenarios_size++] = (UseCaseScenario){ + .scenario = (UseCaseScenarioSupportType)5, + .mandatory = false, + .server_features = use_case_scenario_5_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_5_features), + }; + + return kEebusErrorOk; +} + +static EebusError AddGcpMgcpScenario6(GcpMgcpUseCase* self, const GcpMgcpMonitorVoltageConfig* voltage_cfg) { + EebusMonitorObject* const voltage_monitor = GcpMgcpMonitorVoltageCreate(voltage_cfg); + if (voltage_monitor == NULL) { + return kEebusErrorInit; + } + + EebusMonitorContainerAdd(&self->monitor_container, voltage_monitor); + + static const FeatureTypeType use_case_scenario_6_features[] = { + kFeatureTypeTypeElectricalConnection, + kFeatureTypeTypeMeasurement, + }; + + self->use_case_scenarios[self->use_case_scenarios_size++] = (UseCaseScenario){ + .scenario = (UseCaseScenarioSupportType)6, + .mandatory = false, + .server_features = use_case_scenario_6_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_6_features), + }; + + return kEebusErrorOk; +} + +static EebusError AddGcpMgcpScenario7(GcpMgcpUseCase* self, const GcpMgcpMonitorFrequencyConfig* frequency_cfg) { + EebusMonitorObject* const frequency_monitor = GcpMgcpMonitorFrequencyCreate(frequency_cfg); + if (frequency_monitor == NULL) { + return kEebusErrorInit; + } + + EebusMonitorContainerAdd(&self->monitor_container, frequency_monitor); + + static const FeatureTypeType use_case_scenario_7_features[] = { + kFeatureTypeTypeElectricalConnection, + kFeatureTypeTypeMeasurement, + }; + + self->use_case_scenarios[self->use_case_scenarios_size++] = (UseCaseScenario){ + .scenario = (UseCaseScenarioSupportType)7, + .mandatory = false, + .server_features = use_case_scenario_7_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_7_features), + }; + + return kEebusErrorOk; +} + +static EebusError AddDeviceConfigurationFeature(EntityLocalObject* entity) { + FeatureLocalObject* const fl + = ENTITY_LOCAL_ADD_FEATURE_WITH_TYPE_AND_ROLE(entity, kFeatureTypeTypeDeviceConfiguration, kRoleTypeServer); + if (fl == NULL) { + return kEebusErrorInit; + } + + FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(fl, kFunctionTypeDeviceConfigurationKeyValueDescriptionListData, true, false); + FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(fl, kFunctionTypeDeviceConfigurationKeyValueListData, true, false); + + DeviceConfigurationServer dcs; + if (DeviceConfigurationServerConstruct(&dcs, entity) != kEebusErrorOk) { + return kEebusErrorInit; + } + + const DeviceConfigurationKeyValueDescriptionDataType description = { + .key_name = &(DeviceConfigurationKeyNameType){kDeviceConfigurationKeyNameTypePvCurtailmentLimitFactor}, + .value_type = &(DeviceConfigurationKeyValueTypeType){kDeviceConfigurationKeyValueTypeTypeScaledNumber}, + .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypepct}, + }; + + return DeviceConfigurationServerAddKeyValueDescription(&dcs, &description); +} + +static EebusError GcpMgcpUseCaseConstruct( + GcpMgcpUseCase* self, + EntityLocalObject* local_entity, + ElectricalConnectionIdType ec_id, + const GcpMgcpConfig* cfg +) { + USE_CASE_INTERFACE(self) = &gcp_mgcp_use_case_methods; + + self->electrical_connection_id = ec_id; + self->use_case_scenarios_size = 0; + self->has_scenario1 = false; + + const EebusError container_err = EebusMonitorContainerConstruct(&self->monitor_container); + if (container_err != kEebusErrorOk) { + return container_err; + } + + // Scenario 1: PV curtailment limit factor (optional) + if (cfg->pv_curtailment_cfg != NULL) { + const EebusError err = AddGcpMgcpScenario1(self); + if (err != kEebusErrorOk) { + return err; + } + } + + // Scenario 2: power total (mandatory) + EebusError err = AddGcpMgcpScenario2(self, &cfg->power_cfg); + if (err != kEebusErrorOk) { + return err; + } + + // Scenarios 3 and/or 4: energy (optional) + if (cfg->energy_cfg != NULL) { + err = AddGcpMgcpEnergyScenarios(self, cfg->energy_cfg); + if (err != kEebusErrorOk) { + return err; + } + } + + // Scenario 5: per-phase current (optional) + if (cfg->current_cfg != NULL) { + err = AddGcpMgcpScenario5(self, cfg->current_cfg); + if (err != kEebusErrorOk) { + return err; + } + } + + // Scenario 6: per-phase voltage (optional) + if (cfg->voltage_cfg != NULL) { + err = AddGcpMgcpScenario6(self, cfg->voltage_cfg); + if (err != kEebusErrorOk) { + return err; + } + } + + // Scenario 7: frequency (optional) + if (cfg->frequency_cfg != NULL) { + err = AddGcpMgcpScenario7(self, cfg->frequency_cfg); + if (err != kEebusErrorOk) { + return err; + } + } + + self->gcp_mgcp_use_case_info = (UseCaseInfo){ + .valid_actor_types = valid_actor_types, + .valid_actor_types_size = ARRAY_SIZE(valid_actor_types), + .valid_entity_types = NULL, + .valid_entity_types_size = 0, + .use_case_scenarios = self->use_case_scenarios, + .use_case_scenarios_size = self->use_case_scenarios_size, + .actor = kUseCaseActorTypeGridConnectionPoint, + .use_case_name_id = kUseCaseNameTypeMonitoringOfGridConnectionPoint, + .version = "1.0.0", + .sub_revision = "release", + .available = true, + }; + + UseCaseConstruct(USE_CASE(self), &self->gcp_mgcp_use_case_info, local_entity, NULL); + + const EebusError features_err + = EebusMonitorFeaturesSetup(&self->monitor_container, local_entity, self->electrical_connection_id); + if (features_err != kEebusErrorOk) { + return features_err; + } + + if (self->has_scenario1) { + return AddDeviceConfigurationFeature(local_entity); + } + + return kEebusErrorOk; +} + +GcpMgcpUseCaseObject* +GcpMgcpUseCaseCreate(EntityLocalObject* local_entity, ElectricalConnectionIdType ec_id, const GcpMgcpConfig* cfg) { + if (cfg == NULL) { + return NULL; + } + + GcpMgcpUseCase* const gcp_mgcp_use_case = (GcpMgcpUseCase*)EEBUS_MALLOC(sizeof(GcpMgcpUseCase)); + if (gcp_mgcp_use_case == NULL) { + return NULL; + } + + if (GcpMgcpUseCaseConstruct(gcp_mgcp_use_case, local_entity, ec_id, cfg) != kEebusErrorOk) { + GcpMgcpUseCaseDelete(GCP_MGCP_USE_CASE_OBJECT(gcp_mgcp_use_case)); + return NULL; + } + + return GCP_MGCP_USE_CASE_OBJECT(gcp_mgcp_use_case); +} + +static void Destruct(UseCaseObject* self) { + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + + EebusMonitorContainerDestruct(&gcp_mgcp->monitor_container); + + UseCaseDestruct(self); +} + +static bool IsEntityCompatible(const UseCaseObject* self, const EntityRemoteObject* remote_entity) { + (void)self; + (void)remote_entity; + return true; +} diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp.h b/src/use_case/actor/gcp/mgcp/gcp_mgcp.h new file mode 100644 index 0000000..d31a0b6 --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp.h @@ -0,0 +1,229 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Grid Connection Point MGCP use case (Monitoring of Grid Connection Point). + * + * Scenarios supported: + * Scenario 1 — Publish PV feed-in power limitation factor (optional) + * Scenario 2 — Monitor momentary power consumption/production (mandatory) + * Scenario 3 — Monitor total feed-in energy (optional) + * Scenario 4 — Monitor total consumed energy (optional) + * Scenario 5 — Monitor momentary current per phase (optional) + * Scenario 6 — Monitor voltage per phase (optional) + * Scenario 7 — Monitor frequency (optional) + * + * @code{.c} + * GcpMgcpUseCaseObject* const gcp_mgcp = + * GcpMgcpUseCaseCreate(local_entity, ec_id, &cfg); + * + * // Cache a power measurement + * GcpMgcpSetMeasurementDataCache(gcp_mgcp, kGcpPowerTotal, &(ScaledValue){5000, 0}, NULL, NULL); + * + * // Cache energy with evaluation period + * GcpMgcpSetEnergyFeedInCache(gcp_mgcp, &(ScaledValue){200, 0}, NULL, NULL, &start_time, &end_time); + * GcpMgcpSetEnergyConsumedCache(gcp_mgcp, &(ScaledValue){800, 0}, NULL, NULL, &start_time, &end_time); + * + * // Flush all caches to the local feature and notify remotes + * GcpMgcpUpdate(gcp_mgcp); + * @endcode + */ + +#ifndef SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_H_ +#define SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_H_ + +#include + +#include "src/common/eebus_malloc.h" +#include "src/spine/entity/entity_local.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h" +#include "src/use_case/api/use_case_interface.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" +#include "src/use_case/use_case.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * @brief GCP MGCP Scenario 1 configuration + * + * Scenario 1 — Publish PV feed-in power limitation factor (DeviceConfiguration, + * key "pvCurtailmentLimitFactor", unit %). Presence of a non-NULL pointer in + * GcpMgcpConfig::pv_curtailment_cfg signals support for this scenario. + */ +typedef struct GcpMgcpPvCurtailmentConfig GcpMgcpPvCurtailmentConfig; + +struct GcpMgcpPvCurtailmentConfig { + uint8_t reserved; /**< Reserved for future extension */ +}; + +/** + * @brief GCP MGCP use case configuration (scenarios 1–7) + */ +typedef struct GcpMgcpConfig GcpMgcpConfig; + +struct GcpMgcpConfig { + /** Optional: Scenario 1 — PV curtailment limit factor; NULL = not supported */ + const GcpMgcpPvCurtailmentConfig* pv_curtailment_cfg; + /** Required: Scenario 2 — total active power */ + const GcpMgcpMonitorPowerConfig power_cfg; + /** Optional: Scenarios 3 and/or 4 — grid energy; NULL = not supported */ + const GcpMgcpMonitorEnergyConfig* energy_cfg; + /** Optional: Scenario 5 — per-phase AC current; NULL = not supported */ + const GcpMgcpMonitorCurrentConfig* current_cfg; + /** Optional: Scenario 6 — per-phase AC voltage; NULL = not supported */ + const GcpMgcpMonitorVoltageConfig* voltage_cfg; + /** Optional: Scenario 7 — AC frequency; NULL = not supported */ + const GcpMgcpMonitorFrequencyConfig* frequency_cfg; +}; + +typedef struct GcpMgcpUseCaseObject GcpMgcpUseCaseObject; + +struct GcpMgcpUseCaseObject { + /** Inherits the Use Case */ + UseCaseObject obj; +}; + +#define GCP_MGCP_USE_CASE_OBJECT(obj) ((GcpMgcpUseCaseObject*)(obj)) + +/** + * @brief Create a GCP MGCP use case instance + * @param local_entity Pointer to the local entity object + * @param ec_id Electrical connection id used for all measurements + * @param cfg Scenario configuration; must not be NULL + * @return Pointer to the created instance, or NULL on failure + */ +GcpMgcpUseCaseObject* +GcpMgcpUseCaseCreate(EntityLocalObject* local_entity, ElectricalConnectionIdType ec_id, const GcpMgcpConfig* cfg); + +/** + * @brief Delete a GCP MGCP use case instance + * @param gcp_mgcp Pointer to the GCP MGCP use case instance to delete + */ +static inline void GcpMgcpUseCaseDelete(GcpMgcpUseCaseObject* gcp_mgcp) { + UseCaseDelete(USE_CASE_OBJECT(gcp_mgcp)); +} + +/** + * @brief Read the current value of a measurement from the local feature + * @param self GCP MGCP use case instance + * @param measurement_name Measurement to read (see GcpMeasurementNameId) + * @param measurement_value Output buffer; must not be NULL + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError GcpMgcpGetMeasurementData( + const GcpMgcpUseCaseObject* self, + GcpMeasurementNameId measurement_name, + ScaledValue* measurement_value +); + +/** + * @brief Stage a measurement value for the next GcpMgcpUpdate() call + * + * For energy measurements with an evaluation period, prefer + * GcpMgcpSetEnergyFeedInCache() / GcpMgcpSetEnergyConsumedCache(). + * + * @param self GCP MGCP use case instance + * @param measurement_name Measurement to set (see GcpMeasurementNameId) + * @param measurement_value Value to stage; must not be NULL + * @param timestamp Optional measurement timestamp; may be NULL + * @param value_state Optional value state; may be NULL + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError GcpMgcpSetMeasurementDataCache( + GcpMgcpUseCaseObject* self, + GcpMeasurementNameId measurement_name, + const ScaledValue* measurement_value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state +); + +/** + * @brief Flush all staged measurement values to the local feature + * and notify connected remote features + * @param self GCP MGCP use case instance + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError GcpMgcpUpdate(const GcpMgcpUseCaseObject* self); + +/** + * @brief Stage a feed-in energy value (Scenario 3) with optional evaluation period + * @param self GCP MGCP use case instance + * @param energy_feed_in Feed-in energy value; must not be NULL + * @param timestamp Optional measurement timestamp; may be NULL + * @param value_state Optional value state; may be NULL + * @param start_time Evaluation period start; may be NULL + * @param end_time Evaluation period end; may be NULL + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError GcpMgcpSetEnergyFeedInCache( + GcpMgcpUseCaseObject* self, + const ScaledValue* energy_feed_in, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +); + +/** + * @brief Stage a consumed energy value (Scenario 4) with optional evaluation period + * @param self GCP MGCP use case instance + * @param energy_consumed Consumed energy value; must not be NULL + * @param timestamp Optional measurement timestamp; may be NULL + * @param value_state Optional value state; may be NULL + * @param start_time Evaluation period start; may be NULL + * @param end_time Evaluation period end; may be NULL + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError GcpMgcpSetEnergyConsumedCache( + GcpMgcpUseCaseObject* self, + const ScaledValue* energy_consumed, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +); + +/** + * @brief Publish the PV feed-in power limitation factor (Scenario 1) + * + * Updates the DeviceConfiguration key "pvCurtailmentLimitFactor" on the local + * server feature and notifies any subscribed remote entities. + * + * @param self GCP MGCP use case instance + * @param value Curtailment factor (0–100 %); must not be NULL + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError GcpMgcpSetPvCurtailmentLimitFactor(GcpMgcpUseCaseObject* self, const ScaledValue* value); + +/** + * @brief Read the current PV feed-in power limitation factor (Scenario 1) + * + * Reads the DeviceConfiguration key "pvCurtailmentLimitFactor" from the local + * server feature. + * + * @param self GCP MGCP use case instance + * @param value Output buffer; must not be NULL + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError GcpMgcpGetPvCurtailmentLimitFactor(const GcpMgcpUseCaseObject* self, ScaledValue* value); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_H_ diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp_internal.h b/src/use_case/actor/gcp/mgcp/gcp_mgcp_internal.h new file mode 100644 index 0000000..efb990e --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp_internal.h @@ -0,0 +1,59 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief GCP MGCP use case internal declarations + */ + +#ifndef SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_INTERNAL_H_ +#define SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_INTERNAL_H_ + +#include +#include + +#include "src/use_case/actor/common/eebus_monitor_container.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct GcpMgcpUseCase GcpMgcpUseCase; + +struct GcpMgcpUseCase { + /** Inherits the Use Case */ + UseCase obj; + + ElectricalConnectionIdType electrical_connection_id; + + EebusMonitorContainer monitor_container; + + /** Dynamically built scenario array (scenarios 1–7, up to 7 entries) */ + UseCaseScenario use_case_scenarios[7]; + size_t use_case_scenarios_size; + + bool has_scenario1; + + UseCaseInfo gcp_mgcp_use_case_info; +}; + +#define GCP_MGCP_USE_CASE(self) ((GcpMgcpUseCase*)(self)) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_INTERNAL_H_ diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.c b/src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.c new file mode 100644 index 0000000..4f2f469 --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.c @@ -0,0 +1,93 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief GCP MGCP Measurement implementation + */ + +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.h" + +static ScopeTypeType GetEnergyScopeType(GcpMeasurementNameId name) { + switch (name) { + case kGcpEnergyFeedIn: return kScopeTypeTypeGridFeedIn; + case kGcpEnergyConsumed: return kScopeTypeTypeGridConsumption; + default: return kScopeTypeTypeACEnergy; + } +} + +static ElectricalConnectionPhaseNameType GetCurrentPhase(GcpMeasurementNameId name) { + switch (name) { + case kGcpCurrentPhaseA: return kElectricalConnectionPhaseNameTypeA; + case kGcpCurrentPhaseB: return kElectricalConnectionPhaseNameTypeB; + case kGcpCurrentPhaseC: return kElectricalConnectionPhaseNameTypeC; + default: return kElectricalConnectionPhaseNameTypeNone; + } +} + +static ElectricalConnectionPhaseNameType GetVoltagePhase(GcpMeasurementNameId name) { + switch (name) { + case kGcpVoltagePhaseA: return kElectricalConnectionPhaseNameTypeA; + case kGcpVoltagePhaseB: return kElectricalConnectionPhaseNameTypeB; + case kGcpVoltagePhaseC: return kElectricalConnectionPhaseNameTypeC; + case kGcpVoltagePhaseAb: return kElectricalConnectionPhaseNameTypeAb; + case kGcpVoltagePhaseBc: return kElectricalConnectionPhaseNameTypeBc; + case kGcpVoltagePhaseAc: return kElectricalConnectionPhaseNameTypeAc; + default: return kElectricalConnectionPhaseNameTypeNone; + } +} + +EebusMeasurementObject* +GcpMgcpMeasurementPowerTotalCreate(ElectricalConnectionPhaseNameType phases, const GcpMgcpMeasurementConfig* cfg) { + return EebusMeasurementBaseCreate( + kGcpPowerTotal, + kScopeTypeTypeACPowerTotal, + phases, + cfg, + EebusMeasurementBaseConfigurePower + ); +} + +EebusMeasurementObject* GcpMgcpMeasurementCreate(GcpMeasurementNameId name, const GcpMgcpMeasurementConfig* cfg) { + ScopeTypeType scope = (ScopeTypeType)0; + ElectricalConnectionPhaseNameType phase = (ElectricalConnectionPhaseNameType)0; + EebusMeasurementConfigureStrategy strategy = NULL; + const int32_t measurement_group = (int32_t)name & (int32_t)kGcpMonitorNameIdMask; + + if (measurement_group == kGcpMonitorPower) { + // kGcpPowerTotal is the only power measurement; use GcpMgcpMeasurementPowerTotalCreate() instead + return NULL; + } else if (measurement_group == kGcpMonitorEnergy) { + scope = GetEnergyScopeType(name); + phase = kElectricalConnectionPhaseNameTypeNone; + strategy = EebusMeasurementBaseConfigureEnergy; + } else if (measurement_group == kGcpMonitorCurrent) { + scope = kScopeTypeTypeACCurrent; + phase = GetCurrentPhase(name); + strategy = EebusMeasurementBaseConfigureCurrent; + } else if (measurement_group == kGcpMonitorVoltage) { + scope = kScopeTypeTypeACVoltage; + phase = GetVoltagePhase(name); + strategy = EebusMeasurementBaseConfigureVoltage; + } else if (measurement_group == kGcpMonitorFrequency) { + scope = kScopeTypeTypeACFrequency; + phase = kElectricalConnectionPhaseNameTypeNone; + strategy = EebusMeasurementBaseConfigureFrequency; + } else { + return NULL; + } + + return EebusMeasurementBaseCreate(name, scope, phase, cfg, strategy); +} diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.h b/src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.h new file mode 100644 index 0000000..e1af277 --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.h @@ -0,0 +1,79 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief GCP MGCP Measurement implementation declarations + */ + +#ifndef SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_MEASUREMENT_H_ +#define SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_MEASUREMENT_H_ + +#include "src/common/eebus_malloc.h" +#include "src/use_case/actor/common/eebus_measurement_base.h" +#include "src/use_case/model/mgcp_types.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef EebusMeasurementBaseConfig GcpMgcpMeasurementConfig; + +/** + * @brief Create a power-total measurement with explicit phases + * + * Used by GcpMgcpMonitorPowerCreate() to set the connected-phase bitmask on the + * electrical-connection parameter description. + * + * @param phases Connected phases (e.g. kElectricalConnectionPhaseNameTypeAbc) + * @param cfg Measurement configuration + * @return Pointer to created object, or NULL on failure + */ +EebusMeasurementObject* +GcpMgcpMeasurementPowerTotalCreate(ElectricalConnectionPhaseNameType phases, const GcpMgcpMeasurementConfig* cfg); + +/** + * @brief Create a measurement object for the given name id + * + * Supported name ids: + * Scenario 2: kGcpPowerTotal + * Scenario 3: kGcpEnergyFeedIn + * Scenario 4: kGcpEnergyConsumed + * Scenario 5: kGcpCurrentPhaseA, kGcpCurrentPhaseB, kGcpCurrentPhaseC + * Scenario 6: kGcpVoltagePhaseA, kGcpVoltagePhaseB, kGcpVoltagePhaseC, + * kGcpVoltagePhaseAb, kGcpVoltagePhaseBc, kGcpVoltagePhaseAc + * Scenario 7: kGcpFrequency + * + * @param name The measurement name identifier + * @param cfg Measurement configuration (value source + optional constraints) + * @return Pointer to created object, or NULL on failure + */ +EebusMeasurementObject* GcpMgcpMeasurementCreate(GcpMeasurementNameId name, const GcpMgcpMeasurementConfig* cfg); + +/** + * @brief Delete a EebusMeasurementObject + */ +static inline void GcpMgcpMeasurementDelete(EebusMeasurementObject* measurement) { + if (measurement != NULL) { + EEBUS_MEASUREMENT_DESTRUCT(measurement); + EEBUS_FREE(measurement); + } +} + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_MEASUREMENT_H_ diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.c b/src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.c new file mode 100644 index 0000000..4b90510 --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.c @@ -0,0 +1,154 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief GCP MGCP Monitor implementation + */ + +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h" + +#include "src/common/array_util.h" +#include "src/common/eebus_malloc.h" +#include "src/use_case/actor/common/eebus_monitor_base.h" + +EebusMonitorObject* GcpMgcpMonitorPowerCreate(const GcpMgcpMonitorPowerConfig* cfg) { + if (cfg == NULL) { + return NULL; + } + + EebusMonitorBase* const base = (EebusMonitorBase*)EEBUS_MALLOC(sizeof(EebusMonitorBase)); + if (base == NULL) { + return NULL; + } + + if (EebusMonitorBaseConstruct(base, kGcpMonitorPower, kGcpMonitorNameIdMask, GcpMgcpMeasurementCreate) + != kEebusErrorOk) { + EebusMonitorDelete(EEBUS_MONITOR_OBJECT(base)); + return NULL; + } + + EebusMeasurementObject* const power_total = GcpMgcpMeasurementPowerTotalCreate(cfg->phases, &cfg->power_total_cfg); + if (power_total == NULL) { + EebusMonitorDelete(EEBUS_MONITOR_OBJECT(base)); + return NULL; + } + + VectorPushBack(&base->measurements, power_total); + return EEBUS_MONITOR_OBJECT(base); +} + +EebusMonitorObject* GcpMgcpMonitorEnergyCreate(const GcpMgcpMonitorEnergyConfig* cfg) { + if (cfg == NULL) { + return NULL; + } + + if ((cfg->energy_feed_in_cfg == NULL) && (cfg->energy_consumed_cfg == NULL)) { + return NULL; + } + + const EebusMonitorMeasurementParam params[] = { + { kGcpEnergyFeedIn, cfg->energy_feed_in_cfg}, + {kGcpEnergyConsumed, cfg->energy_consumed_cfg}, + }; + + return EebusMonitorCreate( + kGcpMonitorEnergy, + kGcpMonitorNameIdMask, + GcpMgcpMeasurementCreate, + params, + ARRAY_SIZE(params) + ); +} + +EebusMonitorObject* GcpMgcpMonitorCurrentCreate(const GcpMgcpMonitorCurrentConfig* cfg) { + if (cfg == NULL) { + return NULL; + } + + if ((cfg->current_phase_a_cfg == NULL) && (cfg->current_phase_b_cfg == NULL) && (cfg->current_phase_c_cfg == NULL)) { + return NULL; + } + + const EebusMonitorMeasurementParam params[] = { + {kGcpCurrentPhaseA, cfg->current_phase_a_cfg}, + {kGcpCurrentPhaseB, cfg->current_phase_b_cfg}, + {kGcpCurrentPhaseC, cfg->current_phase_c_cfg}, + }; + + return EebusMonitorCreate( + kGcpMonitorCurrent, + kGcpMonitorNameIdMask, + GcpMgcpMeasurementCreate, + params, + ARRAY_SIZE(params) + ); +} + +EebusMonitorObject* GcpMgcpMonitorVoltageCreate(const GcpMgcpMonitorVoltageConfig* cfg) { + if (cfg == NULL) { + return NULL; + } + + if ((cfg->voltage_phase_ab_cfg != NULL) + && ((cfg->voltage_phase_a_cfg == NULL) || (cfg->voltage_phase_b_cfg == NULL))) { + return NULL; + } + + if ((cfg->voltage_phase_bc_cfg != NULL) + && ((cfg->voltage_phase_b_cfg == NULL) || (cfg->voltage_phase_c_cfg == NULL))) { + return NULL; + } + + if ((cfg->voltage_phase_ac_cfg != NULL) + && ((cfg->voltage_phase_a_cfg == NULL) || (cfg->voltage_phase_c_cfg == NULL))) { + return NULL; + } + + const EebusMonitorMeasurementParam params[] = { + { kGcpVoltagePhaseA, cfg->voltage_phase_a_cfg}, + { kGcpVoltagePhaseB, cfg->voltage_phase_b_cfg}, + { kGcpVoltagePhaseC, cfg->voltage_phase_c_cfg}, + {kGcpVoltagePhaseAb, cfg->voltage_phase_ab_cfg}, + {kGcpVoltagePhaseBc, cfg->voltage_phase_bc_cfg}, + {kGcpVoltagePhaseAc, cfg->voltage_phase_ac_cfg}, + }; + + return EebusMonitorCreate( + kGcpMonitorVoltage, + kGcpMonitorNameIdMask, + GcpMgcpMeasurementCreate, + params, + ARRAY_SIZE(params) + ); +} + +EebusMonitorObject* GcpMgcpMonitorFrequencyCreate(const GcpMgcpMonitorFrequencyConfig* cfg) { + if (cfg == NULL) { + return NULL; + } + + const EebusMonitorMeasurementParam params[] = { + {kGcpFrequency, &cfg->frequency_cfg}, + }; + + return EebusMonitorCreate( + kGcpMonitorFrequency, + kGcpMonitorNameIdMask, + GcpMgcpMeasurementCreate, + params, + ARRAY_SIZE(params) + ); +} diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h b/src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h new file mode 100644 index 0000000..25a5ed7 --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.h @@ -0,0 +1,77 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief GCP MGCP Monitor implementation declarations + * + * Create one monitor per scenario group: + * Scenario 2: GcpMgcpMonitorPowerCreate() + * Scenarios 3+4 (combined): GcpMgcpMonitorEnergyCreate() + * Scenario 5: GcpMgcpMonitorCurrentCreate() + * Scenario 6: GcpMgcpMonitorVoltageCreate() + * Scenario 7: GcpMgcpMonitorFrequencyCreate() + * + * Delete with EebusMonitorDelete(). + */ + +#ifndef SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_MONITOR_H_ +#define SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_MONITOR_H_ + +#include "src/use_case/actor/common/eebus_monitor_base.h" +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * @brief Scenario 2: total active power + */ +typedef struct GcpMgcpMonitorPowerConfig GcpMgcpMonitorPowerConfig; +struct GcpMgcpMonitorPowerConfig { + /** Phases connected to this meter (e.g. kElectricalConnectionPhaseNameTypeAbc for 3-phase) */ + ElectricalConnectionPhaseNameType phases; + GcpMgcpMeasurementConfig power_total_cfg; +}; + +/** + * @brief Scenarios 3 and 4: grid energy (feed-in and/or consumed) + * + * At least one of the two pointers MUST be non-NULL. + */ +typedef struct GcpMgcpMonitorEnergyConfig GcpMgcpMonitorEnergyConfig; +struct GcpMgcpMonitorEnergyConfig { + /** Scenario 3: grid feed-in energy; set NULL if not supported */ + const GcpMgcpMeasurementConfig* energy_feed_in_cfg; + /** Scenario 4: grid consumed energy; set NULL if not supported */ + const GcpMgcpMeasurementConfig* energy_consumed_cfg; +}; + +typedef EebusMonitorCurrentConfig GcpMgcpMonitorCurrentConfig; +typedef EebusMonitorVoltageConfig GcpMgcpMonitorVoltageConfig; +typedef EebusMonitorFrequencyConfig GcpMgcpMonitorFrequencyConfig; + +EebusMonitorObject* GcpMgcpMonitorPowerCreate(const GcpMgcpMonitorPowerConfig* cfg); +EebusMonitorObject* GcpMgcpMonitorEnergyCreate(const GcpMgcpMonitorEnergyConfig* cfg); +EebusMonitorObject* GcpMgcpMonitorCurrentCreate(const GcpMgcpMonitorCurrentConfig* cfg); +EebusMonitorObject* GcpMgcpMonitorVoltageCreate(const GcpMgcpMonitorVoltageConfig* cfg); +EebusMonitorObject* GcpMgcpMonitorFrequencyCreate(const GcpMgcpMonitorFrequencyConfig* cfg); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_GCP_MGCP_GCP_MGCP_MONITOR_H_ diff --git a/src/use_case/actor/gcp/mgcp/gcp_mgcp_public.c b/src/use_case/actor/gcp/mgcp/gcp_mgcp_public.c new file mode 100644 index 0000000..85f0afc --- /dev/null +++ b/src/use_case/actor/gcp/mgcp/gcp_mgcp_public.c @@ -0,0 +1,200 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief GCP MGCP public API implementation + */ + +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp.h" + +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp_internal.h" +#include "src/use_case/specialization/device_configuration/device_configuration_server.h" + +EebusError GcpMgcpGetMeasurementData( + const GcpMgcpUseCaseObject* self, + GcpMeasurementNameId measurement_name, + ScaledValue* measurement_value +) { + if (measurement_value == NULL) { + return kEebusErrorInputArgumentNull; + } + + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + const UseCase* const use_case = USE_CASE(self); + return EebusMonitorContainerGetMeasurementData( + &gcp_mgcp->monitor_container, + use_case->local_entity, + use_case->local_device, + measurement_name, + measurement_value + ); +} + +EebusError GcpMgcpSetMeasurementDataCache( + GcpMgcpUseCaseObject* self, + GcpMeasurementNameId measurement_name, + const ScaledValue* measurement_value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state +) { + if (measurement_value == NULL) { + return kEebusErrorInputArgumentNull; + } + + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + return EebusMonitorContainerSetMeasurementDataCache( + &gcp_mgcp->monitor_container, + measurement_name, + measurement_value, + timestamp, + value_state + ); +} + +EebusError GcpMgcpUpdate(const GcpMgcpUseCaseObject* self) { + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + const UseCase* const use_case = USE_CASE(self); + return EebusMonitorContainerUpdate(&gcp_mgcp->monitor_container, use_case->local_entity, use_case->local_device); +} + +EebusError GcpMgcpSetEnergyFeedInCache( + GcpMgcpUseCaseObject* self, + const ScaledValue* energy_feed_in, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +) { + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + return EebusMonitorContainerSetMeasurementDataCacheWithTime( + &gcp_mgcp->monitor_container, + kGcpEnergyFeedIn, + energy_feed_in, + timestamp, + value_state, + start_time, + end_time + ); +} + +EebusError GcpMgcpSetEnergyConsumedCache( + GcpMgcpUseCaseObject* self, + const ScaledValue* energy_consumed, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time +) { + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + return EebusMonitorContainerSetMeasurementDataCacheWithTime( + &gcp_mgcp->monitor_container, + kGcpEnergyConsumed, + energy_consumed, + timestamp, + value_state, + start_time, + end_time + ); +} + +static EebusError GcpMgcpSetPvCurtailmentLimitFactorInternal(GcpMgcpUseCase* self, const ScaledValue* value) { + const UseCase* const use_case = USE_CASE(self); + + DeviceConfigurationServer dcs = {0}; + + const EebusError err = DeviceConfigurationServerConstruct(&dcs, use_case->local_entity); + if (err != kEebusErrorOk) { + return err; + } + + // clang-format off + const DeviceConfigurationKeyValueDataType data = { + .value = &(DeviceConfigurationKeyValueValueType){ + .scaled_number = &(ScaledNumberType){ + .number = &(int64_t){value->value}, + .scale = &(int8_t){value->scale}, + }, + }, + }; + // clang-format on + + const DeviceConfigurationKeyValueDescriptionDataType filter = { + .key_name = &(DeviceConfigurationKeyNameType){kDeviceConfigurationKeyNameTypePvCurtailmentLimitFactor}, + }; + + return DeviceConfigurationServerUpdateKeyValueWithFilter(&dcs, &data, NULL, &filter); +} + +EebusError GcpMgcpSetPvCurtailmentLimitFactor(GcpMgcpUseCaseObject* self, const ScaledValue* value) { + if (value == NULL) { + return kEebusErrorInputArgumentNull; + } + + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + + if (!gcp_mgcp->has_scenario1) { + return kEebusErrorNotSupported; + } + + const UseCase* const use_case = USE_CASE(self); + + DEVICE_LOCAL_LOCK(use_case->local_device); + const EebusError err = GcpMgcpSetPvCurtailmentLimitFactorInternal(gcp_mgcp, value); + DEVICE_LOCAL_UNLOCK(use_case->local_device); + + return err; +} + +static EebusError GcpMgcpGetPvCurtailmentLimitFactorInternal(const GcpMgcpUseCase* self, ScaledValue* value) { + const UseCase* const use_case = USE_CASE(self); + + DeviceConfigurationServer dcs = {0}; + + const EebusError err = DeviceConfigurationServerConstruct(&dcs, use_case->local_entity); + if (err != kEebusErrorOk) { + return err; + } + + const DeviceConfigurationKeyValueDescriptionDataType filter = { + .key_name = &(DeviceConfigurationKeyNameType){kDeviceConfigurationKeyNameTypePvCurtailmentLimitFactor}, + .value_type = &(DeviceConfigurationKeyValueTypeType){kDeviceConfigurationKeyValueTypeTypeScaledNumber}, + }; + + const DeviceConfigurationKeyValueDataType* const key_value + = DeviceConfigurationCommonGetKeyValueWithFilter(&dcs.device_cfg_common, &filter); + const ScaledNumberType* const scaled_number = DeviceConfigurationKeyValueGetScaledNumber(key_value); + return ScaledValueInitWithScaledNumber(value, scaled_number); +} + +EebusError GcpMgcpGetPvCurtailmentLimitFactor(const GcpMgcpUseCaseObject* self, ScaledValue* value) { + if (value == NULL) { + return kEebusErrorInputArgumentNull; + } + + GcpMgcpUseCase* const gcp_mgcp = GCP_MGCP_USE_CASE(self); + + if (!gcp_mgcp->has_scenario1) { + return kEebusErrorNotSupported; + } + + const UseCase* const use_case = USE_CASE(self); + + DEVICE_LOCAL_LOCK(use_case->local_device); + const EebusError err = GcpMgcpGetPvCurtailmentLimitFactorInternal(gcp_mgcp, value); + DEVICE_LOCAL_UNLOCK(use_case->local_device); + + return err; +} diff --git a/src/use_case/actor/ma/ma_events.c b/src/use_case/actor/ma/ma_events.c new file mode 100644 index 0000000..5c772db --- /dev/null +++ b/src/use_case/actor/ma/ma_events.c @@ -0,0 +1,63 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared MA event handler implementations for MPC and MGCP monitoring use cases + */ + +#include "src/use_case/actor/ma/ma_events.h" + +#include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" +#include "src/use_case/specialization/measurement/measurement_client.h" + +void MaOnEntityAddedHandleElectricalConnection(const UseCase* use_case, EntityRemoteObject* entity) { + ElectricalConnectionClient electrical_connection; + if (ElectricalConnectionClientConstruct(&electrical_connection, use_case->local_entity, entity) != kEebusErrorOk) { + return; + } + + FeatureInfoClient* const feature_info = &electrical_connection.feature_info_client; + if (!HasSubscription(feature_info)) { + Subscribe(feature_info); + } + + ElectricalConnectionClientRequestDescriptions(&electrical_connection, NULL, NULL); + ElectricalConnectionClientRequestParameterDescriptions(&electrical_connection, NULL, NULL); +} + +void MaOnEntityAddedHandleMeasurement(const UseCase* use_case, EntityRemoteObject* entity) { + MeasurementClient measurement; + if (MeasurementClientConstruct(&measurement, use_case->local_entity, entity) != kEebusErrorOk) { + return; + } + + FeatureInfoClient* const feature_info = &measurement.feature_info_client; + if (!HasSubscription(feature_info)) { + Subscribe(feature_info); + } + + MeasurementClientRequestDescriptions(&measurement, NULL, NULL); + MeasurementClientRequestConstraints(&measurement, NULL, NULL); +} + +void MaOnMeasurementDescriptionDataUpdate(const UseCase* use_case, const EventPayload* payload) { + MeasurementClient mcl; + if (MeasurementClientConstruct(&mcl, use_case->local_entity, payload->entity) != kEebusErrorOk) { + return; + } + + MeasurementClientRequestData(&mcl, NULL, NULL); +} diff --git a/src/use_case/actor/ma/ma_events.h b/src/use_case/actor/ma/ma_events.h new file mode 100644 index 0000000..237830a --- /dev/null +++ b/src/use_case/actor/ma/ma_events.h @@ -0,0 +1,40 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared MA event handlers for MPC and MGCP monitoring use cases + */ + +#ifndef SRC_USE_CASE_ACTOR_MA_MA_EVENTS_H_ +#define SRC_USE_CASE_ACTOR_MA_MA_EVENTS_H_ + +#include "src/spine/api/entity_remote_interface.h" +#include "src/spine/events/events.h" +#include "src/use_case/use_case.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void MaOnEntityAddedHandleElectricalConnection(const UseCase* use_case, EntityRemoteObject* entity); +void MaOnEntityAddedHandleMeasurement(const UseCase* use_case, EntityRemoteObject* entity); +void MaOnMeasurementDescriptionDataUpdate(const UseCase* use_case, const EventPayload* payload); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_MA_MA_EVENTS_H_ diff --git a/src/use_case/actor/ma/ma_measurement.c.tmp b/src/use_case/actor/ma/ma_measurement.c.tmp new file mode 100644 index 0000000..6f4a7bf --- /dev/null +++ b/src/use_case/actor/ma/ma_measurement.c.tmp @@ -0,0 +1,3 @@ +MaMeasurement +EebusMeasurementNameId GetName() const +EebusError GetData(EntityLocalObject* local_entity, EntityRemoteObject* remote_entity, ScaledValue* measurement_value) const diff --git a/src/use_case/actor/ma/ma_measurement_base.c b/src/use_case/actor/ma/ma_measurement_base.c new file mode 100644 index 0000000..75564c9 --- /dev/null +++ b/src/use_case/actor/ma/ma_measurement_base.c @@ -0,0 +1,349 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared MA Measurement base implementation for MPC and MGCP monitoring use cases + */ + +#include "src/use_case/actor/ma/ma_measurement_base.h" + +#include "src/common/eebus_arguments.h" + +#define MA_MEASUREMENT_BASE(obj) ((MaMeasurementBase*)(obj)) + +static EebusMeasurementNameId GetName(const MaMeasurementObject* self) { + return MA_MEASUREMENT_BASE(self)->name; +} + +static EebusError GetDataValue( + const MaMeasurementObject* self, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* measurement_value +) { + const MaMeasurementBase* const measurement = MA_MEASUREMENT_BASE(self); + return measurement->get_measurement_strategy(measurement, mcl, eccl, measurement_value); +} + +static EebusError GetData( + const MaMeasurementObject* self, + EntityLocalObject* local_entity, + EntityRemoteObject* remote_entity, + ScaledValue* measurement_value +) { + if ((local_entity == NULL) || (remote_entity == NULL)) { + return kEebusErrorNoChange; + } + + MeasurementClient mcl = {0}; + + EebusError err = MeasurementClientConstruct(&mcl, local_entity, remote_entity); + if (err != kEebusErrorOk) { + return err; + } + + ElectricalConnectionClient ecl = {0}; + + err = ElectricalConnectionClientConstruct(&ecl, local_entity, remote_entity); + if (err != kEebusErrorOk) { + return err; + } + + return GetDataValue(self, &mcl, &ecl, measurement_value); +} + +const MaMeasurementInterface ma_measurement_methods = { + .get_name = GetName, + .get_data = GetData, +}; + +static bool PhasesMatch( + const MaMeasurementBase* measurement, + const ElectricalConnectionPhaseNameType* phases, + const ElectricalConnectionPhaseNameType* in_reference_to +) { + if (measurement->phases != NULL) { + if ((phases == NULL) || (*measurement->phases != *phases)) { + return false; + } + } + + if (measurement->in_reference_to != NULL) { + if ((in_reference_to == NULL) || (*measurement->in_reference_to != *in_reference_to)) { + return false; + } + } + + return true; +} + +static bool MatchesTypeAndScopeAndPhases( + const MaMeasurementBase* measurement, + MeasurementTypeType measurement_type, + ScopeTypeType scope, + const ElectricalConnectionPhaseNameType* phases, + const ElectricalConnectionPhaseNameType* in_reference_to +) { + if ((measurement->measurement_type != measurement_type) || (measurement->scope != scope)) { + return false; + } + + return PhasesMatch(measurement, phases, in_reference_to); +} + +static EebusError GetPhasesWithMeasurementId( + const ElectricalConnectionClient* eccl, + MeasurementIdType measurement_id, + const ElectricalConnectionPhaseNameType** phases, + const ElectricalConnectionPhaseNameType** in_reference_to +) { + if ((phases == NULL) && (in_reference_to == NULL)) { + return kEebusErrorInputArgumentNull; + } + + const ElectricalConnectionParameterDescriptionDataType filter = { + .measurement_id = &measurement_id, + }; + + const ElectricalConnectionParameterDescriptionDataType* const parameter_description + = ElectricalConnectionCommonGetParameterDescriptionWithFilter(&eccl->el_connection_common, &filter); + + if (parameter_description == NULL) { + return kEebusErrorNotAvailable; + } + + *phases = parameter_description->ac_measured_phases; + *in_reference_to = parameter_description->ac_measured_in_reference_to; + return kEebusErrorOk; +} + +static bool CheckPhaseSpecificData( + const MaMeasurementBase* measurement, + const ElectricalConnectionClient* eccl, + const EnergyDirectionType* energy_direction, + const MeasurementDataType* item +) { + if ((item->value == NULL) || (item->value->number == NULL) || (item->measurement_id == NULL)) { + return false; + } + + if (measurement->phases != NULL) { + const ElectricalConnectionPhaseNameType* phases = NULL; + const ElectricalConnectionPhaseNameType* in_reference_to = NULL; + if (GetPhasesWithMeasurementId(eccl, *item->measurement_id, &phases, &in_reference_to) != kEebusErrorOk) { + return false; + } + + if (!PhasesMatch(measurement, phases, in_reference_to)) { + return false; + } + } + + if (energy_direction != NULL) { + const ElectricalConnectionParameterDescriptionDataType filter = { + .measurement_id = item->measurement_id, + }; + + const ElectricalConnectionDescriptionDataType* const description + = ElectricalConnectionCommonGetDescriptionWithParameterDescriptionFilter(&eccl->el_connection_common, &filter); + + if (description == NULL) { + return false; + } + + if ((description->positive_energy_direction == NULL) + || (*description->positive_energy_direction != *energy_direction)) { + return false; + } + } + + if ((item->value_state != NULL) && (*item->value_state != kMeasurementValueStateTypeNormal)) { + return false; + } + + return true; +} + +static EebusError GetPhaseSpecificData( + const MaMeasurementBase* measurement, + const MeasurementClient* mcl, + const ElectricalConnectionClient* eccl, + const EnergyDirectionType* energy_direction, + ScaledValue* value +) { + const MeasurementDescriptionDataType filter = { + .measurement_type = &measurement->measurement_type, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .scope_type = &measurement->scope, + }; + + EebusDataListMatchIterator it = {0}; + MeasurementCommonGetMeasurementDescriptionMatchFirst(&mcl->measurement_common, &filter, &it); + + for (; !EebusDataListMatchIteratorIsDone(&it); EebusDataListMatchIteratorNext(&it)) { + const MeasurementDescriptionDataType* const description = EebusDataListMatchIteratorGet(&it); + + const MeasurementDataType filter2 = { + .measurement_id = description->measurement_id, + }; + + const MeasurementListDataType* const measurement_list = MeasurementCommonGetMeasurements(&mcl->measurement_common); + + EebusDataListMatchIterator it2 = {0}; + HelperListMatchFirst(kFunctionTypeMeasurementListData, measurement_list, &filter2, &it2); + + for (; !EebusDataListMatchIteratorIsDone(&it2); EebusDataListMatchIteratorNext(&it2)) { + const MeasurementDataType* const data = EebusDataListMatchIteratorGet(&it2); + if (CheckPhaseSpecificData(measurement, eccl, energy_direction, data)) { + return ScaledValueInitWithScaledNumber(value, data->value); + } + } + } + + return kEebusErrorNotAvailable; +} + +EebusError MaMeasurementGetPowerStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +) { + static const EnergyDirectionType energy_direction = kEnergyDirectionTypeConsume; + return GetPhaseSpecificData(measurement, mcl, eccl, &energy_direction, value); +} + +EebusError MaMeasurementGetCurrentStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +) { + static const EnergyDirectionType energy_direction = kEnergyDirectionTypeConsume; + return GetPhaseSpecificData(measurement, mcl, eccl, &energy_direction, value); +} + +EebusError MaMeasurementGetEnergyStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +) { + UNUSED(eccl); + + const MeasurementDescriptionDataType filter = { + .measurement_type = &measurement->measurement_type, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .scope_type = &measurement->scope, + }; + + const MeasurementDataType* const measurement_data + = MeasurementCommonGetMeasurementWithFilter(&mcl->measurement_common, &filter); + if (measurement_data == NULL) { + return kEebusErrorNotAvailable; + } + + if ((measurement_data->value_state != NULL) && (*measurement_data->value_state != kMeasurementValueStateTypeNormal)) { + return kEebusErrorInvalid; + } + + return ScaledValueInitWithScaledNumber(value, measurement_data->value); +} + +EebusError MaMeasurementGetVoltageStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +) { + return GetPhaseSpecificData(measurement, mcl, eccl, NULL, value); +} + +EebusError MaMeasurementGetFrequencyStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +) { + UNUSED(measurement); + UNUSED(eccl); + + const MeasurementDescriptionDataType filter = { + .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeFrequency}, + .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, + .scope_type = &(ScopeTypeType){kScopeTypeTypeACFrequency}, + }; + + const MeasurementDataType* const measurement_data + = MeasurementCommonGetMeasurementWithFilter(&mcl->measurement_common, &filter); + + if (measurement_data == NULL) { + return kEebusErrorNotAvailable; + } + + if ((measurement_data->value_state != NULL) && (*measurement_data->value_state != kMeasurementValueStateTypeNormal)) { + return kEebusErrorInvalid; + } + + return ScaledValueInitWithScaledNumber(value, measurement_data->value); +} + +const MaMeasurementObject* MaMeasurementGetInstance( + const MaMeasurementBase* table, + size_t table_size, + const MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + const MeasurementDataType* measurement_data +) { + if ((measurement_data == NULL) || (measurement_data->measurement_id == NULL)) { + return NULL; + } + + const MeasurementDescriptionDataType* const description + = MeasurementCommonGetMeasurementDescriptionWithId(&mcl->measurement_common, *measurement_data->measurement_id); + + if ((description == NULL) || (description->measurement_type == NULL) || (description->scope_type == NULL)) { + return NULL; + } + + const MeasurementTypeType measurement_type = *description->measurement_type; + const ScopeTypeType scope = *description->scope_type; + + const ElectricalConnectionPhaseNameType* phases = NULL; + const ElectricalConnectionPhaseNameType* in_reference_to = NULL; + if (GetPhasesWithMeasurementId(eccl, *measurement_data->measurement_id, &phases, &in_reference_to) != kEebusErrorOk) { + return NULL; + } + + for (size_t i = 0; i < table_size; ++i) { + if (MatchesTypeAndScopeAndPhases(&table[i], measurement_type, scope, phases, in_reference_to)) { + return MA_MEASUREMENT_OBJECT(&table[i]); + } + } + + return NULL; +} + +const MaMeasurementObject* +MaMeasurementGetInstanceWithNameId(const MaMeasurementBase* table, size_t table_size, EebusMeasurementNameId name) { + for (size_t i = 0; i < table_size; ++i) { + if (table[i].name == name) { + return MA_MEASUREMENT_OBJECT(&table[i]); + } + } + + return NULL; +} diff --git a/src/use_case/actor/ma/ma_measurement_base.h b/src/use_case/actor/ma/ma_measurement_base.h new file mode 100644 index 0000000..6d0d021 --- /dev/null +++ b/src/use_case/actor/ma/ma_measurement_base.h @@ -0,0 +1,99 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Shared MA Measurement base for MPC and MGCP monitoring use cases + */ + +#ifndef SRC_USE_CASE_ACTOR_MA_MA_MEASUREMENT_BASE_H_ +#define SRC_USE_CASE_ACTOR_MA_MA_MEASUREMENT_BASE_H_ + +#include + +#include "src/use_case/api/ma_measurement_interface.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct MaMeasurementBase MaMeasurementBase; + +typedef EebusError (*MaGetMeasurementDataStrategy)( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +); + +struct MaMeasurementBase { + MaMeasurementObject obj; + EebusMeasurementNameId name; + MeasurementTypeType measurement_type; + ScopeTypeType scope; + const ElectricalConnectionPhaseNameType* phases; + const ElectricalConnectionPhaseNameType* in_reference_to; + MaGetMeasurementDataStrategy get_measurement_strategy; +}; + +extern const MaMeasurementInterface ma_measurement_methods; + +EebusError MaMeasurementGetPowerStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +); +EebusError MaMeasurementGetCurrentStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +); +EebusError MaMeasurementGetEnergyStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +); +EebusError MaMeasurementGetVoltageStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +); +EebusError MaMeasurementGetFrequencyStrategy( + const MaMeasurementBase* measurement, + MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + ScaledValue* value +); + +const MaMeasurementObject* MaMeasurementGetInstance( + const MaMeasurementBase* table, + size_t table_size, + const MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + const MeasurementDataType* measurement_data +); + +const MaMeasurementObject* +MaMeasurementGetInstanceWithNameId(const MaMeasurementBase* table, size_t table_size, EebusMeasurementNameId name); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_MA_MA_MEASUREMENT_BASE_H_ diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp.c b/src/use_case/actor/ma/mgcp/ma_mgcp.c new file mode 100644 index 0000000..7981563 --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp.c @@ -0,0 +1,166 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Monitoring Appliance MGCP use case implementation + */ + +#include "src/use_case/actor/ma/mgcp/ma_mgcp.h" + +#include + +#include "src/common/array_util.h" +#include "src/spine/entity/entity_local.h" +#include "src/spine/feature/feature_local.h" +#include "src/spine/model/usecase_information_types.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp_events.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp_internal.h" +#include "src/use_case/use_case.h" + +static void MaMgcpUseCaseDestruct(UseCaseObject* self); + +static const UseCaseInterface ma_mgcp_use_case_methods = { + .destruct = MaMgcpUseCaseDestruct, + .is_entity_compatible = UseCaseIsEntityCompatible, + .is_use_case_compatible = UseCaseIsUseCaseCompatible, + .get_remote_entity_with_address = UseCaseGetRemoteEntityWithAddress, +}; + +/* MA accepts remote entities that advertise as GridConnectionPoint actor */ +static const UseCaseActorType valid_actor_types[] = {kUseCaseActorTypeGridConnectionPoint}; + +/* Valid remote entity types for the Grid Connection Point */ +static const EntityTypeType valid_entity_types[] = { + kEntityTypeTypeGridConnectionPointOfPremises, + kEntityTypeTypeCEM, +}; + +static const FeatureTypeType use_case_scenario_features[] = { + kFeatureTypeTypeElectricalConnection, + kFeatureTypeTypeMeasurement, +}; + +static const FeatureTypeType use_case_scenario1_features[] = { + kFeatureTypeTypeDeviceConfiguration, +}; + +static const UseCaseScenario use_case_scenarios[] = { + { + .scenario = (UseCaseScenarioSupportType)1, + .mandatory = false, + .server_features = use_case_scenario1_features, + .server_features_size = ARRAY_SIZE(use_case_scenario1_features), + }, + { + .scenario = (UseCaseScenarioSupportType)2, + .mandatory = true, + .server_features = use_case_scenario_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_features), + }, + { + .scenario = (UseCaseScenarioSupportType)3, + .mandatory = false, + .server_features = use_case_scenario_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_features), + }, + { + .scenario = (UseCaseScenarioSupportType)4, + .mandatory = false, + .server_features = use_case_scenario_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_features), + }, + { + .scenario = (UseCaseScenarioSupportType)5, + .mandatory = false, + .server_features = use_case_scenario_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_features), + }, + { + .scenario = (UseCaseScenarioSupportType)6, + .mandatory = false, + .server_features = use_case_scenario_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_features), + }, + { + .scenario = (UseCaseScenarioSupportType)7, + .mandatory = false, + .server_features = use_case_scenario_features, + .server_features_size = ARRAY_SIZE(use_case_scenario_features), + }, +}; + +static const UseCaseInfo ma_mgcp_use_case_info = { + .valid_actor_types = valid_actor_types, + .valid_actor_types_size = ARRAY_SIZE(valid_actor_types), + .valid_entity_types = valid_entity_types, + .valid_entity_types_size = ARRAY_SIZE(valid_entity_types), + .use_case_scenarios = use_case_scenarios, + .use_case_scenarios_size = ARRAY_SIZE(use_case_scenarios), + .actor = kUseCaseActorTypeMonitoringAppliance, + .use_case_name_id = kUseCaseNameTypeMonitoringOfGridConnectionPoint, + .version = "1.0.0", + .sub_revision = "release", + .available = true, +}; + +static EebusError AddFeatures(UseCaseObject* self, EntityLocalObject* entity); +static EebusError +MaMgcpUseCaseConstruct(MaMgcpUseCase* self, EntityLocalObject* local_entity, MaMgcpListenerObject* ma_mgcp_listener); + +static EebusError AddFeatures(UseCaseObject* self, EntityLocalObject* entity) { + (void)self; + + const FeatureTypeType client_features[] = { + kFeatureTypeTypeElectricalConnection, + kFeatureTypeTypeMeasurement, + kFeatureTypeTypeDeviceConfiguration, + }; + + for (size_t i = 0; i < ARRAY_SIZE(client_features); ++i) { + if (ENTITY_LOCAL_ADD_FEATURE_WITH_TYPE_AND_ROLE(entity, client_features[i], kRoleTypeClient) == NULL) { + return kEebusErrorInit; + } + } + + return kEebusErrorOk; +} + +static EebusError +MaMgcpUseCaseConstruct(MaMgcpUseCase* self, EntityLocalObject* local_entity, MaMgcpListenerObject* ma_mgcp_listener) { + UseCaseConstruct(USE_CASE(self), &ma_mgcp_use_case_info, local_entity, MaMgcpHandleEvent); + USE_CASE_INTERFACE(self) = &ma_mgcp_use_case_methods; + + self->ma_mgcp_listener = ma_mgcp_listener; + return AddFeatures(USE_CASE_OBJECT(self), local_entity); +} + +MaMgcpUseCaseObject* MaMgcpUseCaseCreate(EntityLocalObject* local_entity, MaMgcpListenerObject* ma_mgcp_listener) { + MaMgcpUseCase* const ma_mgcp_use_case = EEBUS_MALLOC(sizeof(*ma_mgcp_use_case)); + if (ma_mgcp_use_case == NULL) { + return NULL; + } + + if (MaMgcpUseCaseConstruct(ma_mgcp_use_case, local_entity, ma_mgcp_listener) != kEebusErrorOk) { + MaMgcpUseCaseDelete(MA_MGCP_USE_CASE_OBJECT(ma_mgcp_use_case)); + return NULL; + } + + return MA_MGCP_USE_CASE_OBJECT(ma_mgcp_use_case); +} + +static void MaMgcpUseCaseDestruct(UseCaseObject* self) { + UseCaseDestruct(self); +} diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp.h b/src/use_case/actor/ma/mgcp/ma_mgcp.h new file mode 100644 index 0000000..52c7827 --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp.h @@ -0,0 +1,100 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Monitoring Appliance MGCP use case (Monitoring of Grid Connection Point) + * + * Scenarios supported: + * Scenario 1 — Read PV feed-in power limitation factor + * Scenario 2 — Monitor momentary power consumption/production + * Scenario 3 — Monitor total feed-in energy + * Scenario 4 — Monitor total consumed energy + * Scenario 5 — Monitor momentary current per phase + * Scenario 6 — Monitor voltage per phase + * Scenario 7 — Monitor frequency + */ + +#ifndef SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_H_ +#define SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_H_ + +#include "src/common/eebus_malloc.h" +#include "src/spine/entity/entity_local.h" +#include "src/use_case/api/ma_mgcp_listener_interface.h" +#include "src/use_case/api/use_case_interface.h" +#include "src/use_case/use_case.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct MaMgcpUseCaseObject MaMgcpUseCaseObject; + +struct MaMgcpUseCaseObject { + /** Inherits the Use Case */ + UseCaseObject obj; +}; + +#define MA_MGCP_USE_CASE_OBJECT(obj) ((MaMgcpUseCaseObject*)(obj)) + +/** + * @brief Create a MA MGCP use case instance + * @param local_entity Pointer to the local entity object + * @param ma_mgcp_listener Pointer to the MA MGCP listener (may be NULL) + * @return Pointer to the created MA MGCP use case instance, or NULL on failure + */ +MaMgcpUseCaseObject* MaMgcpUseCaseCreate(EntityLocalObject* local_entity, MaMgcpListenerObject* ma_mgcp_listener); + +/** + * @brief Delete a MA MGCP use case instance + * @param ma_mgcp_use_case Pointer to the MA MGCP use case instance to delete + */ +static inline void MaMgcpUseCaseDelete(MaMgcpUseCaseObject* ma_mgcp_use_case) { + UseCaseDelete(USE_CASE_OBJECT(ma_mgcp_use_case)); +} + +/** + * @brief Get measurement data for the given measurement name id from the given remote entity + * @param self MA MGCP use case instance + * @param measurement_name_id Measurement to retrieve (see GcpMeasurementNameId) + * @param remote_entity_addr Remote entity address + * @param measurement_value Output buffer for the measurement value + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError MaMgcpGetMeasurementData( + const MaMgcpUseCaseObject* self, + GcpMeasurementNameId measurement_name_id, + const EntityAddressType* remote_entity_addr, + ScaledValue* measurement_value +); + +/** + * @brief Read the PV feed-in power limitation factor from a remote entity (Scenario 1) + * @param self MA MGCP use case instance + * @param remote_entity_addr Remote entity address + * @param value Output buffer for the curtailment factor (0–100 %) + * @return kEebusErrorOk on success, error code otherwise + */ +EebusError MaMgcpGetPvCurtailmentLimitFactor( + const MaMgcpUseCaseObject* self, + const EntityAddressType* remote_entity_addr, + ScaledValue* value +); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_H_ diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp_events.c b/src/use_case/actor/ma/mgcp/ma_mgcp_events.c new file mode 100644 index 0000000..240e696 --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp_events.c @@ -0,0 +1,211 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA MGCP events handling implementation + */ + +#include "src/spine/events/events.h" +#include "src/use_case/actor/ma/ma_events.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp_internal.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp_measurement.h" +#include "src/use_case/specialization/device_configuration/device_configuration_client.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" +#include "src/use_case/specialization/measurement/measurement_client.h" + +static void OnEntityAddedHandleDeviceConfiguration(const MaMgcpUseCase* self, EntityRemoteObject* entity); +static void OnEntityAdded(MaMgcpUseCase* self, const EventPayload* payload); +static void OnEntityRemoved(const MaMgcpUseCase* self, const EventPayload* payload); +static void OnMeasurementDataUpdate(MaMgcpUseCase* self, const EventPayload* payload); +static void OnDeviceConfigurationDescriptionDataUpdate(MaMgcpUseCase* self, const EventPayload* payload); +static void OnDeviceConfigurationDataUpdate(MaMgcpUseCase* self, const EventPayload* payload); +static void OnDataChange(MaMgcpUseCase* self, const EventPayload* payload); + +static void OnEntityAddedHandleDeviceConfiguration(const MaMgcpUseCase* self, EntityRemoteObject* entity) { + const UseCase* const use_case = USE_CASE(self); + + DeviceConfigurationClient device_cfg; + if (DeviceConfigurationClientConstruct(&device_cfg, use_case->local_entity, entity) != kEebusErrorOk) { + return; + } + + FeatureInfoClient* const feature_info = &device_cfg.feature_info_client; + if (!HasSubscription(feature_info)) { + Subscribe(feature_info); + } + + DeviceConfigurationClientRequestKeyValueDescription(&device_cfg, NULL, NULL); +} + +static void OnEntityAdded(MaMgcpUseCase* self, const EventPayload* payload) { + EntityRemoteObject* const entity = payload->entity; + + if (!USE_CASE_IS_USE_CASE_COMPATIBLE(USE_CASE_OBJECT(self), payload->use_case_filter)) { + return; + } + + MaOnEntityAddedHandleElectricalConnection(USE_CASE(self), entity); + MaOnEntityAddedHandleMeasurement(USE_CASE(self), entity); + OnEntityAddedHandleDeviceConfiguration(self, entity); + + if (self->ma_mgcp_listener != NULL) { + const EntityAddressType* const entity_addr = ENTITY_GET_ADDRESS(ENTITY_OBJECT(entity)); + MA_MGCP_LISTENER_ON_REMOTE_ENTITY_CONNECT(self->ma_mgcp_listener, entity_addr); + } +} + +static void OnEntityRemoved(const MaMgcpUseCase* self, const EventPayload* payload) { + EntityRemoteObject* const entity = payload->entity; + + if (!USE_CASE_IS_USE_CASE_COMPATIBLE(USE_CASE_OBJECT(self), payload->use_case_filter)) { + return; + } + + if (self->ma_mgcp_listener != NULL) { + const EntityAddressType* const entity_addr = ENTITY_GET_ADDRESS(ENTITY_OBJECT(entity)); + MA_MGCP_LISTENER_ON_REMOTE_ENTITY_DISCONNECT(self->ma_mgcp_listener, entity_addr); + } +} + +static void OnMeasurementDataUpdate(MaMgcpUseCase* self, const EventPayload* payload) { + const UseCase* const use_case = USE_CASE(self); + + MeasurementClient mcl; + if (MeasurementClientConstruct(&mcl, use_case->local_entity, payload->entity) != kEebusErrorOk) { + return; + } + + ElectricalConnectionClient ecl; + if (ElectricalConnectionClientConstruct(&ecl, use_case->local_entity, payload->entity) != kEebusErrorOk) { + return; + } + + const MeasurementListDataType* const measurement_list = payload->function_data; + if (measurement_list == NULL) { + return; + } + + const EntityAddressType* const entity_addr = ENTITY_GET_ADDRESS(ENTITY_OBJECT(payload->entity)); + + for (size_t i = 0; i < measurement_list->measurement_data_size; ++i) { + const MeasurementDataType* const measurement = measurement_list->measurement_data[i]; + + const MaMeasurementObject* const mgcp_measurement = MaMgcpMeasurementGetInstance(&mcl, &ecl, measurement); + if (mgcp_measurement == NULL) { + continue; + } + + ScaledValue value = {0}; + if (MA_MEASUREMENT_GET_DATA(mgcp_measurement, use_case->local_entity, payload->entity, &value) != kEebusErrorOk) { + continue; + } + + const EebusMeasurementNameId name_id = MA_MEASUREMENT_GET_NAME(mgcp_measurement); + if (self->ma_mgcp_listener != NULL) { + MA_MGCP_LISTENER_ON_MEASUREMENT_RECEIVE(self->ma_mgcp_listener, name_id, &value, entity_addr); + } + } +} + +static void OnDeviceConfigurationDescriptionDataUpdate(MaMgcpUseCase* self, const EventPayload* payload) { + const UseCase* const use_case = USE_CASE(self); + + DeviceConfigurationClient dcc; + if (DeviceConfigurationClientConstruct(&dcc, use_case->local_entity, payload->entity) != kEebusErrorOk) { + return; + } + + DeviceConfigurationClientRequestKeyValue(&dcc, NULL, NULL); +} + +static void OnDeviceConfigurationDataUpdate(MaMgcpUseCase* self, const EventPayload* payload) { + const UseCase* const use_case = USE_CASE(self); + + if (self->ma_mgcp_listener == NULL) { + return; + } + + if (MA_MGCP_LISTENER_INTERFACE(self->ma_mgcp_listener)->on_pv_curtailment_limit_factor_receive == NULL) { + return; + } + + DeviceConfigurationClient dcc; + if (DeviceConfigurationClientConstruct(&dcc, use_case->local_entity, payload->entity) != kEebusErrorOk) { + return; + } + + const DeviceConfigurationKeyValueDescriptionDataType filter = { + .key_name = &(DeviceConfigurationKeyNameType){kDeviceConfigurationKeyNameTypePvCurtailmentLimitFactor}, + }; + + const DeviceConfigurationKeyValueListDataType* const key_value_list = payload->function_data; + if (!DeviceConfigurationCommonCheckKeyValueWithFilter(&dcc.device_cfg_common, key_value_list, &filter)) { + return; + } + + const DeviceConfigurationKeyValueDataType* const key_value + = DeviceConfigurationCommonGetKeyValueWithFilter(&dcc.device_cfg_common, &filter); + + ScaledValue value = {0}; + + const ScaledNumberType* const scaled_number = DeviceConfigurationKeyValueGetScaledNumber(key_value); + if (ScaledValueInitWithScaledNumber(&value, scaled_number) != kEebusErrorOk) { + return; + } + + const EntityAddressType* const entity_addr = ENTITY_GET_ADDRESS(ENTITY_OBJECT(payload->entity)); + MA_MGCP_LISTENER_ON_PV_CURTAILMENT_LIMIT_FACTOR_RECEIVE(self->ma_mgcp_listener, &value, entity_addr); +} + +static void OnDataChange(MaMgcpUseCase* self, const EventPayload* payload) { + switch (payload->function_type) { + case kFunctionTypeMeasurementDescriptionListData: { + MaOnMeasurementDescriptionDataUpdate(USE_CASE(self), payload); + break; + } + case kFunctionTypeMeasurementListData: { + OnMeasurementDataUpdate(self, payload); + break; + } + case kFunctionTypeDeviceConfigurationKeyValueDescriptionListData: { + OnDeviceConfigurationDescriptionDataUpdate(self, payload); + break; + } + case kFunctionTypeDeviceConfigurationKeyValueListData: { + OnDeviceConfigurationDataUpdate(self, payload); + break; + } + default: break; + } +} + +void MaMgcpHandleEvent(const EventPayload* payload, void* ctx) { + MaMgcpUseCase* const ma_mgcp_use_case = (MaMgcpUseCase*)ctx; + + if (!USE_CASE_IS_ENTITY_COMPATIBLE(USE_CASE_OBJECT(ma_mgcp_use_case), payload->entity)) { + return; + } + + if (payload->event_type == kEventTypeUseCaseChange) { + if (payload->change_type == kElementChangeAdd) { + OnEntityAdded(ma_mgcp_use_case, payload); + } else if (payload->change_type == kElementChangeRemove) { + OnEntityRemoved(ma_mgcp_use_case, payload); + } + } else if ((payload->event_type == kEventTypeDataChange) || (payload->change_type == kElementChangeUpdate)) { + OnDataChange(ma_mgcp_use_case, payload); + } +} diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp_events.h b/src/use_case/actor/ma/mgcp/ma_mgcp_events.h new file mode 100644 index 0000000..39ec527 --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp_events.h @@ -0,0 +1,36 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA MGCP events handling + */ + +#ifndef SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_EVENTS_H_ +#define SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_EVENTS_H_ + +#include "src/spine/events/events.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void MaMgcpHandleEvent(const EventPayload* payload, void* ctx); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_EVENTS_H_ diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp_internal.h b/src/use_case/actor/ma/mgcp/ma_mgcp_internal.h new file mode 100644 index 0000000..4cf6c85 --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp_internal.h @@ -0,0 +1,46 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA MGCP use case internal declarations + */ + +#ifndef SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_INTERNAL_H_ +#define SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_INTERNAL_H_ + +#include "src/use_case/api/ma_mgcp_listener_interface.h" +#include "src/use_case/use_case.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct MaMgcpUseCase MaMgcpUseCase; + +struct MaMgcpUseCase { + /** Inherits the Use Case */ + UseCase obj; + + MaMgcpListenerObject* ma_mgcp_listener; +}; + +#define MA_MGCP_USE_CASE(obj) ((MaMgcpUseCase*)(obj)) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_INTERNAL_H_ diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp_listener.c.tmp b/src/use_case/actor/ma/mgcp/ma_mgcp_listener.c.tmp new file mode 100644 index 0000000..977127a --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp_listener.c.tmp @@ -0,0 +1,6 @@ +MaMgcpListener +void Destruct() +void OnRemoteEntityConnect(const EntityAddressType* entity_addr) +void OnRemoteEntityDisconnect(const EntityAddressType* entity_addr) +void OnMeasurementReceive(GcpMeasurementNameId name_id, const ScaledValue* measurement_value, const EntityAddressType* remote_entity_addr) +void OnPvCurtailmentLimitFactorReceive(const ScaledValue* value, const EntityAddressType* remote_entity_addr) diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp_measurement.c b/src/use_case/actor/ma/mgcp/ma_mgcp_measurement.c new file mode 100644 index 0000000..3b84696 --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp_measurement.c @@ -0,0 +1,114 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA MGCP Measurement implementation + */ + +#include "src/use_case/actor/ma/mgcp/ma_mgcp_measurement.h" + +#include "src/common/array_util.h" +#include "src/use_case/actor/ma/ma_measurement_base.h" +#include "src/use_case/model/mgcp_types.h" + +#define MA_MGCP_MEASUREMENT_POWER_TOTAL \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = kGcpPowerTotal, \ + .measurement_type = kMeasurementTypeTypePower, \ + .scope = kScopeTypeTypeACPowerTotal, \ + .phases = NULL, \ + .in_reference_to = NULL, \ + .get_measurement_strategy = MaMeasurementGetPowerStrategy, \ + } + +#define MA_MGCP_MEASUREMENT_ENERGY(name_id, energy_scope) \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = name_id, \ + .measurement_type = kMeasurementTypeTypeEnergy, \ + .scope = energy_scope, \ + .phases = NULL, \ + .in_reference_to = NULL, \ + .get_measurement_strategy = MaMeasurementGetEnergyStrategy, \ + } + +#define MA_MGCP_MEASUREMENT_CURRENT(name_id, phase) \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = name_id, \ + .measurement_type = kMeasurementTypeTypeCurrent, \ + .scope = kScopeTypeTypeACCurrent, \ + .phases = &(ElectricalConnectionPhaseNameType){kElectricalConnectionPhaseNameType##phase}, \ + .in_reference_to = NULL, \ + .get_measurement_strategy = MaMeasurementGetCurrentStrategy, \ + } + +#define MA_MGCP_MEASUREMENT_VOLTAGE(name_id, phase, ref_phase) \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = name_id, \ + .measurement_type = kMeasurementTypeTypeVoltage, \ + .scope = kScopeTypeTypeACVoltage, \ + .phases = &(ElectricalConnectionPhaseNameType){kElectricalConnectionPhaseNameType##phase}, \ + .in_reference_to = &(ElectricalConnectionPhaseNameType){kElectricalConnectionPhaseNameType##ref_phase}, \ + .get_measurement_strategy = MaMeasurementGetVoltageStrategy, \ + } + +#define MA_MGCP_MEASUREMENT_FREQUENCY \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = kGcpFrequency, \ + .measurement_type = kMeasurementTypeTypeFrequency, \ + .scope = kScopeTypeTypeACFrequency, \ + .phases = NULL, \ + .in_reference_to = NULL, \ + .get_measurement_strategy = MaMeasurementGetFrequencyStrategy, \ + } + +static const MaMeasurementBase measurement_table[] = { + /* Scenario 2 */ + MA_MGCP_MEASUREMENT_POWER_TOTAL, + /* Scenario 3 */ + MA_MGCP_MEASUREMENT_ENERGY(kGcpEnergyFeedIn, kScopeTypeTypeGridFeedIn), + /* Scenario 4 */ + MA_MGCP_MEASUREMENT_ENERGY(kGcpEnergyConsumed, kScopeTypeTypeGridConsumption), + /* Scenario 5 */ + MA_MGCP_MEASUREMENT_CURRENT(kGcpCurrentPhaseA, A), + MA_MGCP_MEASUREMENT_CURRENT(kGcpCurrentPhaseB, B), + MA_MGCP_MEASUREMENT_CURRENT(kGcpCurrentPhaseC, C), + /* Scenario 6 */ + MA_MGCP_MEASUREMENT_VOLTAGE(kGcpVoltagePhaseA, A, Neutral), + MA_MGCP_MEASUREMENT_VOLTAGE(kGcpVoltagePhaseB, B, Neutral), + MA_MGCP_MEASUREMENT_VOLTAGE(kGcpVoltagePhaseC, C, Neutral), + MA_MGCP_MEASUREMENT_VOLTAGE(kGcpVoltagePhaseAb, A, B), + MA_MGCP_MEASUREMENT_VOLTAGE(kGcpVoltagePhaseBc, B, C), + MA_MGCP_MEASUREMENT_VOLTAGE(kGcpVoltagePhaseAc, C, A), + /* Scenario 7 */ + MA_MGCP_MEASUREMENT_FREQUENCY, +}; + +const MaMeasurementObject* MaMgcpMeasurementGetInstance( + const MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + const MeasurementDataType* measurement_data +) { + return MaMeasurementGetInstance(measurement_table, ARRAY_SIZE(measurement_table), mcl, eccl, measurement_data); +} + +const MaMeasurementObject* MaMgcpMeasurementGetInstanceWithNameId(EebusMeasurementNameId name) { + return MaMeasurementGetInstanceWithNameId(measurement_table, ARRAY_SIZE(measurement_table), name); +} diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp_measurement.h b/src/use_case/actor/ma/mgcp/ma_mgcp_measurement.h new file mode 100644 index 0000000..71b911f --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp_measurement.h @@ -0,0 +1,44 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA MGCP Measurement implementation declarations + */ + +#ifndef SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_MEASUREMENT_H_ +#define SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_MEASUREMENT_H_ + +#include "src/use_case/api/ma_measurement_interface.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" +#include "src/use_case/specialization/measurement/measurement_client.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +const MaMeasurementObject* MaMgcpMeasurementGetInstance( + const MeasurementClient* mcl, + ElectricalConnectionClient* eccl, + const MeasurementDataType* measurement_data +); + +const MaMeasurementObject* MaMgcpMeasurementGetInstanceWithNameId(EebusMeasurementNameId name); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_MEASUREMENT_H_ diff --git a/src/use_case/actor/ma/mgcp/ma_mgcp_public.c b/src/use_case/actor/ma/mgcp/ma_mgcp_public.c new file mode 100644 index 0000000..fa0b7e4 --- /dev/null +++ b/src/use_case/actor/ma/mgcp/ma_mgcp_public.c @@ -0,0 +1,111 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA MGCP public API implementation + */ + +#include "src/common/eebus_errors.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp_internal.h" +#include "src/use_case/actor/ma/mgcp/ma_mgcp_measurement.h" +#include "src/use_case/specialization/device_configuration/device_configuration_client.h" +#include "src/use_case/use_case.h" + +static EebusError MaMgcpGetPvCurtailmentLimitFactorInternal( + const MaMgcpUseCase* self, + const EntityAddressType* remote_entity_addr, + ScaledValue* value +) { + const UseCase* const use_case = USE_CASE(self); + + EntityRemoteObject* const remote_entity + = USE_CASE_GET_REMOTE_ENTITY_WITH_ADDRESS(USE_CASE_OBJECT(self), remote_entity_addr); + + if (remote_entity == NULL) { + return kEebusErrorNoChange; + } + + DeviceConfigurationClient dcc = {0}; + + const EebusError err = DeviceConfigurationClientConstruct(&dcc, use_case->local_entity, remote_entity); + if (err != kEebusErrorOk) { + return err; + } + + const DeviceConfigurationKeyValueDescriptionDataType filter = { + .key_name = &(DeviceConfigurationKeyNameType){kDeviceConfigurationKeyNameTypePvCurtailmentLimitFactor}, + .value_type = &(DeviceConfigurationKeyValueTypeType){kDeviceConfigurationKeyValueTypeTypeScaledNumber}, + }; + + const DeviceConfigurationKeyValueDataType* const key_value + = DeviceConfigurationCommonGetKeyValueWithFilter(&dcc.device_cfg_common, &filter); + + const ScaledNumberType* const scaled_number = DeviceConfigurationKeyValueGetScaledNumber(key_value); + return ScaledValueInitWithScaledNumber(value, scaled_number); +} + +EebusError MaMgcpGetPvCurtailmentLimitFactor( + const MaMgcpUseCaseObject* self, + const EntityAddressType* remote_entity_addr, + ScaledValue* value +) { + const UseCase* const use_case = USE_CASE(self); + + if (value == NULL) { + return kEebusErrorInputArgumentNull; + } + + EebusError err = kEebusErrorOk; + + DEVICE_LOCAL_LOCK(use_case->local_device); + err = MaMgcpGetPvCurtailmentLimitFactorInternal(MA_MGCP_USE_CASE(self), remote_entity_addr, value); + DEVICE_LOCAL_UNLOCK(use_case->local_device); + + return err; +} + +EebusError MaMgcpGetMeasurementData( + const MaMgcpUseCaseObject* self, + GcpMeasurementNameId measurement_name_id, + const EntityAddressType* remote_entity_addr, + ScaledValue* measurement_value +) { + const UseCase* const use_case = USE_CASE(self); + + if (measurement_value == NULL) { + return kEebusErrorInputArgumentNull; + } + + EebusError err = kEebusErrorOk; + + DEVICE_LOCAL_LOCK(use_case->local_device); + + EntityRemoteObject* const remote_entity + = USE_CASE_GET_REMOTE_ENTITY_WITH_ADDRESS(USE_CASE_OBJECT(use_case), remote_entity_addr); + + const MaMeasurementObject* const measurement = MaMgcpMeasurementGetInstanceWithNameId(measurement_name_id); + + if (measurement != NULL) { + err = MA_MEASUREMENT_GET_DATA(measurement, use_case->local_entity, remote_entity, measurement_value); + } else { + err = kEebusErrorNotSupported; + } + + DEVICE_LOCAL_UNLOCK(use_case->local_device); + + return err; +} diff --git a/src/use_case/actor/ma/mpc/ma_mpc.h b/src/use_case/actor/ma/mpc/ma_mpc.h index 7723306..98fcfcc 100644 --- a/src/use_case/actor/ma/mpc/ma_mpc.h +++ b/src/use_case/actor/ma/mpc/ma_mpc.h @@ -50,10 +50,7 @@ MaMpcUseCaseObject* MaMpcUseCaseCreate(EntityLocalObject* local_entity, MaMpcLis * @param ma_mpc_use_case Pointer to the MA MPC use case instance to delete */ static inline void MaMpcUseCaseDelete(MaMpcUseCaseObject* ma_mpc_use_case) { - if (ma_mpc_use_case != NULL) { - USE_CASE_DESTRUCT(USE_CASE_OBJECT(ma_mpc_use_case)); - EEBUS_FREE(ma_mpc_use_case); - } + UseCaseDelete(USE_CASE_OBJECT(ma_mpc_use_case)); } /** diff --git a/src/use_case/actor/ma/mpc/ma_mpc_events.c b/src/use_case/actor/ma/mpc/ma_mpc_events.c index 74aa977..905935f 100644 --- a/src/use_case/actor/ma/mpc/ma_mpc_events.c +++ b/src/use_case/actor/ma/mpc/ma_mpc_events.c @@ -19,69 +19,26 @@ */ #include "src/spine/events/events.h" +#include "src/use_case/actor/ma/ma_events.h" #include "src/use_case/actor/ma/mpc/ma_mpc_internal.h" #include "src/use_case/actor/ma/mpc/ma_mpc_measurement.h" #include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" #include "src/use_case/specialization/measurement/measurement_client.h" -static void OnEntityAddedHandleElectricalConnection(const MaMpcUseCase* self, EntityRemoteObject* entity); -static void OnEntityAddedHandleMeasurement(const MaMpcUseCase* self, EntityRemoteObject* entity); static void OnEntityAdded(MaMpcUseCase* self, const EventPayload* payload); static void OnEntityRemoved(const MaMpcUseCase* self, const EventPayload* payload); -static void OnMeasurementDescriptionDataUpdate(MaMpcUseCase* self, const EventPayload* payload); static void OnMeasurementDataUpdate(MaMpcUseCase* self, const EventPayload* payload); static void OnDataChange(MaMpcUseCase* self, const EventPayload* payload); -void OnEntityAddedHandleElectricalConnection(const MaMpcUseCase* self, EntityRemoteObject* entity) { - const UseCase* const use_case = USE_CASE(self); - - ElectricalConnectionClient electrical_connection; - if (ElectricalConnectionClientConstruct(&electrical_connection, use_case->local_entity, entity) != kEebusErrorOk) { - return; - } - - FeatureInfoClient* feature_info = &electrical_connection.feature_info_client; - if (!HasSubscription(feature_info)) { - Subscribe(feature_info); - } - - // Get descriptions - ElectricalConnectionClientRequestDescriptions(&electrical_connection, NULL, NULL); - - // Get parameter descriptions - ElectricalConnectionClientRequestParameterDescriptions(&electrical_connection, NULL, NULL); -} - -void OnEntityAddedHandleMeasurement(const MaMpcUseCase* self, EntityRemoteObject* entity) { - const UseCase* const use_case = USE_CASE(self); - - MeasurementClient measurement; - if (MeasurementClientConstruct(&measurement, use_case->local_entity, entity) != kEebusErrorOk) { - return; - } - - FeatureInfoClient* feature_info = &measurement.feature_info_client; - if (!HasSubscription(feature_info)) { - Subscribe(feature_info); - } - - // Get descriptions - MeasurementClientRequestDescriptions(&measurement, NULL, NULL); - - // Get constraints - MeasurementClientRequestConstraints(&measurement, NULL, NULL); -} - -// process required steps when a device is connected -void OnEntityAdded(MaMpcUseCase* self, const EventPayload* payload) { +static void OnEntityAdded(MaMpcUseCase* self, const EventPayload* payload) { EntityRemoteObject* const entity = payload->entity; if (!USE_CASE_IS_USE_CASE_COMPATIBLE(USE_CASE_OBJECT(self), payload->use_case_filter)) { return; } - OnEntityAddedHandleElectricalConnection(self, entity); - OnEntityAddedHandleMeasurement(self, entity); + MaOnEntityAddedHandleElectricalConnection(USE_CASE(self), entity); + MaOnEntityAddedHandleMeasurement(USE_CASE(self), entity); if (self->ma_mpc_listener != NULL) { const EntityAddressType* const entity_addr = ENTITY_GET_ADDRESS(ENTITY_OBJECT(entity)); @@ -89,7 +46,7 @@ void OnEntityAdded(MaMpcUseCase* self, const EventPayload* payload) { } } -void OnEntityRemoved(const MaMpcUseCase* self, const EventPayload* payload) { +static void OnEntityRemoved(const MaMpcUseCase* self, const EventPayload* payload) { EntityRemoteObject* const entity = payload->entity; if (!USE_CASE_IS_USE_CASE_COMPATIBLE(USE_CASE_OBJECT(self), payload->use_case_filter)) { @@ -102,19 +59,7 @@ void OnEntityRemoved(const MaMpcUseCase* self, const EventPayload* payload) { } } -void OnMeasurementDescriptionDataUpdate(MaMpcUseCase* self, const EventPayload* payload) { - const UseCase* const use_case = USE_CASE(self); - - MeasurementClient mcl; - if (MeasurementClientConstruct(&mcl, use_case->local_entity, payload->entity) != kEebusErrorOk) { - return; - } - - // Measurement descriptions received, now get the data - MeasurementClientRequestData(&mcl, NULL, NULL); -} - -void OnMeasurementDataUpdate(MaMpcUseCase* self, const EventPayload* payload) { +static void OnMeasurementDataUpdate(MaMpcUseCase* self, const EventPayload* payload) { const UseCase* const use_case = USE_CASE(self); MeasurementClient mcl; @@ -137,27 +82,27 @@ void OnMeasurementDataUpdate(MaMpcUseCase* self, const EventPayload* payload) { for (size_t i = 0; i < measurement_list->measurement_data_size; ++i) { const MeasurementDataType* const measurement = measurement_list->measurement_data[i]; - const MaMpcMeasurementObject* const mpc_measurement = MaMpcMeasurementGetInstance(&mcl, &ecl, measurement); + const MaMeasurementObject* const mpc_measurement = MaMpcMeasurementGetInstance(&mcl, &ecl, measurement); if (mpc_measurement == NULL) { continue; } ScaledValue value = {0}; - if (MA_MPC_MEASUREMENT_GET_DATA_VALUE(mpc_measurement, &mcl, &ecl, &value) != kEebusErrorOk) { + if (MA_MEASUREMENT_GET_DATA(mpc_measurement, use_case->local_entity, payload->entity, &value) != kEebusErrorOk) { continue; } - const MuMpcMeasurementNameId name_id = MA_MPC_MEASUREMENT_GET_NAME(mpc_measurement); + const EebusMeasurementNameId name_id = MA_MEASUREMENT_GET_NAME(mpc_measurement); if (self->ma_mpc_listener != NULL) { MA_MPC_LISTENER_ON_MEASUREMENT_RECEIVE(self->ma_mpc_listener, name_id, &value, entity_addr); } } } -void OnDataChange(MaMpcUseCase* self, const EventPayload* payload) { +static void OnDataChange(MaMpcUseCase* self, const EventPayload* payload) { switch (payload->function_type) { case kFunctionTypeMeasurementDescriptionListData: { - OnMeasurementDescriptionDataUpdate(self, payload); + MaOnMeasurementDescriptionDataUpdate(USE_CASE(self), payload); break; } diff --git a/src/use_case/actor/ma/mpc/ma_mpc_measurement.c b/src/use_case/actor/ma/mpc/ma_mpc_measurement.c index 71143dd..1be1496 100644 --- a/src/use_case/actor/ma/mpc/ma_mpc_measurement.c +++ b/src/use_case/actor/ma/mpc/ma_mpc_measurement.c @@ -18,160 +18,79 @@ * @brief Ma Mpc Measurement implementation */ +#include "src/use_case/actor/ma/mpc/ma_mpc_measurement.h" + #include "src/common/array_util.h" -#include "src/common/eebus_arguments.h" -#include "src/use_case/api/ma_mpc_measurement_interface.h" +#include "src/use_case/actor/ma/ma_measurement_base.h" #include "src/use_case/model/mpc_types.h" -#include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" -#include "src/use_case/specialization/measurement/measurement_client.h" - -typedef struct MaMpcMeasurement MaMpcMeasurement; - -/** - * @brief MA MPC Measurement is measurement info (including id, phases, value source and constraints) - * for the MA MPC use case scenarios 1-5 - */ -typedef struct MaMpcMeasurement MaMpcMeasurement; - -/** - * @brief MA MPC Measurement Read Strategy - */ -typedef EebusError (*GetMeasurementDataStrategy)( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -); - -struct MaMpcMeasurement { - /** Implements the MA MPC Measurement Interface */ - MaMpcMeasurementObject obj; - - MuMpcMeasurementNameId name; - /** Measurement Type */ - MeasurementTypeType measurement_type; - /** Measurement Object (total or per phase) */ - ScopeTypeType scope; - /** Measured phases for total or single one for per phase */ - const ElectricalConnectionPhaseNameType* phases; - /** In case of per phase measurement, the phase the measurement refers to */ - const ElectricalConnectionPhaseNameType* in_reference_to; - /** The strategy to configure the measurement scope */ - GetMeasurementDataStrategy get_measurement_strategy; -}; - -#define MA_MPC_MEASUREMENT(obj) ((MaMpcMeasurement*)(obj)) - -static MuMpcMeasurementNameId GetName(const MaMpcMeasurementObject* self); -static EebusError GetDataValue( - const MaMpcMeasurementObject* self, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* measurement_value -); - -static const MaMpcMeasurementInterface ma_mpc_measurement_scope_methods = { - .get_name = GetName, - .get_data_value = GetDataValue, -}; - -static EebusError GetPowerStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -); -static EebusError GetCurrentStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -); -static EebusError GetEnergyStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -); -static EebusError GetVoltageStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -); -static EebusError GetFrequencyStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -); -#define MA_MPC_MEASUREMENT_POWER_TOTAL \ - { \ - .obj = {.interface_ = &ma_mpc_measurement_scope_methods}, \ - .name = kMpcPowerTotal, \ - .measurement_type = kMeasurementTypeTypePower, \ - .scope = kScopeTypeTypeACPowerTotal, \ - .phases = NULL, \ - .in_reference_to = NULL, \ - .get_measurement_strategy = GetPowerStrategy, \ +#define MA_MPC_MEASUREMENT_POWER_TOTAL \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = kMpcPowerTotal, \ + .measurement_type = kMeasurementTypeTypePower, \ + .scope = kScopeTypeTypeACPowerTotal, \ + .phases = NULL, \ + .in_reference_to = NULL, \ + .get_measurement_strategy = MaMeasurementGetPowerStrategy, \ } #define MA_MPC_MEASUREMENT_POWER(name_id, phase) \ { \ - .obj = {.interface_ = &ma_mpc_measurement_scope_methods}, \ + .obj = {.interface_ = &ma_measurement_methods}, \ .name = name_id, \ .measurement_type = kMeasurementTypeTypePower, \ .scope = kScopeTypeTypeACPower, \ .phases = &(ElectricalConnectionPhaseNameType){kElectricalConnectionPhaseNameType##phase}, \ .in_reference_to = NULL, \ - .get_measurement_strategy = GetPowerStrategy, \ + .get_measurement_strategy = MaMeasurementGetPowerStrategy, \ } -#define MA_MPC_MEASUREMENT_ENERGY(name_id, energy_scope) \ - { \ - .obj = {.interface_ = &ma_mpc_measurement_scope_methods}, \ - .name = name_id, \ - .measurement_type = kMeasurementTypeTypeEnergy, \ - .scope = energy_scope, \ - .phases = NULL, \ - .in_reference_to = NULL, \ - .get_measurement_strategy = GetEnergyStrategy, \ +#define MA_MPC_MEASUREMENT_ENERGY(name_id, energy_scope) \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = name_id, \ + .measurement_type = kMeasurementTypeTypeEnergy, \ + .scope = energy_scope, \ + .phases = NULL, \ + .in_reference_to = NULL, \ + .get_measurement_strategy = MaMeasurementGetEnergyStrategy, \ } #define MA_MPC_MEASUREMENT_CURRENT(name_id, phase) \ { \ - .obj = {.interface_ = &ma_mpc_measurement_scope_methods}, \ + .obj = {.interface_ = &ma_measurement_methods}, \ .name = name_id, \ .measurement_type = kMeasurementTypeTypeCurrent, \ .scope = kScopeTypeTypeACCurrent, \ .phases = &(ElectricalConnectionPhaseNameType){kElectricalConnectionPhaseNameType##phase}, \ .in_reference_to = NULL, \ - .get_measurement_strategy = GetCurrentStrategy, \ + .get_measurement_strategy = MaMeasurementGetCurrentStrategy, \ } #define MA_MPC_MEASUREMENT_VOLTAGE(name_id, phase, ref_phase) \ { \ - .obj = {.interface_ = &ma_mpc_measurement_scope_methods}, \ + .obj = {.interface_ = &ma_measurement_methods}, \ .name = name_id, \ .measurement_type = kMeasurementTypeTypeVoltage, \ .scope = kScopeTypeTypeACVoltage, \ .phases = &(ElectricalConnectionPhaseNameType){kElectricalConnectionPhaseNameType##phase}, \ .in_reference_to = &(ElectricalConnectionPhaseNameType){kElectricalConnectionPhaseNameType##ref_phase}, \ - .get_measurement_strategy = GetVoltageStrategy, \ + .get_measurement_strategy = MaMeasurementGetVoltageStrategy, \ } -#define MA_MPC_MEASUREMENT_FREQUENCY \ - { \ - .obj = {.interface_ = &ma_mpc_measurement_scope_methods}, \ - .name = kMpcFrequency, \ - .measurement_type = kMeasurementTypeTypeFrequency, \ - .scope = kScopeTypeTypeACFrequency, \ - .phases = NULL, \ - .get_measurement_strategy = GetFrequencyStrategy, \ +#define MA_MPC_MEASUREMENT_FREQUENCY \ + { \ + .obj = {.interface_ = &ma_measurement_methods}, \ + .name = kMpcFrequency, \ + .measurement_type = kMeasurementTypeTypeFrequency, \ + .scope = kScopeTypeTypeACFrequency, \ + .phases = NULL, \ + .in_reference_to = NULL, \ + .get_measurement_strategy = MaMeasurementGetFrequencyStrategy, \ } -static const MaMpcMeasurement measurement_table[] = { +static const MaMeasurementBase measurement_table[] = { MA_MPC_MEASUREMENT_POWER_TOTAL, MA_MPC_MEASUREMENT_POWER(kMpcPowerPhaseA, A), MA_MPC_MEASUREMENT_POWER(kMpcPowerPhaseB, B), @@ -190,315 +109,14 @@ static const MaMpcMeasurement measurement_table[] = { MA_MPC_MEASUREMENT_FREQUENCY, }; -const MaMpcMeasurementObject* MaMpcMeasurementGetInstanceWithNameId(MuMpcMeasurementNameId name) { - for (size_t i = 0; i < ARRAY_SIZE(measurement_table); ++i) { - if (measurement_table[i].name == name) { - return MA_MPC_MEASUREMENT_OBJECT(&measurement_table[i]); - } - } - - return NULL; -} - -bool MaMpcMeasurementPhasesMatch( - const MaMpcMeasurement* measurement, - const ElectricalConnectionPhaseNameType* phases, - const ElectricalConnectionPhaseNameType* in_reference_to -) { - // Note: NULL phases in measurement means "don't care" - if (measurement->phases != NULL) { - if (phases == NULL || (*measurement->phases != *phases)) { - return false; - } - } - - // Note: NULL in_reference_to in measurement means "don't care" - if (measurement->in_reference_to != NULL) { - if (in_reference_to == NULL || (*measurement->in_reference_to != *in_reference_to)) { - return false; - } - } - - return true; -} - -bool MaMpcMeasurementMatchesTypeAndScopeAndPhases( - const MaMpcMeasurement* measurement, - MeasurementTypeType measurement_type, - ScopeTypeType scope, - const ElectricalConnectionPhaseNameType* phases, - const ElectricalConnectionPhaseNameType* in_reference_to -) { - if ((measurement->measurement_type != measurement_type) || (measurement->scope != scope)) { - return false; - } - - return MaMpcMeasurementPhasesMatch(measurement, phases, in_reference_to); -} - -EebusError GetPhasesWithMeasurementId( - const ElectricalConnectionClient* eccl, - MeasurementIdType measurement_id, - const ElectricalConnectionPhaseNameType** const phases, - const ElectricalConnectionPhaseNameType** const in_reference_to -) { - if ((phases == NULL) && (in_reference_to == NULL)) { - return kEebusErrorInputArgumentNull; - } - - const ElectricalConnectionParameterDescriptionDataType filter = { - .measurement_id = &measurement_id, - }; - - const ElectricalConnectionParameterDescriptionDataType* const parameter_description - = ElectricalConnectionCommonGetParameterDescriptionWithFilter(&eccl->el_connection_common, &filter); - - if (parameter_description == NULL) { - return kEebusErrorNotAvailable; - } - - *phases = parameter_description->ac_measured_phases; - *in_reference_to = parameter_description->ac_measured_in_reference_to; - return kEebusErrorOk; -} - -const MaMpcMeasurement* MaMpcMeasurementGetInstance( +const MaMeasurementObject* MaMpcMeasurementGetInstance( const MeasurementClient* mcl, ElectricalConnectionClient* eccl, const MeasurementDataType* measurement_data ) { - if ((measurement_data == NULL) || (measurement_data->measurement_id == NULL)) { - return NULL; - } - - const MeasurementDescriptionDataType* description - = MeasurementCommonGetMeasurementDescriptionWithId(&mcl->measurement_common, *measurement_data->measurement_id); - - if ((description == NULL) || (description->measurement_type == NULL) || (description->scope_type == NULL)) { - return NULL; - } - - const MeasurementTypeType measurement_type = *description->measurement_type; - - const ScopeTypeType scope = *description->scope_type; - - const ElectricalConnectionPhaseNameType* phases = NULL; - const ElectricalConnectionPhaseNameType* in_reference_to = NULL; - - EebusError error = GetPhasesWithMeasurementId(eccl, *measurement_data->measurement_id, &phases, &in_reference_to); - if (error != kEebusErrorOk) { - return NULL; - } - - for (size_t i = 0; i < ARRAY_SIZE(measurement_table); ++i) { - if (MaMpcMeasurementMatchesTypeAndScopeAndPhases( - &measurement_table[i], - measurement_type, - scope, - phases, - in_reference_to - )) { - return &measurement_table[i]; - } - } - - return NULL; + return MaMeasurementGetInstance(measurement_table, ARRAY_SIZE(measurement_table), mcl, eccl, measurement_data); } -bool CheckPhaseSpecificData( - const MaMpcMeasurement* measurement, - const MeasurementClient* mcl, - const ElectricalConnectionClient* eccl, - const EnergyDirectionType* energy_direction, - const MeasurementDataType* item -) { - UNUSED(mcl); - - if ((item->value == NULL) || (item->value->number == NULL) || (item->measurement_id == NULL)) { - return false; - } - - if (measurement->phases != NULL) { - const ElectricalConnectionPhaseNameType* phases = NULL; - const ElectricalConnectionPhaseNameType* in_reference_to = NULL; - if (GetPhasesWithMeasurementId(eccl, *item->measurement_id, &phases, &in_reference_to) != kEebusErrorOk) { - return false; - } - - if (!MaMpcMeasurementPhasesMatch(measurement, phases, in_reference_to)) { - return false; - } - } - - if (energy_direction != NULL) { - const ElectricalConnectionParameterDescriptionDataType filter = { - .measurement_id = item->measurement_id, - }; - - const ElectricalConnectionDescriptionDataType* description - = ElectricalConnectionCommonGetDescriptionWithParameterDescriptionFilter(&eccl->el_connection_common, &filter); - - if (description == NULL) { - return false; - } - - // If energy direction doesn't match - if ((description->positive_energy_direction == NULL) - || (*description->positive_energy_direction != *energy_direction)) { - return false; - } - } - - // If the value state is set and not normal, the value is not valid and should be ignored - // therefore we return an error - if ((item->value_state != NULL) && (*item->value_state != kMeasurementValueStateTypeNormal)) { - return false; - } - - return true; -} - -EebusError GetPhaseSpecificData( - const MaMpcMeasurement* measurement, - const MeasurementClient* mcl, - const ElectricalConnectionClient* eccl, - const EnergyDirectionType* energy_direction, - ScaledValue* value -) { - const MeasurementDescriptionDataType filter = { - .measurement_type = &measurement->measurement_type, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .scope_type = &measurement->scope, - }; - - EebusDataListMatchIterator it = {0}; - MeasurementCommonGetMeasurementDescriptionMatchFirst(&mcl->measurement_common, &filter, &it); - - for (; !EebusDataListMatchIteratorIsDone(&it); EebusDataListMatchIteratorNext(&it)) { - const MeasurementDescriptionDataType* description = EebusDataListMatchIteratorGet(&it); - - const MeasurementDataType filter2 = { - .measurement_id = description->measurement_id, - }; - - const MeasurementListDataType* const measurement_list = MeasurementCommonGetMeasurements(&mcl->measurement_common); - - EebusDataListMatchIterator it2 = {0}; - HelperListMatchFirst(kFunctionTypeMeasurementListData, measurement_list, &filter2, &it2); - - for (; !EebusDataListMatchIteratorIsDone(&it2); EebusDataListMatchIteratorNext(&it2)) { - const MeasurementDataType* const measurement_data = EebusDataListMatchIteratorGet(&it2); - if (CheckPhaseSpecificData(measurement, mcl, eccl, energy_direction, measurement_data)) { - return ScaledValueInitWithScaledNumber(value, measurement_data->value); - } - } - } - - return kEebusErrorNotAvailable; -} - -EebusError GetPowerStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -) { - static const EnergyDirectionType energy_direction = kEnergyDirectionTypeConsume; - return GetPhaseSpecificData(measurement, mcl, eccl, &energy_direction, value); -} - -EebusError GetCurrentStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -) { - static const EnergyDirectionType energy_direction = kEnergyDirectionTypeConsume; - return GetPhaseSpecificData(measurement, mcl, eccl, &energy_direction, value); -} - -EebusError GetEnergyStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -) { - UNUSED(eccl); - - const MeasurementDescriptionDataType filter = { - .measurement_type = &measurement->measurement_type, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .scope_type = &measurement->scope, - }; - - // Assume there is only one unique result - const MeasurementDataType* const measurement_data - = MeasurementCommonGetMeasurementWithFilter(&mcl->measurement_common, &filter); - if (measurement_data == NULL) { - return kEebusErrorNotAvailable; - } - - // If the value state is set and not normal, the value is not valid and should be ignored - // therefore we return an error - if ((measurement_data->value_state != NULL) && (*measurement_data->value_state != kMeasurementValueStateTypeNormal)) { - return kEebusErrorInvalid; - } - - return ScaledValueInitWithScaledNumber(value, measurement_data->value); -} - -EebusError GetVoltageStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -) { - return GetPhaseSpecificData(measurement, mcl, eccl, NULL, value); -} - -EebusError GetFrequencyStrategy( - const MaMpcMeasurement* measurement, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* value -) { - UNUSED(measurement); - UNUSED(eccl); - - const MeasurementDescriptionDataType filter = { - .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeFrequency}, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .scope_type = &(ScopeTypeType){kScopeTypeTypeACFrequency}, - }; - - const MeasurementDataType* const measurement_data - = MeasurementCommonGetMeasurementWithFilter(&mcl->measurement_common, &filter); - - if (measurement_data == NULL) { - return kEebusErrorNotAvailable; - } - - // If the value state is set and not normal, - // the value is not valid and should be ignored therefore an error is returned - if ((measurement_data->value_state != NULL) && (*measurement_data->value_state != kMeasurementValueStateTypeNormal)) { - return kEebusErrorInvalid; - } - - return ScaledValueInitWithScaledNumber(value, measurement_data->value); -} - -MuMpcMeasurementNameId GetName(const MaMpcMeasurementObject* self) { - const MaMpcMeasurement* const measurement = MA_MPC_MEASUREMENT(self); - return measurement->name; -} - -EebusError GetDataValue( - const MaMpcMeasurementObject* self, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* measurement_value -) { - const MaMpcMeasurement* const measurement = MA_MPC_MEASUREMENT(self); - - return measurement->get_measurement_strategy(measurement, mcl, eccl, measurement_value); +const MaMeasurementObject* MaMpcMeasurementGetInstanceWithNameId(EebusMeasurementNameId name) { + return MaMeasurementGetInstanceWithNameId(measurement_table, ARRAY_SIZE(measurement_table), name); } diff --git a/src/use_case/actor/ma/mpc/ma_mpc_measurement.c.tmp b/src/use_case/actor/ma/mpc/ma_mpc_measurement.c.tmp deleted file mode 100644 index 6e20b4c..0000000 --- a/src/use_case/actor/ma/mpc/ma_mpc_measurement.c.tmp +++ /dev/null @@ -1,2 +0,0 @@ -MaMpcMeasurement -EebusError GetDataValue(const MeasurementClient* mcl, ElectricalConnectionClient* eccl, ScaledValue* measurement_value) diff --git a/src/use_case/actor/ma/mpc/ma_mpc_measurement.h b/src/use_case/actor/ma/mpc/ma_mpc_measurement.h index 650a39e..2ab9ee2 100644 --- a/src/use_case/actor/ma/mpc/ma_mpc_measurement.h +++ b/src/use_case/actor/ma/mpc/ma_mpc_measurement.h @@ -21,8 +21,7 @@ #ifndef SRC_USE_CASE_ACTOR_MA_MPC_MA_MPC_MEASUREMENT_H_ #define SRC_USE_CASE_ACTOR_MA_MPC_MA_MPC_MEASUREMENT_H_ -#include "src/use_case/api/ma_mpc_measurement_interface.h" -#include "src/use_case/model/mpc_types.h" +#include "src/use_case/api/ma_measurement_interface.h" #include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" #include "src/use_case/specialization/measurement/measurement_client.h" @@ -30,25 +29,13 @@ extern "C" { #endif // __cplusplus -/** - * @brief Search for a MA MPC Measurement instance matching the given measurement data - * @param mcl Measurement Client instance - * @param eccl Electrical Connection Client instance - * @param measurement_data Measurement Data to search for matching the key fields - * @return MA MPC Measurement instance or NULL if not found - */ -const MaMpcMeasurementObject* MaMpcMeasurementGetInstance( +const MaMeasurementObject* MaMpcMeasurementGetInstance( const MeasurementClient* mcl, ElectricalConnectionClient* eccl, const MeasurementDataType* measurement_data ); -/** - * @brief Search for a MA MPC Measurement instance matching the given name id - * @param name Measurement Name Id to search for - * @return MA MPC Measurement instance or NULL if not found - */ -const MaMpcMeasurementObject* MaMpcMeasurementGetInstanceWithNameId(MuMpcMeasurementNameId name); +const MaMeasurementObject* MaMpcMeasurementGetInstanceWithNameId(EebusMeasurementNameId name); #ifdef __cplusplus } diff --git a/src/use_case/actor/ma/mpc/ma_mpc_public.c b/src/use_case/actor/ma/mpc/ma_mpc_public.c index 2e6f229..8409615 100644 --- a/src/use_case/actor/ma/mpc/ma_mpc_public.c +++ b/src/use_case/actor/ma/mpc/ma_mpc_public.c @@ -21,48 +21,9 @@ #include "src/common/eebus_errors.h" #include "src/use_case/actor/ma/mpc/ma_mpc.h" -#include "src/use_case/actor/ma/mpc/ma_mpc_internal.h" #include "src/use_case/actor/ma/mpc/ma_mpc_measurement.h" #include "src/use_case/use_case.h" -EebusError MaMpcGetMeasurementDataInternal( - const MaMpcUseCase* self, - MuMpcMeasurementNameId measurement_name_id, - const EntityAddressType* remote_entity_addr, - ScaledValue* measurement_value -) { - const UseCase* const use_case = USE_CASE(self); - - EntityRemoteObject* const remote_entity - = USE_CASE_GET_REMOTE_ENTITY_WITH_ADDRESS(USE_CASE_OBJECT(self), remote_entity_addr); - - if (remote_entity == NULL) { - return kEebusErrorNoChange; - } - - MeasurementClient mcl = {0}; - - EebusError err = MeasurementClientConstruct(&mcl, use_case->local_entity, remote_entity); - if (err != kEebusErrorOk) { - return err; - } - - ElectricalConnectionClient ecl = {0}; - - err = ElectricalConnectionClientConstruct(&ecl, use_case->local_entity, remote_entity); - if (err != kEebusErrorOk) { - return err; - } - - const MaMpcMeasurementObject* const measurement = MaMpcMeasurementGetInstanceWithNameId(measurement_name_id); - - if (measurement == NULL) { - return kEebusErrorNotSupported; - } - - return MA_MPC_MEASUREMENT_GET_DATA_VALUE(measurement, &mcl, &ecl, measurement_value); -} - EebusError MaMpcGetMeasurementData( const MaMpcUseCaseObject* self, MuMpcMeasurementNameId measurement_name_id, @@ -79,12 +40,16 @@ EebusError MaMpcGetMeasurementData( DEVICE_LOCAL_LOCK(use_case->local_device); - err = MaMpcGetMeasurementDataInternal( - MA_MPC_USE_CASE(self), - measurement_name_id, - remote_entity_addr, - measurement_value - ); + EntityRemoteObject* const remote_entity + = USE_CASE_GET_REMOTE_ENTITY_WITH_ADDRESS(USE_CASE_OBJECT(use_case), remote_entity_addr); + + const MaMeasurementObject* const measurement = MaMpcMeasurementGetInstanceWithNameId(measurement_name_id); + + if (measurement != NULL) { + err = MA_MEASUREMENT_GET_DATA(measurement, use_case->local_entity, remote_entity, measurement_value); + } else { + err = kEebusErrorNotSupported; + } DEVICE_LOCAL_UNLOCK(use_case->local_device); diff --git a/src/use_case/actor/mu/mpc/mu_mpc.c b/src/use_case/actor/mu/mpc/mu_mpc.c index 8755c00..74ccec7 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc.c +++ b/src/use_case/actor/mu/mpc/mu_mpc.c @@ -18,13 +18,11 @@ #include "src/common/array_util.h" #include "src/common/eebus_arguments.h" -#include "src/common/eebus_mutex/eebus_mutex.h" +#include "src/use_case/actor/common/eebus_monitor_features.h" #include "src/use_case/actor/mu/mpc/mu_mpc_internal.h" #include "src/use_case/actor/mu/mpc/mu_mpc_measurement.h" #include "src/use_case/actor/mu/mpc/mu_mpc_monitor.h" #include "src/use_case/model/load_limit_types.h" -#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" -#include "src/use_case/specialization/measurement/measurement_server.h" #include "src/use_case/use_case.h" static const UseCaseActorType valid_actor_types[] = {kUseCaseActorTypeMonitoringAppliance}; @@ -46,15 +44,13 @@ static EebusError MuMpcUseCaseConstruct( const MuMpcConfig* cfg ); -static void AddFeatures(UseCaseObject* self, EntityLocalObject* entity); - EebusError AddMuMpcScenario1(MuMpcUseCase* self, const MuMpcMonitorPowerConfig* power_cfg) { - MuMpcMonitorObject* const power_monitor = MuMpcMonitorPowerCreate(power_cfg); + EebusMonitorObject* const power_monitor = MuMpcMonitorPowerCreate(power_cfg); if (power_monitor == NULL) { return kEebusErrorInit; } - VectorPushBack(&self->monitors, power_monitor); + EebusMonitorContainerAdd(&self->monitor_container, power_monitor); static const FeatureTypeType use_case_scenario_support_1_features[] = { kFeatureTypeTypeElectricalConnection, @@ -72,12 +68,12 @@ EebusError AddMuMpcScenario1(MuMpcUseCase* self, const MuMpcMonitorPowerConfig* } EebusError AddMuMpcScenario2(MuMpcUseCase* self, const MuMpcMonitorEnergyConfig* energy_cfg) { - MuMpcMonitorObject* const energy_monitor = MuMpcMonitorEnergyCreate(energy_cfg); + EebusMonitorObject* const energy_monitor = MuMpcMonitorEnergyCreate(energy_cfg); if (energy_monitor == NULL) { return kEebusErrorInit; } - VectorPushBack(&self->monitors, energy_monitor); + EebusMonitorContainerAdd(&self->monitor_container, energy_monitor); static const FeatureTypeType use_case_scenario_support_2_features[] = { kFeatureTypeTypeElectricalConnection, @@ -95,12 +91,12 @@ EebusError AddMuMpcScenario2(MuMpcUseCase* self, const MuMpcMonitorEnergyConfig* } EebusError AddMuMpcScenario3(MuMpcUseCase* self, const MuMpcMonitorCurrentConfig* current_cfg) { - MuMpcMonitorObject* const current_monitor = MuMpcMonitorCurrentCreate(current_cfg); + EebusMonitorObject* const current_monitor = MuMpcMonitorCurrentCreate(current_cfg); if (current_monitor == NULL) { return kEebusErrorInit; } - VectorPushBack(&self->monitors, current_monitor); + EebusMonitorContainerAdd(&self->monitor_container, current_monitor); static const FeatureTypeType use_case_scenario_support_3_features[] = { kFeatureTypeTypeElectricalConnection, @@ -118,12 +114,12 @@ EebusError AddMuMpcScenario3(MuMpcUseCase* self, const MuMpcMonitorCurrentConfig } EebusError AddMuMpcScenario4(MuMpcUseCase* self, const MuMpcMonitorVoltageConfig* voltage_cfg) { - MuMpcMonitorObject* const voltage_monitor = MuMpcMonitorVoltageCreate(voltage_cfg); + EebusMonitorObject* const voltage_monitor = MuMpcMonitorVoltageCreate(voltage_cfg); if (voltage_monitor == NULL) { return kEebusErrorInit; } - VectorPushBack(&self->monitors, voltage_monitor); + EebusMonitorContainerAdd(&self->monitor_container, voltage_monitor); static const FeatureTypeType use_case_scenario_support_4_features[] = { kFeatureTypeTypeElectricalConnection, @@ -141,12 +137,12 @@ EebusError AddMuMpcScenario4(MuMpcUseCase* self, const MuMpcMonitorVoltageConfig } EebusError AddMuMpcScenario5(MuMpcUseCase* self, const MuMpcMonitorFrequencyConfig* frequency_cfg) { - MuMpcMonitorObject* const frequency_monitor = MuMpcMonitorFrequencyCreate(frequency_cfg); + EebusMonitorObject* const frequency_monitor = MuMpcMonitorFrequencyCreate(frequency_cfg); if (frequency_monitor == NULL) { return kEebusErrorInit; } - VectorPushBack(&self->monitors, frequency_monitor); + EebusMonitorContainerAdd(&self->monitor_container, frequency_monitor); static const FeatureTypeType use_case_scenario_support_5_features[] = { kFeatureTypeTypeElectricalConnection, @@ -163,80 +159,6 @@ EebusError AddMuMpcScenario5(MuMpcUseCase* self, const MuMpcMonitorFrequencyConf return kEebusErrorOk; } -void AddFeatures(UseCaseObject* self, EntityLocalObject* entity) { - MuMpcUseCase* const mu_mpc = MU_MPC_USE_CASE(self); - - // Server features - // Electrical connection feature - FeatureLocalObject* const ecfl - = ENTITY_LOCAL_ADD_FEATURE_WITH_TYPE_AND_ROLE(entity, kFeatureTypeTypeElectricalConnection, kRoleTypeServer); - - FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(ecfl, kFunctionTypeElectricalConnectionDescriptionListData, true, false); - FEATURE_LOCAL_SET_FUNCTION_OPERATIONS( - ecfl, - kFunctionTypeElectricalConnectionParameterDescriptionListData, - true, - false - ); - - // Measurement feature - FeatureLocalObject* const mfl - = ENTITY_LOCAL_ADD_FEATURE_WITH_TYPE_AND_ROLE(entity, kFeatureTypeTypeMeasurement, kRoleTypeServer); - FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(mfl, kFunctionTypeMeasurementDescriptionListData, true, false); - FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(mfl, kFunctionTypeMeasurementListData, true, false); - - MeasurementServer msrv; - if (MeasurementServerConstruct(&msrv, entity) != kEebusErrorOk) { - return; - } - - // Electrical connection feature - ElectricalConnectionServer ecsrv; - if (ElectricalConnectionServerConstruct(&ecsrv, entity) != kEebusErrorOk) { - return; - } - - const ElectricalConnectionIdType ec_id = mu_mpc->electrical_connection_id; - if (ElectricalConnectionCommonGetDescriptionWithId(&ecsrv.el_connection_common, ec_id) == NULL) { - const ElectricalConnectionDescriptionDataType ec_description = { - .power_supply_type = &(ElectricalConnectionVoltageTypeType){kElectricalConnectionVoltageTypeTypeAc}, - .positive_energy_direction = &(EnergyDirectionType){kEnergyDirectionTypeConsume}, - }; - - EebusError err = ElectricalConnectionServerAddDescriptionWithId(&ecsrv, &ec_description, ec_id); - - if (err != kEebusErrorOk) { - return; - } - } - - MeasurementConstraintsListDataType* const measurement_constraints = MeasurementConstraintsCreateEmpty(); - if (measurement_constraints == NULL) { - return; - } - - for (size_t i = 0; i < VectorGetSize(&mu_mpc->monitors); ++i) { - MuMpcMonitorObject* const mu_mpc_monitor = (MuMpcMonitorObject*)VectorGetElement(&mu_mpc->monitors, i); - - EebusError err = MU_MPC_MONITOR_CONFIGURE(mu_mpc_monitor, &msrv, &ecsrv, ec_id, measurement_constraints); - if (err != kEebusErrorOk) { - MeasurementConstraintsDelete(measurement_constraints); - return; // Configuration failed - } - } - - if (measurement_constraints->measurement_constraints_data_size != 0) { - FEATURE_LOCAL_SET_FUNCTION_OPERATIONS(mfl, kFunctionTypeMeasurementConstraintsListData, true, false); - MeasurementServerUpdateMeasurementConstraints(&msrv, measurement_constraints, NULL, NULL); - } - - MeasurementConstraintsDelete(measurement_constraints); -} - -void MuMpcMonitorDeallocator(void* p) { - MuMpcMonitorDelete((MuMpcMonitorObject*)p); -} - EebusError MuMpcUseCaseConstruct( MuMpcUseCase* self, EntityLocalObject* local_entity, @@ -247,13 +169,11 @@ EebusError MuMpcUseCaseConstruct( USE_CASE_INTERFACE(self) = &mu_mpc_use_case_methods; self->electrical_connection_id = ec_id; - VectorConstructWithDeallocator(&self->monitors, MuMpcMonitorDeallocator); - - self->use_case_scenarios_size = 0; + self->use_case_scenarios_size = 0; - self->mutex = EebusMutexCreate(); - if (self->mutex == NULL) { - return kEebusErrorMemoryAllocate; + const EebusError container_err = EebusMonitorContainerConstruct(&self->monitor_container); + if (container_err != kEebusErrorOk) { + return container_err; } const EebusError add_scenario1_err = AddMuMpcScenario1(self, &cfg->power_cfg); @@ -305,9 +225,7 @@ EebusError MuMpcUseCaseConstruct( UseCaseConstruct(USE_CASE(self), &self->mu_mpc_use_case_info, local_entity, NULL); - AddFeatures(USE_CASE_OBJECT(self), local_entity); - - return kEebusErrorOk; + return EebusMonitorFeaturesSetup(&self->monitor_container, local_entity, self->electrical_connection_id); } MuMpcUseCaseObject* @@ -332,11 +250,7 @@ MuMpcUseCaseCreate(EntityLocalObject* local_entity, ElectricalConnectionIdType e void Destruct(UseCaseObject* self) { MuMpcUseCase* const mu_mpc_use_case = MU_MPC_USE_CASE(self); - EebusMutexDelete(mu_mpc_use_case->mutex); - mu_mpc_use_case->mutex = NULL; - - VectorFreeElements(&mu_mpc_use_case->monitors); - VectorDestruct(&mu_mpc_use_case->monitors); + EebusMonitorContainerDestruct(&mu_mpc_use_case->monitor_container); UseCaseDestruct(self); } diff --git a/src/use_case/actor/mu/mpc/mu_mpc.h b/src/use_case/actor/mu/mpc/mu_mpc.h index edd0a1e..5aa5bf5 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc.h +++ b/src/use_case/actor/mu/mpc/mu_mpc.h @@ -58,6 +58,7 @@ #include "src/spine/entity/entity_local.h" #include "src/use_case/actor/mu/mpc/mu_mpc_monitor.h" #include "src/use_case/api/use_case_interface.h" +#include "src/use_case/use_case.h" #ifdef __cplusplus extern "C" { @@ -108,10 +109,7 @@ MuMpcUseCaseCreate(EntityLocalObject* local_entity, ElectricalConnectionIdType e * @param mu_mpc Pointer to the MuMpcUseCaseObject instance to be deleted */ static inline void MuMpcUseCaseDelete(MuMpcUseCaseObject* mu_mpc) { - if (mu_mpc != NULL) { - USE_CASE_DESTRUCT(USE_CASE_OBJECT(mu_mpc)); - EEBUS_FREE(mu_mpc); - } + UseCaseDelete(USE_CASE_OBJECT(mu_mpc)); } /** diff --git a/src/use_case/actor/mu/mpc/mu_mpc_internal.h b/src/use_case/actor/mu/mpc/mu_mpc_internal.h index f15e6eb..c5b528a 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc_internal.h +++ b/src/use_case/actor/mu/mpc/mu_mpc_internal.h @@ -19,12 +19,11 @@ #include -#include "src/common/api/eebus_mutex_interface.h" #include "src/spine/model/measurement_types.h" +#include "src/use_case/actor/common/eebus_monitor_container.h" #include "src/use_case/actor/mu/mpc/mu_mpc_measurement.h" #include "src/use_case/actor/mu/mpc/mu_mpc_monitor.h" #include "src/use_case/model/load_limit_types.h" -#include "src/use_case/use_case.h" typedef struct MuMpcUseCase MuMpcUseCase; @@ -34,14 +33,12 @@ struct MuMpcUseCase { ElectricalConnectionIdType electrical_connection_id; - Vector monitors; + EebusMonitorContainer monitor_container; UseCaseScenario use_case_scenarios[5]; size_t use_case_scenarios_size; UseCaseInfo mu_mpc_use_case_info; - - EebusMutexObject* mutex; }; #define MU_MPC_USE_CASE(self) ((MuMpcUseCase*)(self)) diff --git a/src/use_case/actor/mu/mpc/mu_mpc_measurement.c b/src/use_case/actor/mu/mpc/mu_mpc_measurement.c index 1abd622..b6582b5 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc_measurement.c +++ b/src/use_case/actor/mu/mpc/mu_mpc_measurement.c @@ -13,377 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include -#include "src/common/array_util.h" -#include "src/spine/model/absolute_or_relative_time.h" -#include "src/spine/model/measurement_types.h" #include "src/use_case/actor/mu/mpc/mu_mpc_measurement.h" -/** - * @brief MU MPC Measurement is measurement info (including id, phases, value source and constraints) - * for the MU MPC use case scenarios 1-5 - */ -typedef struct MuMpcMeasurement MuMpcMeasurement; - -/** - * @brief MU MPC Measurement Configuration Strategy - */ -typedef EebusError (*MeasurementConfigurationStrategy)( - MuMpcMeasurement* measurement, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -); - -/** - * @brief MU MPC Measurement structure - */ -struct MuMpcMeasurement { - /** Implements the MU MPC Measurement Object Interface */ - MuMpcMeasurementObject obj; - - MuMpcMeasurementNameId name; - /** MuMpcMeasurement Id (required). Shall be obtained while MEASUREMENT_CONFIGURE() exectution */ - MeasurementIdType id; - /** Measurement Object (total or per phase) */ - ScopeTypeType scope; - /** Measured phases for total or single one for per phase */ - ElectricalConnectionPhaseNameType phases; - /** The source of the values (required) */ - MeasurementValueSourceType value_source; - /** The constraints for the voltage values (optional) */ - MeasurementConstraintsDataType* constraints; - /** The strategy to configure the measurement scope */ - MeasurementConfigurationStrategy cfg_strategy; - /** Cache for the measurement data */ - MeasurementDataType* measurement_data; -}; - -#define MEASUREMENT(obj) ((MuMpcMeasurement*)(obj)) - -static void Destruct(MuMpcMeasurementObject* self); -static MuMpcMeasurementNameId GetName(const MuMpcMeasurementObject* self); -static EebusError -GetDataValue(const MuMpcMeasurementObject* self, MeasurementServer* msrv, ScaledValue* measurement_value); -static const MeasurementConstraintsDataType* GetConstraints(const MuMpcMeasurementObject* self); -static EebusError Configure( - MuMpcMeasurementObject* self, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -); -static EebusError SetDataCache( - MuMpcMeasurementObject* self, - const ScaledValue* measured_value, - const EebusDateTime* timestamp, - const MeasurementValueStateType* value_state, - const EebusDateTime* start_time, - const EebusDateTime* end_time -); -static MeasurementDataType* ReleaseDataCache(MuMpcMeasurementObject* self); - -static const MuMpcMeasurementInterface measurement_scope_methods = { - .destruct = Destruct, - .get_name = GetName, - .get_data_value = GetDataValue, - .get_constraints = GetConstraints, - .configure = Configure, - .set_data_cache = SetDataCache, - .release_data_cache = ReleaseDataCache, -}; - -static EebusError MuMpcMeasurementConstruct( - MuMpcMeasurement* self, - MuMpcMeasurementNameId name, - ScopeTypeType scope, - ElectricalConnectionPhaseNameType phases, - const MuMpcMeasurementConfig* cfg, - MeasurementConfigurationStrategy cfg_strategy -); - -EebusError MuMpcMeasurementConstruct( - MuMpcMeasurement* self, - MuMpcMeasurementNameId name, - ScopeTypeType scope, - ElectricalConnectionPhaseNameType phases, - const MuMpcMeasurementConfig* cfg, - MeasurementConfigurationStrategy cfg_strategy -) { - // Override "virtual functions table" - MU_MPC_MEASUREMENT_INTERFACE(self) = &measurement_scope_methods; - - self->name = name; - self->id = 0; - self->scope = scope; - self->phases = phases; - self->value_source = 0; - self->constraints = NULL; - self->cfg_strategy = cfg_strategy; - self->measurement_data = NULL; - - if (cfg == NULL) { - return kEebusErrorInputArgumentNull; - } - - if (cfg->constraints != NULL) { - self->constraints = MeasurementConstraintsDataCopy(cfg->constraints); - if (self->constraints == NULL) { - return kEebusErrorMemoryAllocate; - } - } - - return kEebusErrorOk; -} - -MuMpcMeasurementObject* MuMpcMeasurementCreateInternal( - MuMpcMeasurementNameId name, - ScopeTypeType scope, - ElectricalConnectionPhaseNameType phases, - const MuMpcMeasurementConfig* cfg, - MeasurementConfigurationStrategy cfg_strategy -) { - MuMpcMeasurement* const measurement = (MuMpcMeasurement*)EEBUS_MALLOC(sizeof(MuMpcMeasurement)); - if (measurement == NULL) { - return NULL; - } - - EebusError err = MuMpcMeasurementConstruct(measurement, name, scope, phases, cfg, cfg_strategy); - if (err != kEebusErrorOk) { - MuMpcMeasurementDelete(MU_MPC_MEASUREMENT_OBJECT(measurement)); - return NULL; - } - - return MU_MPC_MEASUREMENT_OBJECT(measurement); -} - -void Destruct(MuMpcMeasurementObject* self) { - MuMpcMeasurement* const measurement = MEASUREMENT(self); - - MeasurementConstraintsDataDelete(measurement->constraints); - measurement->constraints = NULL; - - MeasurementDataDelete(measurement->measurement_data); - measurement->measurement_data = NULL; -} - -MuMpcMeasurementNameId GetName(const MuMpcMeasurementObject* self) { - return MEASUREMENT(self)->name; -} - -EebusError GetDataValue(const MuMpcMeasurementObject* self, MeasurementServer* msrv, ScaledValue* measurement_value) { - MuMpcMeasurement* const measurement = MEASUREMENT(self); - - if ((msrv == NULL) || (measurement_value == NULL)) { - return kEebusErrorInputArgumentNull; - } - - const MeasurementDataType* data = MeasurementCommonGetMeasurementWithId(&msrv->measurement_common, measurement->id); - if (data == NULL) { - return kEebusErrorNoChange; - } - - return ScaledValueInitWithScaledNumber(measurement_value, data->value); -} - -const MeasurementConstraintsDataType* GetConstraints(const MuMpcMeasurementObject* self) { - return MEASUREMENT(self)->constraints; -} - -EebusError Configure( - MuMpcMeasurementObject* self, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -) { - MuMpcMeasurement* const measurement = MEASUREMENT(self); - - if (measurement->cfg_strategy == NULL) { - return kEebusErrorInit; - } - - // Call the strategy to configure the measurement scope - EebusError err = measurement->cfg_strategy(measurement, msrv, ecsrv, electrical_connection_id); - if (err != kEebusErrorOk) { - return err; - } - - // If constraints are configured, set the constraints id - if (measurement->constraints != NULL) { - err = MeasurementConstraintsSetId(measurement->constraints, measurement->id); - } - - return err; -} - -EebusError SetDataCache( - MuMpcMeasurementObject* self, - const ScaledValue* measured_value, - const EebusDateTime* timestamp, - const MeasurementValueStateType* value_state, - const EebusDateTime* start_time, - const EebusDateTime* end_time -) { - MuMpcMeasurement* const measurement = MEASUREMENT(self); - - if (measured_value == NULL) { - return kEebusErrorInputArgumentNull; - } - - // If measurement data already exists, release it - MeasurementDataDelete(measurement->measurement_data); - - const AbsoluteOrRelativeTimeType* const start_time_tmp = ABSOLUTE_OR_RELATIVE_TIME_PTR(start_time); - const AbsoluteOrRelativeTimeType* const end_time_tmp = ABSOLUTE_OR_RELATIVE_TIME_PTR(end_time); - - const TimePeriodType* const evaluation_period_tmp - = ((start_time_tmp != NULL) && (end_time_tmp != NULL)) - ? &(TimePeriodType){.start_time = start_time_tmp, .end_time = end_time_tmp} - : NULL; - - const MeasurementDataType measurement_data_new = { - .measurement_id = &measurement->id, - .value_type = &(MeasurementValueTypeType){kMeasurementValueTypeTypeValue}, - .timestamp = ABSOLUTE_OR_RELATIVE_TIME_PTR(timestamp), - .value = &(ScaledNumberType){.number = &measured_value->value, .scale = &measured_value->scale}, - .evaluation_period = evaluation_period_tmp, - .value_source = &measurement->value_source, - .value_tendency = NULL, - .value_state = value_state, - }; - - measurement->measurement_data = MeasurementDataCopy(&measurement_data_new); - if (measurement->measurement_data == NULL) { - return kEebusErrorMemoryAllocate; - } - - return kEebusErrorOk; -} - -MeasurementDataType* ReleaseDataCache(MuMpcMeasurementObject* self) { - MuMpcMeasurement* const measurement = MEASUREMENT(self); - - MeasurementDataType* const measurement_data = measurement->measurement_data; - - measurement->measurement_data = NULL; - - return measurement_data; -} - -//-------------------------------------------------------------------------------------------// -// -// Power MuMpcMeasurment Object Creation (Scenario 1) -// -//-------------------------------------------------------------------------------------------// -EebusError ConfigureElectricalConnectionDescription( - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -) { - const ElectricalConnectionDescriptionDataType description = { - .power_supply_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), - .positive_energy_direction = &(EnergyDirectionType){kEnergyDirectionTypeConsume}, - }; - - return ElectricalConnectionServerAddDescriptionWithId(ecsrv, &description, electrical_connection_id); -} - -EebusError ConfigurePower( - MuMpcMeasurement* measurement, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -) { - const MeasurementDescriptionDataType measurement_description = { - .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypePower}, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeW}, - .scope_type = &measurement->scope, - }; - - EebusError err = MeasurementServerAddDescription(msrv, &measurement_description, &measurement->id); - if (err != kEebusErrorOk) { - return err; - } - - err = ConfigureElectricalConnectionDescription(ecsrv, electrical_connection_id); - if (err != kEebusErrorOk) { - return err; - } - - const ElectricalConnectionParameterDescriptionDataType parameter_description = { - .electrical_connection_id = &electrical_connection_id, - .measurement_id = &measurement->id, - .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), - .ac_measured_phases = &measurement->phases, - .ac_measured_in_reference_to = &ELECTRICAL_CONNECTION_PHASE_NAME(Neutral), - .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Real), - .ac_measurement_variant = &ELECTRICAL_CONNECTION_MEASURAND_VARIANT(Rms), - }; - - ElectricalConnectionParameterIdType parameter_id; - return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶meter_description, ¶meter_id); -} - -ElectricalConnectionPhaseNameType GetPowerPhaseWithNameId(MuMpcMeasurementNameId name) { - switch (name) { - case kMpcPowerPhaseA: return kElectricalConnectionPhaseNameTypeA; - case kMpcPowerPhaseB: return kElectricalConnectionPhaseNameTypeB; - case kMpcPowerPhaseC: return kElectricalConnectionPhaseNameTypeC; - default: return kElectricalConnectionPhaseNameTypeNone; // Invalid phase - } -} - -MuMpcMeasurementObject* MuMpcMeasurementPowerCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg) { - if (!((uint8_t)name & (uint8_t)kMpcMonitorPower)) { - return NULL; - } - - const ScopeTypeType scope = kScopeTypeTypeACPower; - - const ElectricalConnectionPhaseNameType phases = GetPowerPhaseWithNameId(name); - return MuMpcMeasurementCreateInternal(name, scope, phases, cfg, ConfigurePower); -} - -MuMpcMeasurementObject* -MuMpcMeasurementPowerTotalCreate(ElectricalConnectionPhaseNameType phases, const MuMpcMeasurementConfig* cfg) { - return MuMpcMeasurementCreateInternal(kMpcPowerTotal, kScopeTypeTypeACPowerTotal, phases, cfg, ConfigurePower); -} - -//-------------------------------------------------------------------------------------------// -// -// Energy MuMpcMeasurment Object Creation (Scenario 2) -// -//-------------------------------------------------------------------------------------------// -EebusError ConfigureEnergy( - MuMpcMeasurement* measurement, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -) { - const MeasurementDescriptionDataType measurement_description = { - .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeEnergy}, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeWh}, - .scope_type = &measurement->scope, - }; - - EebusError err = MeasurementServerAddDescription(msrv, &measurement_description, &measurement->id); - if (err != kEebusErrorOk) { - return err; - } - - const ElectricalConnectionParameterDescriptionDataType parameter_description = { - .electrical_connection_id = &electrical_connection_id, - .measurement_id = &measurement->id, - .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), - .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Real), - }; - - ElectricalConnectionParameterIdType parameter_description_id; - return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶meter_description, ¶meter_description_id); -} - -ScopeTypeType GetEnergyScopeType(MuMpcMeasurementNameId name) { +static ScopeTypeType GetEnergyScopeType(MuMpcMeasurementNameId name) { switch (name) { case kMpcEnergyConsumed: return kScopeTypeTypeACEnergyConsumed; case kMpcEnergyProduced: return kScopeTypeTypeACEnergyProduced; @@ -391,138 +24,25 @@ ScopeTypeType GetEnergyScopeType(MuMpcMeasurementNameId name) { } } -MuMpcMeasurementObject* MuMpcMeasurementEnergyCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg) { - if (!((uint8_t)name & (uint8_t)kMpcMonitorEnergy)) { - return NULL; - } - - const ScopeTypeType scope = GetEnergyScopeType(name); - - const ElectricalConnectionPhaseNameType phase = kElectricalConnectionPhaseNameTypeNone; - return MuMpcMeasurementCreateInternal(name, scope, phase, cfg, ConfigureEnergy); -} - -//-------------------------------------------------------------------------------------------// -// -// Current MuMpcMeasurment Object Creation (Scenario 3) -// -//-------------------------------------------------------------------------------------------// -EebusError ConfigureCurrent( - MuMpcMeasurement* measurement, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -) { - const MeasurementDescriptionDataType measurement_description = { - .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeCurrent}, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeA}, - .scope_type = &measurement->scope, - }; - - EebusError err = MeasurementServerAddDescription(msrv, &measurement_description, &measurement->id); - if (err != kEebusErrorOk) { - return err; - } - - err = ConfigureElectricalConnectionDescription(ecsrv, electrical_connection_id); - if (err != kEebusErrorOk) { - return err; +static ElectricalConnectionPhaseNameType GetPowerPhase(MuMpcMeasurementNameId name) { + switch (name) { + case kMpcPowerPhaseA: return kElectricalConnectionPhaseNameTypeA; + case kMpcPowerPhaseB: return kElectricalConnectionPhaseNameTypeB; + case kMpcPowerPhaseC: return kElectricalConnectionPhaseNameTypeC; + default: return kElectricalConnectionPhaseNameTypeNone; } - - const ElectricalConnectionParameterDescriptionDataType parameter_description = { - .electrical_connection_id = &electrical_connection_id, - .measurement_id = &measurement->id, - .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), - .ac_measured_phases = &measurement->phases, - .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Real), - .ac_measurement_variant = &ELECTRICAL_CONNECTION_MEASURAND_VARIANT(Rms), - }; - - ElectricalConnectionParameterIdType parameter_description_id; - return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶meter_description, ¶meter_description_id); } -ElectricalConnectionPhaseNameType GetCurrentPhaseWithNameId(MuMpcMeasurementNameId name) { +static ElectricalConnectionPhaseNameType GetCurrentPhase(MuMpcMeasurementNameId name) { switch (name) { case kMpcCurrentPhaseA: return kElectricalConnectionPhaseNameTypeA; case kMpcCurrentPhaseB: return kElectricalConnectionPhaseNameTypeB; case kMpcCurrentPhaseC: return kElectricalConnectionPhaseNameTypeC; - default: return kElectricalConnectionPhaseNameTypeNone; // Invalid phase + default: return kElectricalConnectionPhaseNameTypeNone; } } -MuMpcMeasurementObject* MuMpcMeasurementCurrentCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg) { - if (!((uint8_t)name & (uint8_t)kMpcMonitorCurrent)) { - return NULL; - } - - const ElectricalConnectionPhaseNameType phase = GetCurrentPhaseWithNameId(name); - return MuMpcMeasurementCreateInternal(name, kScopeTypeTypeACCurrent, phase, cfg, ConfigureCurrent); -} - -//-------------------------------------------------------------------------------------------// -// -// Voltage MuMpcMeasurment Object Creation (Scenario 4) -// -//-------------------------------------------------------------------------------------------// -ElectricalConnectionPhaseNameType GetPhaseFrom(ElectricalConnectionPhaseNameType phases) { - switch (phases) { - case kElectricalConnectionPhaseNameTypeA: return kElectricalConnectionPhaseNameTypeA; - case kElectricalConnectionPhaseNameTypeB: return kElectricalConnectionPhaseNameTypeB; - case kElectricalConnectionPhaseNameTypeC: return kElectricalConnectionPhaseNameTypeC; - case kElectricalConnectionPhaseNameTypeAb: return kElectricalConnectionPhaseNameTypeA; - case kElectricalConnectionPhaseNameTypeBc: return kElectricalConnectionPhaseNameTypeB; - case kElectricalConnectionPhaseNameTypeAc: return kElectricalConnectionPhaseNameTypeC; - default: return 0; // Invalid phase - } -} - -ElectricalConnectionPhaseNameType GetPhaseTo(ElectricalConnectionPhaseNameType phase) { - switch (phase) { - case kElectricalConnectionPhaseNameTypeA: return kElectricalConnectionPhaseNameTypeNeutral; - case kElectricalConnectionPhaseNameTypeB: return kElectricalConnectionPhaseNameTypeNeutral; - case kElectricalConnectionPhaseNameTypeC: return kElectricalConnectionPhaseNameTypeNeutral; - case kElectricalConnectionPhaseNameTypeAb: return kElectricalConnectionPhaseNameTypeB; - case kElectricalConnectionPhaseNameTypeBc: return kElectricalConnectionPhaseNameTypeC; - case kElectricalConnectionPhaseNameTypeAc: return kElectricalConnectionPhaseNameTypeA; - default: return 0; // Invalid phase - } -} - -EebusError ConfigureVoltage( - MuMpcMeasurement* self, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -) { - const MeasurementDescriptionDataType measurement_description = { - .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeVoltage}, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeV}, - .scope_type = &self->scope, - }; - - EebusError err = MeasurementServerAddDescription(msrv, &measurement_description, &self->id); - if (err != kEebusErrorOk) { - return err; - } - - const ElectricalConnectionParameterDescriptionDataType parameter_description = { - .electrical_connection_id = &electrical_connection_id, - .measurement_id = &self->id, - .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), - .ac_measured_phases = &(ElectricalConnectionPhaseNameType){GetPhaseFrom(self->phases)}, - .ac_measured_in_reference_to = &(ElectricalConnectionPhaseNameType){GetPhaseTo(self->phases)}, - .ac_measurement_type = &ELECTRICAL_CONNECTION_AC_MEASUREMENT_TYPE(Apparent), - .ac_measurement_variant = &ELECTRICAL_CONNECTION_MEASURAND_VARIANT(Rms), - }; - - ElectricalConnectionParameterIdType parameter_description_id; - return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶meter_description, ¶meter_description_id); -} - -ElectricalConnectionPhaseNameType GetVoltagePhaseWithNameId(MuMpcMeasurementNameId name) { +static ElectricalConnectionPhaseNameType GetVoltagePhase(MuMpcMeasurementNameId name) { switch (name) { case kMpcVoltagePhaseA: return kElectricalConnectionPhaseNameTypeA; case kMpcVoltagePhaseB: return kElectricalConnectionPhaseNameTypeB; @@ -530,96 +50,55 @@ ElectricalConnectionPhaseNameType GetVoltagePhaseWithNameId(MuMpcMeasurementName case kMpcVoltagePhaseAb: return kElectricalConnectionPhaseNameTypeAb; case kMpcVoltagePhaseBc: return kElectricalConnectionPhaseNameTypeBc; case kMpcVoltagePhaseAc: return kElectricalConnectionPhaseNameTypeAc; - default: return kElectricalConnectionPhaseNameTypeNone; // Invalid phase - } -} - -MuMpcMeasurementObject* MuMpcMeasurementVoltageCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg) { - if (!((uint8_t)name & (uint8_t)kMpcMonitorVoltage)) { - return NULL; + default: return kElectricalConnectionPhaseNameTypeNone; } - - const ElectricalConnectionPhaseNameType phases = GetVoltagePhaseWithNameId(name); - return MuMpcMeasurementCreateInternal(name, kScopeTypeTypeACVoltage, phases, cfg, ConfigureVoltage); } -//-------------------------------------------------------------------------------------------// -// -// Frequency MuMpcMeasurment Object Creation (Scenario 5) -// -//-------------------------------------------------------------------------------------------// -EebusError ConfigureFrequency( - MuMpcMeasurement* measurement, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id -) { - const MeasurementDescriptionDataType measurement_description = { - .measurement_type = &(MeasurementTypeType){kMeasurementTypeTypeFrequency}, - .commodity_type = &(CommodityTypeType){kCommodityTypeTypeElectricity}, - .unit = &(UnitOfMeasurementType){kUnitOfMeasurementTypeHz}, - .scope_type = &measurement->scope, - }; - - EebusError err = MeasurementServerAddDescription(msrv, &measurement_description, &measurement->id); - if (err != kEebusErrorOk) { - return err; - } - - const ElectricalConnectionParameterDescriptionDataType parameter_description = { - .electrical_connection_id = &electrical_connection_id, - .measurement_id = &measurement->id, - .voltage_type = &ELECTRICAL_CONNECTION_VOLTAGE_TYPE(Ac), - }; - - ElectricalConnectionParameterIdType parameter_description_id; - return ElectricalConnectionServerAddParameterDescription(ecsrv, ¶meter_description, ¶meter_description_id); -} - -MuMpcMeasurementObject* MuMpcMeasurementFrequencyCreate(const MuMpcMeasurementConfig* cfg) { - const ScopeTypeType scope = kScopeTypeTypeACFrequency; - - const ElectricalConnectionPhaseNameType phase = kElectricalConnectionPhaseNameTypeNone; - return MuMpcMeasurementCreateInternal(kMpcFrequency, scope, phase, cfg, ConfigureFrequency); -} - -//-------------------------------------------------------------------------------------------// -// -// Generic MuMpcMeasurement Object Creation entry point -// -//-------------------------------------------------------------------------------------------// -MuMpcMeasurementObject* MuMpcMeasurementCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg) { - switch (name) { - case kMpcCurrentPhaseA: - case kMpcCurrentPhaseB: - case kMpcCurrentPhaseC: { - return MuMpcMeasurementCurrentCreate(name, cfg); - } - - case kMpcEnergyConsumed: - case kMpcEnergyProduced: { - return MuMpcMeasurementEnergyCreate(name, cfg); - } - - case kMpcFrequency: { - return MuMpcMeasurementFrequencyCreate(cfg); - } - - case kMpcPowerPhaseA: - case kMpcPowerPhaseB: - case kMpcPowerPhaseC: { - return MuMpcMeasurementPowerCreate(name, cfg); - } - - case kMpcVoltagePhaseA: - case kMpcVoltagePhaseB: - case kMpcVoltagePhaseC: - case kMpcVoltagePhaseAb: - case kMpcVoltagePhaseBc: - case kMpcVoltagePhaseAc: { - return MuMpcMeasurementVoltageCreate(name, cfg); +EebusMeasurementObject* +MuMpcMeasurementPowerTotalCreate(ElectricalConnectionPhaseNameType phases, const MuMpcMeasurementConfig* cfg) { + return EebusMeasurementBaseCreate( + kMpcPowerTotal, + kScopeTypeTypeACPowerTotal, + phases, + cfg, + EebusMeasurementBaseConfigurePower + ); +} + +EebusMeasurementObject* MuMpcMeasurementCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg) { + ScopeTypeType scope = (ScopeTypeType)0; + ElectricalConnectionPhaseNameType phase = (ElectricalConnectionPhaseNameType)0; + EebusMeasurementConfigureStrategy strategy = NULL; + const int32_t measurement_group = (int32_t)name & (int32_t)kMpcMonitorNameIdMask; + + if (measurement_group == kMpcMonitorPower) { + if (name == kMpcPowerTotal) { + // Use MuMpcMeasurementPowerTotalCreate() instead + return NULL; } - default: return NULL; // Invalid measurement name + scope = kScopeTypeTypeACPower; + phase = GetPowerPhase(name); + strategy = EebusMeasurementBaseConfigurePower; + } else if (measurement_group == kMpcMonitorEnergy) { + scope = GetEnergyScopeType(name); + phase = kElectricalConnectionPhaseNameTypeNone; + strategy = EebusMeasurementBaseConfigureEnergy; + } else if (measurement_group == kMpcMonitorCurrent) { + scope = kScopeTypeTypeACCurrent; + phase = GetCurrentPhase(name); + strategy = EebusMeasurementBaseConfigureCurrent; + } else if (measurement_group == kMpcMonitorVoltage) { + scope = kScopeTypeTypeACVoltage; + phase = GetVoltagePhase(name); + strategy = EebusMeasurementBaseConfigureVoltage; + } else if (measurement_group == kMpcMonitorFrequency) { + scope = kScopeTypeTypeACFrequency; + phase = kElectricalConnectionPhaseNameTypeNone; + strategy = EebusMeasurementBaseConfigureFrequency; + } else { + return NULL; } + + return EebusMeasurementBaseCreate(name, scope, phase, cfg, strategy); } diff --git a/src/use_case/actor/mu/mpc/mu_mpc_measurement.h b/src/use_case/actor/mu/mpc/mu_mpc_measurement.h index 7a1a9a9..45fde0f 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc_measurement.h +++ b/src/use_case/actor/mu/mpc/mu_mpc_measurement.h @@ -26,30 +26,15 @@ #ifndef SRC_USE_CASE_ACTOR_MU_MPC_MEASUREMENT_H_ #define SRC_USE_CASE_ACTOR_MU_MPC_MEASUREMENT_H_ -#include - -#include "src/common/eebus_errors.h" #include "src/common/eebus_malloc.h" -#include "src/use_case/api/mu_mpc_measurement_interface.h" +#include "src/use_case/actor/common/eebus_measurement_base.h" +#include "src/use_case/model/mpc_types.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus -/** - * @brief Measurement configuration containing value source and constraints. - */ -typedef struct MuMpcMeasurementConfig MuMpcMeasurementConfig; - -/** - * @brief Measurement configuration structure - */ -struct MuMpcMeasurementConfig { - /** The source of the values (required) */ - MeasurementValueSourceType value_source; - /** The constraints for the current values (optional can be NULL) */ - MeasurementConstraintsDataType* constraints; -}; +typedef EebusMeasurementBaseConfig MuMpcMeasurementConfig; /** * @brief Creates a new measurement object for the given name_id and configuration. @@ -78,27 +63,27 @@ struct MuMpcMeasurementConfig { * @note For Scenario 1, use MuMpcMeasurementPowerTotalCreate() to create a total power measurement. * can be alternatively use if evalutaion time has to be set. * @param cfg The configuration for the measurement, including value source and constraints. - * @return A pointer to the created MuMpcMeasurementObject, or NULL if creation failed + * @return A pointer to the created EebusMeasurementObject, or NULL if creation failed */ -MuMpcMeasurementObject* MuMpcMeasurementCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg); +EebusMeasurementObject* MuMpcMeasurementCreate(MuMpcMeasurementNameId name, const MuMpcMeasurementConfig* cfg); /** * @brief Creates a new measurement object for the total power measurement. * @param phases The electrical connection phases to measure (e.g., kElectricalConnectionPhaseNameTypeAbc) * @param cfg The configuration for the measurement, including value source and constraints. - * @return A pointer to the created MuMpcMeasurementObject, or NULL if creation failed + * @return A pointer to the created EebusMeasurementObject, or NULL if creation failed */ -MuMpcMeasurementObject* +EebusMeasurementObject* MuMpcMeasurementPowerTotalCreate(ElectricalConnectionPhaseNameType phases, const MuMpcMeasurementConfig* cfg); /** - * @brief Delete the MuMpcMeasurementObject and free its resources. - * @param measurement The MuMpcMeasurementObject to delete. + * @brief Delete the EebusMeasurementObject and free its resources. + * @param measurement The EebusMeasurementObject to delete. * This function will destruct the measurement object and free the memory allocated for it. */ -static inline void MuMpcMeasurementDelete(MuMpcMeasurementObject* measurement) { +static inline void MuMpcMeasurementDelete(EebusMeasurementObject* measurement) { if (measurement != NULL) { - MU_MPC_MEASUREMENT_DESTRUCT(measurement); + EEBUS_MEASUREMENT_DESTRUCT(measurement); EEBUS_FREE(measurement); } } diff --git a/src/use_case/actor/mu/mpc/mu_mpc_monitor.c b/src/use_case/actor/mu/mpc/mu_mpc_monitor.c index 8cfec73..d3d1e63 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc_monitor.c +++ b/src/use_case/actor/mu/mpc/mu_mpc_monitor.c @@ -19,229 +19,18 @@ */ #include "src/use_case/actor/mu/mpc/mu_mpc_monitor.h" + #include "src/common/array_util.h" #include "src/common/eebus_malloc.h" -#include "src/common/vector.h" -#include "src/use_case/api/mu_mpc_monitor_interface.h" - -typedef struct MuMpcMonitor MuMpcMonitor; - -struct MuMpcMonitor { - /** Implements the Mu Mpc Monitor Interface */ - MuMpcMonitorObject obj; - - /** The name of the monitor */ - MuMpcMonitorNameId name; - /** Container for all current measurements */ - Vector measurements; -}; - -#define MU_MPC_MONITOR(obj) ((MuMpcMonitor*)(obj)) - -static void Destruct(MuMpcMonitorObject* self); -static MuMpcMonitorNameId GetName(const MuMpcMonitorObject* self); -static EebusError Configure( - MuMpcMonitorObject* self, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id, - MeasurementConstraintsListDataType* measurements_constraints -); -static MuMpcMeasurementObject* -GetMeasurement(const MuMpcMonitorObject* self, MuMpcMeasurementNameId measurement_name_id); -static EebusError FlushMeasurementCache(MuMpcMonitorObject* self, MeasurementListDataType* measurement_data_list); - -static const MuMpcMonitorInterface mu_mpc_monitor_methods = { - .destruct = Destruct, - .get_name = GetName, - .configure = Configure, - .get_measurement = GetMeasurement, - .flush_measurement_cache = FlushMeasurementCache, -}; - -typedef struct MeasurementParameters { - MuMpcMeasurementNameId measurement_name; - const MuMpcMeasurementConfig* cfg; -} MeasurementParameters; - -static void MeasurementDeallocator(void* p); -EebusError MuMpcMonitorConstruct(MuMpcMonitor* self, MuMpcMonitorNameId name); -static EebusError MuMpcMonitorConstructWithMeasurements( - MuMpcMonitor* self, - MuMpcMonitorNameId name, - const MeasurementParameters* measurement_params, - size_t measurement_params_size -); -static EebusError -AddMeasurements(MuMpcMonitor* self, const MeasurementParameters* measurement_params, size_t measurement_params_size); - -void MeasurementDeallocator(void* p) { - MuMpcMeasurementDelete((MuMpcMeasurementObject*)p); -} - -EebusError MuMpcMonitorConstruct(MuMpcMonitor* self, MuMpcMonitorNameId name) { - // Override "virtual functions table" - MU_MPC_MONITOR_INTERFACE(self) = &mu_mpc_monitor_methods; - - self->name = name; - VectorConstructWithDeallocator(&self->measurements, MeasurementDeallocator); - return kEebusErrorOk; -} - -EebusError MuMpcMonitorConstructWithMeasurements( - MuMpcMonitor* self, - MuMpcMonitorNameId name, - const MeasurementParameters* measurement_params, - size_t measurement_params_size -) { - const EebusError err = MuMpcMonitorConstruct(self, name); - if (err != kEebusErrorOk) { - return err; // Failed to construct the monitor - } - - return AddMeasurements(self, measurement_params, measurement_params_size); -} - -MuMpcMonitorObject* MuMpcMonitorCreate( - MuMpcMonitorNameId name, - const MeasurementParameters* measurement_params, - size_t measurement_params_size -) { - if (measurement_params == NULL || measurement_params_size == 0) { - return NULL; // Invalid parameters - } - - MuMpcMonitor* const monitor = EEBUS_MALLOC(sizeof(MuMpcMonitor)); - if (monitor == NULL) { - return NULL; // Memory allocation failed - } - - const EebusError err - = MuMpcMonitorConstructWithMeasurements(monitor, name, measurement_params, measurement_params_size); - if (err != kEebusErrorOk) { - MuMpcMonitorDelete(MU_MPC_MONITOR_OBJECT(monitor)); - return NULL; // Failed to construct the monitor with measurements - } - - return MU_MPC_MONITOR_OBJECT(monitor); -} - -void Destruct(MuMpcMonitorObject* self) { - MuMpcMonitor* const monitor = MU_MPC_MONITOR(self); - - VectorFreeElements(&monitor->measurements); - VectorDestruct(&monitor->measurements); -} - -MuMpcMonitorNameId GetName(const MuMpcMonitorObject* self) { - return MU_MPC_MONITOR(self)->name; -} - -EebusError Configure( - MuMpcMonitorObject* self, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id, - MeasurementConstraintsListDataType* measurements_constraints -) { - MuMpcMonitor* const monitor = MU_MPC_MONITOR(self); - - if ((msrv == NULL) || (ecsrv == NULL)) { - return kEebusErrorInputArgumentNull; - } - - for (size_t i = 0; i < VectorGetSize(&monitor->measurements); ++i) { - MuMpcMeasurementObject* const measurement = (MuMpcMeasurementObject*)VectorGetElement(&monitor->measurements, i); - - if (measurement != NULL) { - const EebusError err = MU_MPC_MEASUREMENT_CONFIGURE(measurement, msrv, ecsrv, electrical_connection_id); - if (err != kEebusErrorOk) { - return err; - } - } - - const MeasurementConstraintsDataType* constraints = MU_MPC_MEASUREMENT_GET_CONSTRAINTS(measurement); - if (constraints != NULL) { - const EebusError err = MeasurementConstraintsAdd(measurements_constraints, constraints); - if (err != kEebusErrorOk) { - return err; - } - } - } - - return kEebusErrorOk; -} - -EebusError -AddMeasurements(MuMpcMonitor* self, const MeasurementParameters* measurement_params, size_t measurement_params_size) { - for (size_t i = 0; i < measurement_params_size; ++i) { - if (measurement_params[i].cfg != NULL) { - MuMpcMeasurementObject* const measurement - = MuMpcMeasurementCreate(measurement_params[i].measurement_name, measurement_params[i].cfg); - if (measurement == NULL) { - return kEebusErrorInit; - } - - VectorPushBack(&self->measurements, measurement); - } - } - - return kEebusErrorOk; -} - -MuMpcMeasurementObject* GetMeasurement(const MuMpcMonitorObject* self, MuMpcMeasurementNameId measurement_name_id) { - MuMpcMonitor* const monitor = MU_MPC_MONITOR(self); - - const MuMpcMonitorNameId monitor_name = monitor->name & (MuMpcMonitorNameId)kMpcMonitorNameIdMask; - - if (monitor_name != (MuMpcMonitorNameId)((uint8_t)measurement_name_id & (uint8_t)kMpcMonitorNameIdMask)) { - return NULL; // Measurement not found in this monitor - } - - for (size_t i = 0; i < VectorGetSize(&monitor->measurements); ++i) { - MuMpcMeasurementObject* const measurement = (MuMpcMeasurementObject*)VectorGetElement(&monitor->measurements, i); - if (MU_MPC_MEASUREMENT_GET_NAME(measurement) == measurement_name_id) { - return measurement; // Measurement found - } - } - - return NULL; -} - -EebusError FlushMeasurementCache(MuMpcMonitorObject* self, MeasurementListDataType* measurement_data_list) { - MuMpcMonitor* const monitor = MU_MPC_MONITOR(self); - - if (measurement_data_list == NULL) { - return kEebusErrorInputArgumentNull; - } - - for (size_t i = 0; i < VectorGetSize(&monitor->measurements); ++i) { - MuMpcMeasurementObject* const measurement = (MuMpcMeasurementObject*)VectorGetElement(&monitor->measurements, i); - - MeasurementDataType* data = MU_MPC_MEASUREMENT_RELEASE_DATA_CACHE(measurement); - if (data != NULL) { - EebusError err = EebusDataListDataAppend( - (void***)&measurement_data_list->measurement_data, - &measurement_data_list->measurement_data_size, - data - ); - - if (err != kEebusErrorOk) { - MeasurementDataDelete(data); - return err; - } - } - } - - return kEebusErrorOk; -} +#include "src/use_case/actor/common/eebus_monitor_base.h" //-------------------------------------------------------------------------------------------// // // MuMpcMonitorPower Object Creation (Scenario 1) // //-------------------------------------------------------------------------------------------// -uint8_t GetConnectedPhases(const MuMpcMonitorPowerConfig* cfg) { + +static uint8_t GetConnectedPhases(const MuMpcMonitorPowerConfig* cfg) { ElectricalConnectionPhaseNameType connected_phases = 0; if (cfg->power_phase_a_cfg != NULL) { @@ -259,48 +48,45 @@ uint8_t GetConnectedPhases(const MuMpcMonitorPowerConfig* cfg) { return connected_phases; } -EebusError MuMpcMonitorPowerConstruct(MuMpcMonitor* self, const MuMpcMonitorPowerConfig* cfg) { - MuMpcMonitorConstruct(self, kMpcMonitorPower); - +EebusMonitorObject* MuMpcMonitorPowerCreate(const MuMpcMonitorPowerConfig* cfg) { if (cfg == NULL) { - return kEebusErrorInputArgumentNull; + return NULL; + } + + EebusMonitorBase* const base = (EebusMonitorBase*)EEBUS_MALLOC(sizeof(EebusMonitorBase)); + if (base == NULL) { + return NULL; } - const uint8_t connected_phases = GetConnectedPhases(cfg); - MuMpcMeasurementObject* const power_measurement_total - = MuMpcMeasurementPowerTotalCreate(connected_phases, &cfg->power_total_cfg); - if (power_measurement_total == NULL) { - return kEebusErrorInit; + if (EebusMonitorBaseConstruct(base, kMpcMonitorPower, kMpcMonitorNameIdMask, MuMpcMeasurementCreate) + != kEebusErrorOk) { + EebusMonitorDelete(EEBUS_MONITOR_OBJECT(base)); + return NULL; + } + + const uint8_t connected_phases = GetConnectedPhases(cfg); + EebusMeasurementObject* const power_total = MuMpcMeasurementPowerTotalCreate(connected_phases, &cfg->power_total_cfg); + if (power_total == NULL) { + EebusMonitorDelete(EEBUS_MONITOR_OBJECT(base)); + return NULL; } - VectorPushBack(&self->measurements, power_measurement_total); + VectorPushBack(&base->measurements, power_total); if (connected_phases != 0) { - MeasurementParameters measurement_params[] = { + const EebusMonitorMeasurementParam params[] = { {kMpcPowerPhaseA, cfg->power_phase_a_cfg}, {kMpcPowerPhaseB, cfg->power_phase_b_cfg}, {kMpcPowerPhaseC, cfg->power_phase_c_cfg}, }; - return AddMeasurements(self, measurement_params, ARRAY_SIZE(measurement_params)); - } - - return kEebusErrorOk; -} - -MuMpcMonitorObject* MuMpcMonitorPowerCreate(const MuMpcMonitorPowerConfig* cfg) { - MuMpcMonitor* const mu_mpc_monitor = (MuMpcMonitor*)EEBUS_MALLOC(sizeof(MuMpcMonitor)); - if (mu_mpc_monitor == NULL) { - return NULL; - } - - EebusError err = MuMpcMonitorPowerConstruct(mu_mpc_monitor, cfg); - if (err != kEebusErrorOk) { - MuMpcMonitorDelete(MU_MPC_MONITOR_OBJECT(mu_mpc_monitor)); - return NULL; + if (EebusMonitorBaseAddMeasurements(base, params, ARRAY_SIZE(params)) != kEebusErrorOk) { + EebusMonitorDelete(EEBUS_MONITOR_OBJECT(base)); + return NULL; + } } - return MU_MPC_MONITOR_OBJECT(mu_mpc_monitor); + return EEBUS_MONITOR_OBJECT(base); } //-------------------------------------------------------------------------------------------// @@ -308,7 +94,7 @@ MuMpcMonitorObject* MuMpcMonitorPowerCreate(const MuMpcMonitorPowerConfig* cfg) // MuMpcMonitorEnergy Object Creation (Scenario 2) // //-------------------------------------------------------------------------------------------// -MuMpcMonitorObject* MuMpcMonitorEnergyCreate(const MuMpcMonitorEnergyConfig* cfg) { +EebusMonitorObject* MuMpcMonitorEnergyCreate(const MuMpcMonitorEnergyConfig* cfg) { if (cfg == NULL) { return NULL; } @@ -317,12 +103,18 @@ MuMpcMonitorObject* MuMpcMonitorEnergyCreate(const MuMpcMonitorEnergyConfig* cfg return NULL; } - const MeasurementParameters measurement_params[] = { + const EebusMonitorMeasurementParam params[] = { {kMpcEnergyConsumed, cfg->energy_consumption_cfg}, {kMpcEnergyProduced, cfg->energy_production_cfg}, }; - return MuMpcMonitorCreate(kMpcMonitorEnergy, measurement_params, ARRAY_SIZE(measurement_params)); + return EebusMonitorCreate( + kMpcMonitorEnergy, + kMpcMonitorNameIdMask, + MuMpcMeasurementCreate, + params, + ARRAY_SIZE(params) + ); } //-------------------------------------------------------------------------------------------// @@ -330,7 +122,7 @@ MuMpcMonitorObject* MuMpcMonitorEnergyCreate(const MuMpcMonitorEnergyConfig* cfg // MuMpcMonitorCurrent Object Creation (Scenario 3) // //-------------------------------------------------------------------------------------------// -MuMpcMonitorObject* MuMpcMonitorCurrentCreate(const MuMpcMonitorCurrentConfig* cfg) { +EebusMonitorObject* MuMpcMonitorCurrentCreate(const MuMpcMonitorCurrentConfig* cfg) { if (cfg == NULL) { return NULL; } @@ -339,13 +131,19 @@ MuMpcMonitorObject* MuMpcMonitorCurrentCreate(const MuMpcMonitorCurrentConfig* c return NULL; } - const MeasurementParameters measurement_params[] = { + const EebusMonitorMeasurementParam params[] = { {kMpcCurrentPhaseA, cfg->current_phase_a_cfg}, {kMpcCurrentPhaseB, cfg->current_phase_b_cfg}, {kMpcCurrentPhaseC, cfg->current_phase_c_cfg}, }; - return MuMpcMonitorCreate(kMpcMonitorCurrent, measurement_params, ARRAY_SIZE(measurement_params)); + return EebusMonitorCreate( + kMpcMonitorCurrent, + kMpcMonitorNameIdMask, + MuMpcMeasurementCreate, + params, + ARRAY_SIZE(params) + ); } //-------------------------------------------------------------------------------------------// @@ -353,30 +151,27 @@ MuMpcMonitorObject* MuMpcMonitorCurrentCreate(const MuMpcMonitorCurrentConfig* c // MuMpcMonitorVoltage Object Creation (Scenario 4) // //-------------------------------------------------------------------------------------------// -MuMpcMonitorObject* MuMpcMonitorVoltageCreate(const MuMpcMonitorVoltageConfig* cfg) { +EebusMonitorObject* MuMpcMonitorVoltageCreate(const MuMpcMonitorVoltageConfig* cfg) { if (cfg == NULL) { return NULL; } - // AB voltage configuration check if ((cfg->voltage_phase_ab_cfg != NULL) && ((cfg->voltage_phase_a_cfg == NULL) || (cfg->voltage_phase_b_cfg == NULL))) { return NULL; } - // BC voltage configuration check if ((cfg->voltage_phase_bc_cfg != NULL) && ((cfg->voltage_phase_b_cfg == NULL) || (cfg->voltage_phase_c_cfg == NULL))) { return NULL; } - // AC voltage configuration check if ((cfg->voltage_phase_ac_cfg != NULL) && ((cfg->voltage_phase_c_cfg == NULL) || (cfg->voltage_phase_a_cfg == NULL))) { return NULL; } - const MeasurementParameters measurement_params[] = { + const EebusMonitorMeasurementParam params[] = { { kMpcVoltagePhaseA, cfg->voltage_phase_a_cfg}, { kMpcVoltagePhaseB, cfg->voltage_phase_b_cfg}, { kMpcVoltagePhaseC, cfg->voltage_phase_c_cfg}, @@ -385,7 +180,13 @@ MuMpcMonitorObject* MuMpcMonitorVoltageCreate(const MuMpcMonitorVoltageConfig* c {kMpcVoltagePhaseAc, cfg->voltage_phase_ac_cfg}, }; - return MuMpcMonitorCreate(kMpcMonitorVoltage, measurement_params, ARRAY_SIZE(measurement_params)); + return EebusMonitorCreate( + kMpcMonitorVoltage, + kMpcMonitorNameIdMask, + MuMpcMeasurementCreate, + params, + ARRAY_SIZE(params) + ); } //-------------------------------------------------------------------------------------------// @@ -393,14 +194,20 @@ MuMpcMonitorObject* MuMpcMonitorVoltageCreate(const MuMpcMonitorVoltageConfig* c // MuMpcMonitorFrequency Object Creation (Scenario 5) // //-------------------------------------------------------------------------------------------// -MuMpcMonitorObject* MuMpcMonitorFrequencyCreate(const MuMpcMonitorFrequencyConfig* cfg) { +EebusMonitorObject* MuMpcMonitorFrequencyCreate(const MuMpcMonitorFrequencyConfig* cfg) { if (cfg == NULL) { return NULL; } - const MeasurementParameters measurement_params[] = { + const EebusMonitorMeasurementParam params[] = { {kMpcFrequency, &cfg->frequency_cfg}, }; - return MuMpcMonitorCreate(kMpcMonitorFrequency, measurement_params, ARRAY_SIZE(measurement_params)); + return EebusMonitorCreate( + kMpcMonitorFrequency, + kMpcMonitorNameIdMask, + MuMpcMeasurementCreate, + params, + ARRAY_SIZE(params) + ); } diff --git a/src/use_case/actor/mu/mpc/mu_mpc_monitor.c.tmp b/src/use_case/actor/mu/mpc/mu_mpc_monitor.c.tmp deleted file mode 100644 index 9784754..0000000 --- a/src/use_case/actor/mu/mpc/mu_mpc_monitor.c.tmp +++ /dev/null @@ -1,6 +0,0 @@ -MuMpcMonitor -void Destruct() -MonitorNameId GetName() const -EebusError Configure(MeasurementServer* msrv, ElectricalConnectionServer* ecsrv, ElectricalConnectionIdType electrical_connection_id, MeasurementConstraintsListDataType* measurements_constraints) -MeasurementObject* GetMeasurement(MeasurementNameId measurement_name_id) const -EebusError FlushMeasurementCache(MeasurementListDataType* measurement_data_list) diff --git a/src/use_case/actor/mu/mpc/mu_mpc_monitor.h b/src/use_case/actor/mu/mpc/mu_mpc_monitor.h index 3e3eba8..89f1476 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc_monitor.h +++ b/src/use_case/actor/mu/mpc/mu_mpc_monitor.h @@ -24,7 +24,7 @@ * Scenario 4: MuMpcMonitorVoltageCreate(); * Scenario 5: MuMpcMonitorFrequencyCreate(). * - * Use MuMpcMonitorDelete() to delete the created MU MPC Monitor instance + * Use EebusMonitorDelete() to delete the created MU MPC Monitor instance */ #ifndef SRC_USE_CASE_ACTOR_MU_MU_MPC_MONITOR_H_ @@ -32,28 +32,14 @@ #include -#include "src/common/eebus_malloc.h" +#include "src/use_case/actor/common/eebus_monitor_base.h" #include "src/use_case/actor/mu/mpc/mu_mpc_measurement.h" -#include "src/use_case/api/mu_mpc_monitor_interface.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus -/** - * MuMpcMonitorCurrentConfig is the configuration for the monitor use case - * If this config is passed via NewMPC, the use case will support current monitoring as specified - */ -typedef struct MuMpcMonitorCurrentConfig MuMpcMonitorCurrentConfig; - -struct MuMpcMonitorCurrentConfig { - /** Phase A AC Current measurement configuration */ - const MuMpcMeasurementConfig* current_phase_a_cfg; - /** Phase B AC Current measurement configuration */ - const MuMpcMeasurementConfig* current_phase_b_cfg; - /** Phase C AC Current measurement configuration */ - const MuMpcMeasurementConfig* current_phase_c_cfg; -}; +typedef EebusMonitorCurrentConfig MuMpcMonitorCurrentConfig; /** * MuMpcMonitorEnergyConfig is the configuration for the monitor use case @@ -69,16 +55,7 @@ struct MuMpcMonitorEnergyConfig { const MuMpcMeasurementConfig* energy_consumption_cfg; }; -/** - * MuMpcMonitorFrequencyConfig is the configuration for the monitor use case - * If this config is passed via NewMPC, the use case will support frequency monitoring as specified - */ -typedef struct MuMpcMonitorFrequencyConfig MuMpcMonitorFrequencyConfig; - -struct MuMpcMonitorFrequencyConfig { - /** The source of the production values (if not NULL is set, the use case will support production) */ - MuMpcMeasurementConfig frequency_cfg; -}; +typedef EebusMonitorFrequencyConfig MuMpcMonitorFrequencyConfig; /** * MuMpcMonitorPowerConfig is the configuration for the monitor use case @@ -98,84 +75,42 @@ struct MuMpcMonitorPowerConfig { const MuMpcMeasurementConfig* power_phase_c_cfg; }; -/** - * MuMpcMonitorVoltageConfig is the configuration for the monitor use case - * This config is required by the mpc use case and must be used in mpc.NewMPC - */ -typedef struct MuMpcMonitorVoltageConfig MuMpcMonitorVoltageConfig; - -struct MuMpcMonitorVoltageConfig { - /** Phase A AC Voltage measurement configuration (required if the phase is supported) */ - const MuMpcMeasurementConfig* voltage_phase_a_cfg; - /** Phase B AC Voltage measurement configuration (required if the phase is supported) */ - const MuMpcMeasurementConfig* voltage_phase_b_cfg; - /** Phase C AC Voltage measurement configuration (required if the phase is supported) */ - const MuMpcMeasurementConfig* voltage_phase_c_cfg; - - /** - * Phase A to B measurement configuration - * (can be used only if the both related phases are supported) - */ - const MuMpcMeasurementConfig* voltage_phase_ab_cfg; - - /** - * Phase B to C measurement configuration - * (can be used only if the both related phases are supported) - */ - const MuMpcMeasurementConfig* voltage_phase_bc_cfg; - - /** - * Phase C to A measurement configuration - * (can be used only if the both related phases are supported) - */ - const MuMpcMeasurementConfig* voltage_phase_ac_cfg; -}; +typedef EebusMonitorVoltageConfig MuMpcMonitorVoltageConfig; /** - * @brief Creates a new MuMpcMonitorObject for the current monitoring (Scenario 1) - * @param cfg The configuration for the current monitoring use case - * @return A pointer to the created MuMpcMonitorObject, or NULL if creation failed + * @brief Creates a new EebusMonitorObject for the power monitoring (Scenario 1) + * @param cfg The configuration for the power monitoring use case + * @return A pointer to the created EebusMonitorObject, or NULL if creation failed */ -MuMpcMonitorObject* MuMpcMonitorPowerCreate(const MuMpcMonitorPowerConfig* cfg); +EebusMonitorObject* MuMpcMonitorPowerCreate(const MuMpcMonitorPowerConfig* cfg); /** - * @brief Creates a new MuMpcMonitorObject for the energy monitoring (Scenario 2) + * @brief Creates a new EebusMonitorObject for the energy monitoring (Scenario 2) * @param cfg The configuration for the energy monitoring use case - * @return A pointer to the created MuMpcMonitorObject, or NULL if creation failed + * @return A pointer to the created EebusMonitorObject, or NULL if creation failed */ -MuMpcMonitorObject* MuMpcMonitorEnergyCreate(const MuMpcMonitorEnergyConfig* cfg); +EebusMonitorObject* MuMpcMonitorEnergyCreate(const MuMpcMonitorEnergyConfig* cfg); /** - * @brief Creates a new MuMpcMonitorObject for the current monitoring (Scenario 3) + * @brief Creates a new EebusMonitorObject for the current monitoring (Scenario 3) * @param cfg The configuration for the current monitoring use case - * @return A pointer to the created MuMpcMonitorObject, or NULL if creation failed + * @return A pointer to the created EebusMonitorObject, or NULL if creation failed */ -MuMpcMonitorObject* MuMpcMonitorCurrentCreate(const MuMpcMonitorCurrentConfig* cfg); +EebusMonitorObject* MuMpcMonitorCurrentCreate(const MuMpcMonitorCurrentConfig* cfg); /** - * @brief Creates a new MuMpcMonitorObject for the voltage monitoring (Scenario 4) + * @brief Creates a new EebusMonitorObject for the voltage monitoring (Scenario 4) * @param cfg The configuration for the voltage monitoring use case - * @return A pointer to the created MuMpcMonitorObject, or NULL if creation failed + * @return A pointer to the created EebusMonitorObject, or NULL if creation failed */ -MuMpcMonitorObject* MuMpcMonitorVoltageCreate(const MuMpcMonitorVoltageConfig* cfg); +EebusMonitorObject* MuMpcMonitorVoltageCreate(const MuMpcMonitorVoltageConfig* cfg); /** - * @brief Creates a new MuMpcMonitorObject for the frequency monitoring (Scenario 5) + * @brief Creates a new EebusMonitorObject for the frequency monitoring (Scenario 5) * @param cfg The configuration for the frequency monitoring use case - * @return A pointer to the created MuMpcMonitorObject, or NULL if creation failed + * @return A pointer to the created EebusMonitorObject, or NULL if creation failed */ -MuMpcMonitorObject* MuMpcMonitorFrequencyCreate(const MuMpcMonitorFrequencyConfig* cfg); - -/** - * @brief Destructs the MuMpcMonitorObject instance and frees the memory - * @param mu_mpc_monitor Pointer to the MuMpcMonitorObject instance to be deleted - */ -static inline void MuMpcMonitorDelete(MuMpcMonitorObject* mu_mpc_monitor) { - if (mu_mpc_monitor != NULL) { - MU_MPC_MONITOR_DESTRUCT(mu_mpc_monitor); - EEBUS_FREE(mu_mpc_monitor); - } -} +EebusMonitorObject* MuMpcMonitorFrequencyCreate(const MuMpcMonitorFrequencyConfig* cfg); #ifdef __cplusplus } diff --git a/src/use_case/actor/mu/mpc/mu_mpc_public.c b/src/use_case/actor/mu/mpc/mu_mpc_public.c index ecc7678..2135671 100644 --- a/src/use_case/actor/mu/mpc/mu_mpc_public.c +++ b/src/use_case/actor/mu/mpc/mu_mpc_public.c @@ -13,99 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "src/common/array_util.h" #include "src/use_case/actor/mu/mpc/mu_mpc.h" #include "src/use_case/actor/mu/mpc/mu_mpc_internal.h" -#include "src/use_case/model/scaled_value.h" -#include "src/use_case/specialization/measurement/measurement_server.h" -#include "src/use_case/use_case.h" - -MuMpcMonitorObject* GetMonitor(const MuMpcUseCase* self, MuMpcMeasurementNameId measurement_name) { - const MuMpcMonitorNameId monitor_name - = (MuMpcMonitorNameId)((uint8_t)measurement_name & (uint8_t)kMpcMonitorNameIdMask); - - for (size_t i = 0; i < VectorGetSize(&self->monitors); ++i) { - MuMpcMonitorObject* const mu_mpc_monitor = (MuMpcMonitorObject*)VectorGetElement(&self->monitors, i); - if (MU_MPC_MONITOR_GET_NAME(mu_mpc_monitor) == monitor_name) { - return mu_mpc_monitor; - } - } - - return NULL; -} - -MuMpcMeasurementObject* GetMeasurement(const MuMpcUseCase* self, MuMpcMeasurementNameId measurement_name) { - const MuMpcMonitorObject* const monitor = GetMonitor(self, measurement_name); - if (monitor == NULL) { - return NULL; - } - - return MU_MPC_MONITOR_GET_MEASUREMENT(monitor, measurement_name); -} - -EebusError MuMpcGetMeasurementDataInternal( - const MuMpcUseCase* self, - const MuMpcMeasurementNameId measurement_element_id, - ScaledValue* measurement_value -) { - const UseCase* const use_case = USE_CASE(self); - - MuMpcMeasurementObject* measurement = GetMeasurement(self, measurement_element_id); - if (measurement == NULL) { - return kEebusErrorNotSupported; - } - - MeasurementServer msrv = {0}; - - EebusError err = MeasurementServerConstruct(&msrv, use_case->local_entity); - if (err != kEebusErrorOk) { - return err; - } - - return MU_MPC_MEASUREMENT_GET_DATA_VALUE(measurement, &msrv, measurement_value); -} EebusError MuMpcGetMeasurementData( const MuMpcUseCaseObject* self, MuMpcMeasurementNameId measurement_element_id, ScaledValue* measurement_value ) { - const UseCase* const use_case = USE_CASE(self); - if (measurement_value == NULL) { return kEebusErrorInputArgumentNull; } - EebusError err = kEebusErrorOk; - - DEVICE_LOCAL_LOCK(use_case->local_device); - err = MuMpcGetMeasurementDataInternal(MU_MPC_USE_CASE(self), measurement_element_id, measurement_value); - DEVICE_LOCAL_UNLOCK(use_case->local_device); - - return err; -} - -EebusError MuMpcSetMeasurementDataCacheWithTime( - MuMpcUseCase* self, - MuMpcMeasurementNameId measurement_name, - const ScaledValue* measurement_value, - const EebusDateTime* timestamp, - const MeasurementValueStateType* value_state, - const EebusDateTime* start_time, - const EebusDateTime* end_time -) { - MuMpcMeasurementObject* measurement = GetMeasurement(self, measurement_name); - if (measurement == NULL) { - return kEebusErrorNotSupported; - } - - EebusError err = kEebusErrorOk; - - EEBUS_MUTEX_LOCK(self->mutex); - err = MU_MPC_MEASUREMENT_SET_DATA_CACHE(measurement, measurement_value, timestamp, value_state, start_time, end_time); - EEBUS_MUTEX_UNLOCK(self->mutex); - - return err; + MuMpcUseCase* const mu_mpc = MU_MPC_USE_CASE(self); + const UseCase* const use_case = USE_CASE(self); + return EebusMonitorContainerGetMeasurementData( + &mu_mpc->monitor_container, + use_case->local_entity, + use_case->local_device, + measurement_element_id, + measurement_value + ); } EebusError MuMpcSetMeasurementDataCache( @@ -119,53 +47,20 @@ EebusError MuMpcSetMeasurementDataCache( return kEebusErrorInputArgumentNull; } - return MuMpcSetMeasurementDataCacheWithTime( - MU_MPC_USE_CASE(self), + MuMpcUseCase* const mu_mpc = MU_MPC_USE_CASE(self); + return EebusMonitorContainerSetMeasurementDataCache( + &mu_mpc->monitor_container, measurement_name, measurement_value, timestamp, - value_state, - NULL, - NULL + value_state ); } EebusError MuMpcUpdate(const MuMpcUseCaseObject* self) { - UseCase* const use_case = USE_CASE(self); - MuMpcUseCase* const mu_mpc = MU_MPC_USE_CASE(self); - - MeasurementServer msrv = {0}; - - EebusError err = MeasurementServerConstruct(&msrv, use_case->local_entity); - if (err != kEebusErrorOk) { - return err; - } - - MeasurementListDataType* const measurement_data_list = MeasurementsCreateEmpty(); - if (measurement_data_list == NULL) { - return kEebusErrorMemoryAllocate; - } - - EEBUS_MUTEX_LOCK(mu_mpc->mutex); - for (size_t i = 0; i < VectorGetSize(&mu_mpc->monitors); ++i) { - MuMpcMonitorObject* const mu_mpc_monitor = (MuMpcMonitorObject*)VectorGetElement(&mu_mpc->monitors, i); - EebusError err = MU_MPC_MONITOR_FLUSH_MEASUREMENT_CACHE(mu_mpc_monitor, measurement_data_list); - if (err != kEebusErrorOk) { - EEBUS_MUTEX_UNLOCK(mu_mpc->mutex); - return err; - } - } - - EEBUS_MUTEX_UNLOCK(mu_mpc->mutex); - - if (measurement_data_list->measurement_data_size > 0) { - DEVICE_LOCAL_LOCK(use_case->local_device); - err = MeasurementServerUpdateMeasurements(&msrv, measurement_data_list, NULL, NULL); - DEVICE_LOCAL_UNLOCK(use_case->local_device); - } - - MeasurementsDelete(measurement_data_list); - return kEebusErrorOk; + MuMpcUseCase* const mu_mpc = MU_MPC_USE_CASE(self); + const UseCase* const use_case = USE_CASE(self); + return EebusMonitorContainerUpdate(&mu_mpc->monitor_container, use_case->local_entity, use_case->local_device); } EebusError MuMpcSetEnergyConsumedCache( @@ -177,8 +72,8 @@ EebusError MuMpcSetEnergyConsumedCache( const EebusDateTime* end_time ) { MuMpcUseCase* const mu_mpc = MU_MPC_USE_CASE(self); - return MuMpcSetMeasurementDataCacheWithTime( - mu_mpc, + return EebusMonitorContainerSetMeasurementDataCacheWithTime( + &mu_mpc->monitor_container, kMpcEnergyConsumed, energy_consumed, timestamp, @@ -197,8 +92,8 @@ EebusError MuMpcSetEnergyProducedCache( const EebusDateTime* end_time ) { MuMpcUseCase* const mu_mpc = MU_MPC_USE_CASE(self); - return MuMpcSetMeasurementDataCacheWithTime( - mu_mpc, + return EebusMonitorContainerSetMeasurementDataCacheWithTime( + &mu_mpc->monitor_container, kMpcEnergyProduced, energy_produced, timestamp, diff --git a/src/use_case/api/eebus_measurement_interface.h b/src/use_case/api/eebus_measurement_interface.h new file mode 100644 index 0000000..1280aac --- /dev/null +++ b/src/use_case/api/eebus_measurement_interface.h @@ -0,0 +1,96 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Eebus Measurement interface shared between MU MPC and GCP MGCP use cases + */ + +#ifndef SRC_USE_CASE_API_EEBUS_MEASUREMENT_INTERFACE_H_ +#define SRC_USE_CASE_API_EEBUS_MEASUREMENT_INTERFACE_H_ + +#include "src/common/eebus_errors.h" +#include "src/spine/model/common_data_types.h" +#include "src/spine/model/measurement_types.h" +#include "src/use_case/model/eebus_measurement_types.h" +#include "src/use_case/model/scaled_value.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" +#include "src/use_case/specialization/measurement/measurement_server.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct EebusMeasurementInterface EebusMeasurementInterface; +typedef struct EebusMeasurementObject EebusMeasurementObject; + +struct EebusMeasurementInterface { + void (*destruct)(EebusMeasurementObject* self); + EebusMeasurementNameId (*get_name)(const EebusMeasurementObject* self); + EebusError (*get_data_value)( + const EebusMeasurementObject* self, + MeasurementServer* msrv, + ScaledValue* measurement_value + ); + const MeasurementConstraintsDataType* (*get_constraints)(const EebusMeasurementObject* self); + EebusError (*configure)( + EebusMeasurementObject* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType electrical_connection_id + ); + EebusError (*set_data_cache)( + EebusMeasurementObject* self, + const ScaledValue* measured_value, + const EebusDateTime* timestamp, + const MeasurementValueStateType* value_state, + const EebusDateTime* start_time, + const EebusDateTime* end_time + ); + MeasurementDataType* (*release_data_cache)(EebusMeasurementObject* self); +}; + +struct EebusMeasurementObject { + const EebusMeasurementInterface* interface_; +}; + +#define EEBUS_MEASUREMENT_OBJECT(obj) ((EebusMeasurementObject*)(obj)) +#define EEBUS_MEASUREMENT_INTERFACE(obj) (EEBUS_MEASUREMENT_OBJECT(obj)->interface_) + +#define EEBUS_MEASUREMENT_DESTRUCT(obj) (EEBUS_MEASUREMENT_INTERFACE(obj)->destruct(EEBUS_MEASUREMENT_OBJECT(obj))) + +#define EEBUS_MEASUREMENT_GET_NAME(obj) (EEBUS_MEASUREMENT_INTERFACE(obj)->get_name(EEBUS_MEASUREMENT_OBJECT(obj))) + +#define EEBUS_MEASUREMENT_GET_DATA_VALUE(obj, msrv, measurement_value) \ + (EEBUS_MEASUREMENT_INTERFACE(obj)->get_data_value(EEBUS_MEASUREMENT_OBJECT(obj), msrv, measurement_value)) + +#define EEBUS_MEASUREMENT_GET_CONSTRAINTS(obj) \ + (EEBUS_MEASUREMENT_INTERFACE(obj)->get_constraints(EEBUS_MEASUREMENT_OBJECT(obj))) + +#define EEBUS_MEASUREMENT_CONFIGURE(obj, msrv, ecsrv, electrical_connection_id) \ + (EEBUS_MEASUREMENT_INTERFACE(obj)->configure(EEBUS_MEASUREMENT_OBJECT(obj), msrv, ecsrv, electrical_connection_id)) + +#define EEBUS_MEASUREMENT_SET_DATA_CACHE(obj, measured_value, timestamp, value_state, start_time, end_time) \ + (EEBUS_MEASUREMENT_INTERFACE(obj) \ + ->set_data_cache(EEBUS_MEASUREMENT_OBJECT(obj), measured_value, timestamp, value_state, start_time, end_time)) + +#define EEBUS_MEASUREMENT_RELEASE_DATA_CACHE(obj) \ + (EEBUS_MEASUREMENT_INTERFACE(obj)->release_data_cache(EEBUS_MEASUREMENT_OBJECT(obj))) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_API_EEBUS_MEASUREMENT_INTERFACE_H_ diff --git a/src/use_case/api/eebus_monitor_interface.h b/src/use_case/api/eebus_monitor_interface.h new file mode 100644 index 0000000..a7f6d19 --- /dev/null +++ b/src/use_case/api/eebus_monitor_interface.h @@ -0,0 +1,146 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Eebus Monitor interface shared between MU MPC and GCP MGCP use cases + */ + +#ifndef SRC_USE_CASE_API_EEBUS_MONITOR_INTERFACE_H_ +#define SRC_USE_CASE_API_EEBUS_MONITOR_INTERFACE_H_ + +#include "src/common/eebus_errors.h" +#include "src/use_case/actor/common/eebus_measurement_base.h" +#include "src/use_case/api/eebus_measurement_interface.h" +#include "src/use_case/model/eebus_measurement_types.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" +#include "src/use_case/specialization/measurement/measurement_server.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * @brief Per-phase AC current monitor configuration. + * At least one phase pointer must be non-NULL. + */ +typedef struct EebusMonitorCurrentConfig { + const EebusMeasurementBaseConfig* current_phase_a_cfg; + const EebusMeasurementBaseConfig* current_phase_b_cfg; + const EebusMeasurementBaseConfig* current_phase_c_cfg; +} EebusMonitorCurrentConfig; + +/** + * @brief Per-phase AC voltage monitor configuration. + * + * Phase-to-phase entries (AB/BC/AC) may only be non-NULL when both related + * phase-to-neutral entries are also non-NULL. At least one entry must be non-NULL. + */ +typedef struct EebusMonitorVoltageConfig { + const EebusMeasurementBaseConfig* voltage_phase_a_cfg; + const EebusMeasurementBaseConfig* voltage_phase_b_cfg; + const EebusMeasurementBaseConfig* voltage_phase_c_cfg; + const EebusMeasurementBaseConfig* voltage_phase_ab_cfg; + const EebusMeasurementBaseConfig* voltage_phase_bc_cfg; + const EebusMeasurementBaseConfig* voltage_phase_ac_cfg; +} EebusMonitorVoltageConfig; + +/** + * @brief AC frequency monitor configuration. + */ +typedef struct EebusMonitorFrequencyConfig { + EebusMeasurementBaseConfig frequency_cfg; +} EebusMonitorFrequencyConfig; + +/** + * @brief Eebus Monitor Interface + * (Eebus Monitor "virtual functions table" declaration) + */ +typedef struct EebusMonitorInterface EebusMonitorInterface; + +/** + * @brief Eebus Monitor Object type definition + * ("abstract class", has no members but only pointer to + * "virtual functions table") + */ +typedef struct EebusMonitorObject EebusMonitorObject; + +/** + * @brief EebusMonitor Interface Structure + */ +struct EebusMonitorInterface { + void (*destruct)(EebusMonitorObject* self); + EebusMeasurementMonitorNameId (*get_name)(const EebusMonitorObject* self); + EebusError (*configure)( + EebusMonitorObject* self, + MeasurementServer* msrv, + ElectricalConnectionServer* ecsrv, + ElectricalConnectionIdType ec_id, + MeasurementConstraintsListDataType* constraints + ); + EebusMeasurementObject* (*get_measurement)(const EebusMonitorObject* self, EebusMeasurementNameId name_id); + EebusError (*flush_measurement_cache)(EebusMonitorObject* self, MeasurementListDataType* list); +}; + +/** + * @brief Eebus Monitor Object Structure + */ +struct EebusMonitorObject { + const EebusMonitorInterface* interface_; +}; + +/** + * @brief Eebus Monitor pointer typecast + */ +#define EEBUS_MONITOR_OBJECT(obj) ((EebusMonitorObject*)(obj)) + +/** + * @brief Eebus Monitor Interface class pointer typecast + */ +#define EEBUS_MONITOR_INTERFACE(obj) (EEBUS_MONITOR_OBJECT(obj)->interface_) + +/** + * @brief Eebus Monitor Destruct caller definition + */ +#define EEBUS_MONITOR_DESTRUCT(obj) (EEBUS_MONITOR_INTERFACE(obj)->destruct(EEBUS_MONITOR_OBJECT(obj))) + +/** + * @brief Eebus Monitor Get Name caller definition + */ +#define EEBUS_MONITOR_GET_NAME(obj) (EEBUS_MONITOR_INTERFACE(obj)->get_name(EEBUS_MONITOR_OBJECT(obj))) + +/** + * @brief Eebus Monitor Configure caller definition + */ +#define EEBUS_MONITOR_CONFIGURE(obj, msrv, ecsrv, ec_id, constraints) \ + (EEBUS_MONITOR_INTERFACE(obj)->configure(EEBUS_MONITOR_OBJECT(obj), msrv, ecsrv, ec_id, constraints)) + +/** + * @brief Eebus Monitor Get Measurement caller definition + */ +#define EEBUS_MONITOR_GET_MEASUREMENT(obj, name_id) \ + (EEBUS_MONITOR_INTERFACE(obj)->get_measurement(EEBUS_MONITOR_OBJECT(obj), name_id)) + +/** + * @brief Eebus Monitor Flush Measurement Cache caller definition + */ +#define EEBUS_MONITOR_FLUSH_MEASUREMENT_CACHE(obj, list) \ + (EEBUS_MONITOR_INTERFACE(obj)->flush_measurement_cache(EEBUS_MONITOR_OBJECT(obj), list)) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_API_EEBUS_MONITOR_INTERFACE_H_ diff --git a/src/use_case/api/ma_measurement_interface.h b/src/use_case/api/ma_measurement_interface.h new file mode 100644 index 0000000..02b9f77 --- /dev/null +++ b/src/use_case/api/ma_measurement_interface.h @@ -0,0 +1,61 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA Measurement interface shared between MPC and MGCP monitoring use cases + */ + +#ifndef SRC_USE_CASE_API_MA_MEASUREMENT_INTERFACE_H_ +#define SRC_USE_CASE_API_MA_MEASUREMENT_INTERFACE_H_ + +#include "src/common/eebus_errors.h" +#include "src/use_case/model/eebus_measurement_types.h" +#include "src/use_case/model/scaled_value.h" +#include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" +#include "src/use_case/specialization/measurement/measurement_client.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct MaMeasurementInterface MaMeasurementInterface; +typedef struct MaMeasurementObject MaMeasurementObject; + +struct MaMeasurementInterface { + EebusMeasurementNameId (*get_name)(const MaMeasurementObject* self); + EebusError (*get_data)( + const MaMeasurementObject* self, + EntityLocalObject* local_entity, + EntityRemoteObject* remote_entity, + ScaledValue* measurement_value + ); +}; + +struct MaMeasurementObject { + const MaMeasurementInterface* interface_; +}; + +#define MA_MEASUREMENT_OBJECT(obj) ((MaMeasurementObject*)(obj)) +#define MA_MEASUREMENT_INTERFACE(obj) (MA_MEASUREMENT_OBJECT(obj)->interface_) +#define MA_MEASUREMENT_GET_NAME(obj) (MA_MEASUREMENT_INTERFACE(obj)->get_name(MA_MEASUREMENT_OBJECT(obj))) +#define MA_MEASUREMENT_GET_DATA(obj, local_entity, remote_entity, measurement_value) \ + (MA_MEASUREMENT_INTERFACE(obj)->get_data(MA_MEASUREMENT_OBJECT(obj), local_entity, remote_entity, measurement_value)) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_API_MA_MEASUREMENT_INTERFACE_H_ diff --git a/src/use_case/api/ma_mgcp_listener_interface.h b/src/use_case/api/ma_mgcp_listener_interface.h new file mode 100644 index 0000000..f9b8ff8 --- /dev/null +++ b/src/use_case/api/ma_mgcp_listener_interface.h @@ -0,0 +1,77 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MA MGCP Listener interface declarations + */ + +#ifndef SRC_USE_CASE_API_MA_MGCP_LISTENER_INTERFACE_H_ +#define SRC_USE_CASE_API_MA_MGCP_LISTENER_INTERFACE_H_ + +#include "src/spine/model/entity_types.h" +#include "src/use_case/model/mgcp_types.h" +#include "src/use_case/model/scaled_value.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct MaMgcpListenerInterface MaMgcpListenerInterface; +typedef struct MaMgcpListenerObject MaMgcpListenerObject; + +struct MaMgcpListenerInterface { + void (*destruct)(MaMgcpListenerObject* self); + void (*on_remote_entity_connect)(MaMgcpListenerObject* self, const EntityAddressType* entity_addr); + void (*on_remote_entity_disconnect)(MaMgcpListenerObject* self, const EntityAddressType* entity_addr); + void (*on_measurement_receive)( + MaMgcpListenerObject* self, + GcpMeasurementNameId name_id, + const ScaledValue* measurement_value, + const EntityAddressType* remote_entity_addr + ); + void (*on_pv_curtailment_limit_factor_receive)( + MaMgcpListenerObject* self, + const ScaledValue* value, + const EntityAddressType* remote_entity_addr + ); +}; + +struct MaMgcpListenerObject { + const MaMgcpListenerInterface* interface; +}; + +#define MA_MGCP_LISTENER_OBJECT(obj) ((MaMgcpListenerObject*)(obj)) +#define MA_MGCP_LISTENER_INTERFACE(obj) (MA_MGCP_LISTENER_OBJECT(obj)->interface) + +#define MA_MGCP_LISTENER_DESTRUCT(obj) (MA_MGCP_LISTENER_INTERFACE(obj)->destruct(obj)) + +#define MA_MGCP_LISTENER_ON_REMOTE_ENTITY_CONNECT(obj, entity_addr) \ + (MA_MGCP_LISTENER_INTERFACE(obj)->on_remote_entity_connect(obj, entity_addr)) + +#define MA_MGCP_LISTENER_ON_REMOTE_ENTITY_DISCONNECT(obj, entity_addr) \ + (MA_MGCP_LISTENER_INTERFACE(obj)->on_remote_entity_disconnect(obj, entity_addr)) + +#define MA_MGCP_LISTENER_ON_MEASUREMENT_RECEIVE(obj, name_id, measurement_value, remote_entity_addr) \ + (MA_MGCP_LISTENER_INTERFACE(obj)->on_measurement_receive(obj, name_id, measurement_value, remote_entity_addr)) + +#define MA_MGCP_LISTENER_ON_PV_CURTAILMENT_LIMIT_FACTOR_RECEIVE(obj, value, remote_entity_addr) \ + (MA_MGCP_LISTENER_INTERFACE(obj)->on_pv_curtailment_limit_factor_receive(obj, value, remote_entity_addr)) + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_API_MA_MGCP_LISTENER_INTERFACE_H_ diff --git a/src/use_case/api/ma_mpc_measurement_interface.h b/src/use_case/api/ma_mpc_measurement_interface.h deleted file mode 100644 index 9f1da78..0000000 --- a/src/use_case/api/ma_mpc_measurement_interface.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2025 NIBE AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * @file - * @brief Ma Mpc Measurement interface declarations - */ - -#ifndef SRC_USE_CASE_API_MA_MPC_MEASUREMENT_INTERFACE_H_ -#define SRC_USE_CASE_API_MA_MPC_MEASUREMENT_INTERFACE_H_ - -#include "src/common/eebus_errors.h" -#include "src/use_case/model/mpc_types.h" -#include "src/use_case/model/scaled_value.h" -#include "src/use_case/specialization/electrical_connection/electrical_connection_client.h" -#include "src/use_case/specialization/measurement/measurement_client.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * @brief Ma Mpc Measurement Interface - * (Ma Mpc Measurement "virtual functions table" declaration) - */ -typedef struct MaMpcMeasurementInterface MaMpcMeasurementInterface; - -/** - * @brief Ma Mpc Measurement Object type definition - * ("abstract class", has no members but only pointer to - * "virtual functions table") - */ -typedef struct MaMpcMeasurementObject MaMpcMeasurementObject; - -/** - * @brief MaMpcMeasurement Interface Structure - */ -struct MaMpcMeasurementInterface { - MuMpcMeasurementNameId (*get_name)(const MaMpcMeasurementObject* self); - EebusError (*get_data_value)( - const MaMpcMeasurementObject* self, - MeasurementClient* mcl, - ElectricalConnectionClient* eccl, - ScaledValue* measurement_value - ); -}; - -/** - * @brief Ma Mpc Measurement Object Structure - */ -struct MaMpcMeasurementObject { - const MaMpcMeasurementInterface* interface_; -}; - -/** - * @brief Ma Mpc Measurement pointer typecast - */ -#define MA_MPC_MEASUREMENT_OBJECT(obj) ((MaMpcMeasurementObject*)(obj)) - -/** - * @brief Ma Mpc Measurement Interface class pointer typecast - */ -#define MA_MPC_MEASUREMENT_INTERFACE(obj) (MA_MPC_MEASUREMENT_OBJECT(obj)->interface_) - -/** - * @brief Ma Mpc Measurement Get Name caller definition - */ -#define MA_MPC_MEASUREMENT_GET_NAME(obj) (MA_MPC_MEASUREMENT_INTERFACE(obj)->get_name(obj)) - -/** - * @brief Ma Mpc Measurement Get Data Value caller definition - */ -#define MA_MPC_MEASUREMENT_GET_DATA_VALUE(obj, mcl, eccl, measurement_value) \ - (MA_MPC_MEASUREMENT_INTERFACE(obj)->get_data_value(obj, mcl, eccl, measurement_value)) - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // SRC_USE_CASE_API_MA_MPC_MEASUREMENT_INTERFACE_H_ diff --git a/src/use_case/api/mu_mpc_measurement_interface.h b/src/use_case/api/mu_mpc_measurement_interface.h deleted file mode 100644 index 9723d85..0000000 --- a/src/use_case/api/mu_mpc_measurement_interface.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2025 NIBE AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * @file - * @brief Mu Mpc Measurement interface declarations - */ - -#ifndef SRC_USE_CASE_API_MU_MPC_MEASUREMENT_INTERFACE_H_ -#define SRC_USE_CASE_API_MU_MPC_MEASUREMENT_INTERFACE_H_ - -#include "src/common/eebus_errors.h" -#include "src/spine/model/common_data_types.h" -#include "src/spine/model/measurement_types.h" -#include "src/use_case/model/mpc_types.h" -#include "src/use_case/model/scaled_value.h" -#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" -#include "src/use_case/specialization/measurement/measurement_server.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * @brief Mu Mpc Measurement Interface - * (Mu Mpc Measurement "virtual functions table" declaration) - */ -typedef struct MuMpcMeasurementInterface MuMpcMeasurementInterface; - -/** - * @brief Mu Mpc Measurement Object type definition - * ("abstract class", has no members but only pointer to - * "virtual functions table") - */ -typedef struct MuMpcMeasurementObject MuMpcMeasurementObject; - -/** - * @brief MuMpcMeasurement Interface Structure - */ -struct MuMpcMeasurementInterface { - void (*destruct)(MuMpcMeasurementObject* self); - MuMpcMeasurementNameId (*get_name)(const MuMpcMeasurementObject* self); - EebusError (*get_data_value)( - const MuMpcMeasurementObject* self, - MeasurementServer* msrv, - ScaledValue* measurement_value - ); - const MeasurementConstraintsDataType* (*get_constraints)(const MuMpcMeasurementObject* self); - EebusError (*configure)( - MuMpcMeasurementObject* self, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id - ); - EebusError (*set_data_cache)( - MuMpcMeasurementObject* self, - const ScaledValue* measured_value, - const EebusDateTime* timestamp, - const MeasurementValueStateType* value_state, - const EebusDateTime* start_time, - const EebusDateTime* end_time - ); - MeasurementDataType* (*release_data_cache)(MuMpcMeasurementObject* self); -}; - -/** - * @brief Mu Mpc Measurement Object Structure - */ -struct MuMpcMeasurementObject { - const MuMpcMeasurementInterface* interface_; -}; - -/** - * @brief Mu Mpc Measurement pointer typecast - */ -#define MU_MPC_MEASUREMENT_OBJECT(obj) ((MuMpcMeasurementObject*)(obj)) - -/** - * @brief Mu Mpc Measurement Interface class pointer typecast - */ -#define MU_MPC_MEASUREMENT_INTERFACE(obj) (MU_MPC_MEASUREMENT_OBJECT(obj)->interface_) - -/** - * @brief Mu Mpc Measurement Destruct caller definition - */ -#define MU_MPC_MEASUREMENT_DESTRUCT(obj) (MU_MPC_MEASUREMENT_INTERFACE(obj)->destruct(obj)) - -/** - * @brief Mu Mpc Measurement Get Name caller definition - */ -#define MU_MPC_MEASUREMENT_GET_NAME(obj) (MU_MPC_MEASUREMENT_INTERFACE(obj)->get_name(obj)) - -/** - * @brief Mu Mpc Measurement Get Data Value caller definition - */ -#define MU_MPC_MEASUREMENT_GET_DATA_VALUE(obj, msrv, measurement_value) \ - (MU_MPC_MEASUREMENT_INTERFACE(obj)->get_data_value(obj, msrv, measurement_value)) - -/** - * @brief Mu Mpc Measurement Get Constraints caller definition - */ -#define MU_MPC_MEASUREMENT_GET_CONSTRAINTS(obj) (MU_MPC_MEASUREMENT_INTERFACE(obj)->get_constraints(obj)) - -/** - * @brief Mu Mpc Measurement Configure caller definition - */ -#define MU_MPC_MEASUREMENT_CONFIGURE(obj, msrv, ecsrv, electrical_connection_id) \ - (MU_MPC_MEASUREMENT_INTERFACE(obj)->configure(obj, msrv, ecsrv, electrical_connection_id)) - -/** - * @brief Mu Mpc Measurement Set Data Cache caller definition - */ -#define MU_MPC_MEASUREMENT_SET_DATA_CACHE(obj, measured_value, timestamp, value_state, start_time, end_time) \ - (MU_MPC_MEASUREMENT_INTERFACE(obj)->set_data_cache(obj, measured_value, timestamp, value_state, start_time, end_time)) - -/** - * @brief Mu Mpc Measurement Release Data Cache caller definition - */ -#define MU_MPC_MEASUREMENT_RELEASE_DATA_CACHE(obj) (MU_MPC_MEASUREMENT_INTERFACE(obj)->release_data_cache(obj)) - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // SRC_USE_CASE_API_MU_MPC_MEASUREMENT_INTERFACE_H_ diff --git a/src/use_case/api/mu_mpc_monitor_interface.h b/src/use_case/api/mu_mpc_monitor_interface.h deleted file mode 100644 index 80b0fe9..0000000 --- a/src/use_case/api/mu_mpc_monitor_interface.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2025 NIBE AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * @file - * @brief Mu Mpc Monitor interface declarations - */ - -#ifndef SRC_USE_CASE_API_MU_MPC_MONITOR_INTERFACE_H_ -#define SRC_USE_CASE_API_MU_MPC_MONITOR_INTERFACE_H_ - -#include "src/common/eebus_errors.h" -#include "src/use_case/api/mu_mpc_measurement_interface.h" -#include "src/use_case/model/scaled_value.h" -#include "src/use_case/specialization/electrical_connection/electrical_connection_server.h" -#include "src/use_case/specialization/measurement/measurement_server.h" - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * @brief Mu Mpc Monitor Interface - * (Mu Mpc Monitor "virtual functions table" declaration) - */ -typedef struct MuMpcMonitorInterface MuMpcMonitorInterface; - -/** - * @brief Mu Mpc Monitor Object type definition - * ("abstract class", has no members but only pointer to - * "virtual functions table") - */ -typedef struct MuMpcMonitorObject MuMpcMonitorObject; - -/** - * @brief MuMpcMonitor Interface Structure - */ -struct MuMpcMonitorInterface { - void (*destruct)(MuMpcMonitorObject* self); - MuMpcMonitorNameId (*get_name)(const MuMpcMonitorObject* self); - EebusError (*configure)( - MuMpcMonitorObject* self, - MeasurementServer* msrv, - ElectricalConnectionServer* ecsrv, - ElectricalConnectionIdType electrical_connection_id, - MeasurementConstraintsListDataType* measurements_constraints - ); - MuMpcMeasurementObject* (*get_measurement)( - const MuMpcMonitorObject* self, - MuMpcMeasurementNameId measurement_name_id - ); - EebusError (*flush_measurement_cache)(MuMpcMonitorObject* self, MeasurementListDataType* measurement_data_list); -}; - -/** - * @brief Mu Mpc Monitor Object Structure - */ -struct MuMpcMonitorObject { - const MuMpcMonitorInterface* interface_; -}; - -/** - * @brief Mu Mpc Monitor pointer typecast - */ -#define MU_MPC_MONITOR_OBJECT(obj) ((MuMpcMonitorObject*)(obj)) - -/** - * @brief Mu Mpc Monitor Interface class pointer typecast - */ -#define MU_MPC_MONITOR_INTERFACE(obj) (MU_MPC_MONITOR_OBJECT(obj)->interface_) - -/** - * @brief Mu Mpc Monitor Destruct caller definition - */ -#define MU_MPC_MONITOR_DESTRUCT(obj) (MU_MPC_MONITOR_INTERFACE(obj)->destruct(obj)) - -/** - * @brief Mu Mpc Monitor Get Name caller definition - */ -#define MU_MPC_MONITOR_GET_NAME(obj) (MU_MPC_MONITOR_INTERFACE(obj)->get_name(obj)) - -/** - * @brief Mu Mpc Monitor Configure caller definition - */ -#define MU_MPC_MONITOR_CONFIGURE(obj, msrv, ecsrv, electrical_connection_id, measurements_constraints) \ - (MU_MPC_MONITOR_INTERFACE(obj)->configure(obj, msrv, ecsrv, electrical_connection_id, measurements_constraints)) - -/** - * @brief Mu Mpc Monitor Get Measurement caller definition - */ -#define MU_MPC_MONITOR_GET_MEASUREMENT(obj, measurement_name_id) \ - (MU_MPC_MONITOR_INTERFACE(obj)->get_measurement(obj, measurement_name_id)) - -/** - * @brief Mu Mpc Monitor Flush Measurement Cache caller definition - */ -#define MU_MPC_MONITOR_FLUSH_MEASUREMENT_CACHE(obj, measurement_data_list) \ - (MU_MPC_MONITOR_INTERFACE(obj)->flush_measurement_cache(obj, measurement_data_list)) - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // SRC_USE_CASE_API_MU_MPC_MONITOR_INTERFACE_H_ diff --git a/src/use_case/model/eebus_measurement_types.h b/src/use_case/model/eebus_measurement_types.h new file mode 100644 index 0000000..deb42be --- /dev/null +++ b/src/use_case/model/eebus_measurement_types.h @@ -0,0 +1,94 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Measurement type declarations shared between MU MPC and GCP MGCP use cases + */ + +#ifndef SRC_USE_CASE_MODEL_EEBUS_MEASUREMENT_TYPES_H_ +#define SRC_USE_CASE_MODEL_EEBUS_MEASUREMENT_TYPES_H_ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * @brief Measurement monitor group identifiers (high nibble of EebusMeasurementNameId) + */ +enum EebusMeasurementMonitorNameId { + kEebusMeasurementMonitorPower = 0x10, /**< Active power measurements */ + kEebusMeasurementMonitorEnergy = 0x20, /**< Energy measurements */ + kEebusMeasurementMonitorCurrent = 0x30, /**< Per-phase AC current measurements */ + kEebusMeasurementMonitorVoltage = 0x40, /**< Per-phase AC voltage measurements */ + kEebusMeasurementMonitorFrequency = 0x50, /**< AC frequency measurements */ + kEebusMeasurementMonitorNameIdMask = 0xF0, /**< Mask to extract the monitor group */ +}; + +typedef enum EebusMeasurementMonitorNameId EebusMeasurementMonitorNameId; + +/** + * @brief Measurement name identifiers shared by MU MPC and GCP MGCP + * + * High nibble selects the monitor group; low nibble identifies the specific + * measurement within that group. + * + * MU MPC scenarios: + * kMuPowerTotal, kMuPowerPhaseA–C (scenario 1 — total and per-phase power) + * kMuEnergyConsumed, kMuEnergyProduced (scenario 2 — energy) + * kMuCurrentPhaseA–C (scenario 3 — current) + * kMuVoltagePhaseA–C, kMuVoltagePhaseAb/Bc/Ac (scenario 4 — voltage) + * kMuFrequency (scenario 5 — frequency) + * + * GCP MGCP scenarios: + * kMuPowerTotal (scenario 2 — total power) + * kMuEnergyFeedIn (scenario 3 — grid feed-in energy) + * kMuEnergyConsumed (scenario 4 — grid consumed energy) + * kMuCurrentPhaseA–C (scenario 5 — current) + * kMuVoltagePhaseA–C, kMuVoltagePhaseAb/Bc/Ac (scenario 6 — voltage) + * kMuFrequency (scenario 7 — frequency) + */ +enum EebusMeasurementNameId { + /* Active power */ + kMuPowerTotal = kEebusMeasurementMonitorPower | 0x01, /**< Total active power (W) */ + kMuPowerPhaseA = kEebusMeasurementMonitorPower | 0x02, /**< Phase A active power (W) */ + kMuPowerPhaseB = kEebusMeasurementMonitorPower | 0x03, /**< Phase B active power (W) */ + kMuPowerPhaseC = kEebusMeasurementMonitorPower | 0x04, /**< Phase C active power (W) */ + /* Energy */ + kMuEnergyConsumed = kEebusMeasurementMonitorEnergy | 0x01, /**< Consumed energy (Wh) */ + kMuEnergyProduced = kEebusMeasurementMonitorEnergy | 0x02, /**< Produced energy (Wh) — MU MPC only */ + kMuEnergyFeedIn = kEebusMeasurementMonitorEnergy | 0x03, /**< Grid feed-in energy (Wh) — GCP MGCP only */ + /* Per-phase AC current */ + kMuCurrentPhaseA = kEebusMeasurementMonitorCurrent | 0x01, /**< Phase A RMS current (A) */ + kMuCurrentPhaseB = kEebusMeasurementMonitorCurrent | 0x02, /**< Phase B RMS current (A) */ + kMuCurrentPhaseC = kEebusMeasurementMonitorCurrent | 0x03, /**< Phase C RMS current (A) */ + /* Per-phase AC voltage */ + kMuVoltagePhaseA = kEebusMeasurementMonitorVoltage | 0x01, /**< Phase A to neutral RMS voltage (V) */ + kMuVoltagePhaseB = kEebusMeasurementMonitorVoltage | 0x02, /**< Phase B to neutral RMS voltage (V) */ + kMuVoltagePhaseC = kEebusMeasurementMonitorVoltage | 0x03, /**< Phase C to neutral RMS voltage (V) */ + kMuVoltagePhaseAb = kEebusMeasurementMonitorVoltage | 0x04, /**< Phase A to B RMS voltage (V) */ + kMuVoltagePhaseBc = kEebusMeasurementMonitorVoltage | 0x05, /**< Phase B to C RMS voltage (V) */ + kMuVoltagePhaseAc = kEebusMeasurementMonitorVoltage | 0x06, /**< Phase C to A RMS voltage (V) */ + /* AC frequency */ + kMuFrequency = kEebusMeasurementMonitorFrequency | 0x01, /**< Grid frequency (Hz) */ +}; + +typedef enum EebusMeasurementNameId EebusMeasurementNameId; + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_MODEL_EEBUS_MEASUREMENT_TYPES_H_ diff --git a/src/use_case/model/mgcp_types.c b/src/use_case/model/mgcp_types.c new file mode 100644 index 0000000..ecc57b8 --- /dev/null +++ b/src/use_case/model/mgcp_types.c @@ -0,0 +1,72 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MGCP (MA and GCP) utilities implementation + */ + +#include +#include + +#include "src/common/array_util.h" +#include "src/use_case/model/mgcp_types.h" + +typedef struct MgcpNameMapping MgcpNameMapping; + +struct MgcpNameMapping { + const char* name; + GcpMeasurementNameId id; +}; + +static const MgcpNameMapping mgcp_name_lut[] = { + { "power_total", kGcpPowerTotal}, + { "energy_feed_in", kGcpEnergyFeedIn}, + { "energy_consumed", kGcpEnergyConsumed}, + { "current_phase_a", kGcpCurrentPhaseA}, + { "current_phase_b", kGcpCurrentPhaseB}, + { "current_phase_c", kGcpCurrentPhaseC}, + { "voltage_phase_a", kGcpVoltagePhaseA}, + { "voltage_phase_b", kGcpVoltagePhaseB}, + { "voltage_phase_c", kGcpVoltagePhaseC}, + {"voltage_phase_ab", kGcpVoltagePhaseAb}, + {"voltage_phase_bc", kGcpVoltagePhaseBc}, + {"voltage_phase_ac", kGcpVoltagePhaseAc}, + { "frequency", kGcpFrequency}, +}; + +const GcpMeasurementNameId* GcpMgcpMeasurementGetNameId(const char* name) { + if (name == NULL) { + return NULL; + } + + for (size_t i = 0; i < ARRAY_SIZE(mgcp_name_lut); ++i) { + if (strcmp(mgcp_name_lut[i].name, name) == 0) { + return &mgcp_name_lut[i].id; + } + } + + return NULL; +} + +const char* GcpMgcpMeasurementGetName(GcpMeasurementNameId id) { + for (size_t i = 0; i < ARRAY_SIZE(mgcp_name_lut); ++i) { + if (mgcp_name_lut[i].id == id) { + return mgcp_name_lut[i].name; + } + } + + return NULL; +} diff --git a/src/use_case/model/mgcp_types.h b/src/use_case/model/mgcp_types.h new file mode 100644 index 0000000..fc1af7f --- /dev/null +++ b/src/use_case/model/mgcp_types.h @@ -0,0 +1,83 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief MGCP (MA and GCP) type declarations, constants and utilities + */ + +#ifndef SRC_USE_CASE_MODEL_MGCP_TYPES_H_ +#define SRC_USE_CASE_MODEL_MGCP_TYPES_H_ + +#include "src/use_case/model/eebus_measurement_types.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef EebusMeasurementMonitorNameId GcpMonitorNameId; +typedef EebusMeasurementNameId GcpMeasurementNameId; + +#define kGcpMonitorPower kEebusMeasurementMonitorPower /**< Scenario 2: momentary power */ +#define kGcpMonitorEnergy kEebusMeasurementMonitorEnergy /**< Scenarios 3 and 4: energy */ +#define kGcpMonitorCurrent kEebusMeasurementMonitorCurrent /**< Scenario 5: per-phase AC current */ +#define kGcpMonitorVoltage kEebusMeasurementMonitorVoltage /**< Scenario 6: per-phase AC voltage */ +#define kGcpMonitorFrequency kEebusMeasurementMonitorFrequency /**< Scenario 7: AC frequency */ +#define kGcpMonitorNameIdMask kEebusMeasurementMonitorNameIdMask + +/* Scenario 2 — momentary power */ +#define kGcpPowerTotal kMuPowerTotal /**< Total active power (W) */ + +/* Scenario 3 — grid feed-in energy */ +#define kGcpEnergyFeedIn kMuEnergyFeedIn /**< Cumulative feed-in energy (Wh) */ + +/* Scenario 4 — grid consumed energy */ +#define kGcpEnergyConsumed kMuEnergyConsumed /**< Cumulative consumed energy (Wh) */ + +/* Scenario 5 — per-phase AC current */ +#define kGcpCurrentPhaseA kMuCurrentPhaseA /**< Phase A RMS current (A) */ +#define kGcpCurrentPhaseB kMuCurrentPhaseB /**< Phase B RMS current (A) */ +#define kGcpCurrentPhaseC kMuCurrentPhaseC /**< Phase C RMS current (A) */ + +/* Scenario 6 — per-phase AC voltage */ +#define kGcpVoltagePhaseA kMuVoltagePhaseA /**< Phase A to neutral RMS voltage (V) */ +#define kGcpVoltagePhaseB kMuVoltagePhaseB /**< Phase B to neutral RMS voltage (V) */ +#define kGcpVoltagePhaseC kMuVoltagePhaseC /**< Phase C to neutral RMS voltage (V) */ +#define kGcpVoltagePhaseAb kMuVoltagePhaseAb /**< Phase A to B RMS voltage (V) */ +#define kGcpVoltagePhaseBc kMuVoltagePhaseBc /**< Phase B to C RMS voltage (V) */ +#define kGcpVoltagePhaseAc kMuVoltagePhaseAc /**< Phase C to A RMS voltage (V) */ + +/* Scenario 7 — AC frequency */ +#define kGcpFrequency kMuFrequency /**< Grid frequency (Hz) */ + +/** + * @brief Look up a GcpMeasurementNameId by its string name + * @param name String name of the measurement (e.g. "power_total", "current_phase_a") + * @return Pointer to the matching GcpMeasurementNameId, or NULL if not found + */ +const GcpMeasurementNameId* GcpMgcpMeasurementGetNameId(const char* name); + +/** + * @brief Get the string name of a GcpMeasurementNameId + * @param id The measurement name identifier + * @return String name, or NULL if the identifier is unknown + */ +const char* GcpMgcpMeasurementGetName(GcpMeasurementNameId id); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SRC_USE_CASE_MODEL_MGCP_TYPES_H_ diff --git a/src/use_case/model/mpc_types.h b/src/use_case/model/mpc_types.h index 478f491..c504bb0 100644 --- a/src/use_case/model/mpc_types.h +++ b/src/use_case/model/mpc_types.h @@ -21,53 +21,50 @@ #ifndef SRC_USE_CASE_MODEL_MPC_TYPES_H_ #define SRC_USE_CASE_MODEL_MPC_TYPES_H_ +#include "src/use_case/model/eebus_measurement_types.h" + #ifdef __cplusplus extern "C" { #endif // __cplusplus -enum MuMpcMonitorNameId { - kMpcMonitorPower = 0x10, - kMpcMonitorEnergy = 0x20, - kMpcMonitorCurrent = 0x30, - kMpcMonitorVoltage = 0x40, - kMpcMonitorFrequency = 0x50, - kMpcMonitorNameIdMask = 0xF0, -}; - -typedef enum MuMpcMonitorNameId MuMpcMonitorNameId; +typedef EebusMeasurementMonitorNameId MuMpcMonitorNameId; +typedef EebusMeasurementNameId MuMpcMeasurementNameId; -enum MuMpcMeasurementNameId { - kMpcPowerTotal = kMpcMonitorPower | 0x01, - kMpcPowerPhaseA = kMpcMonitorPower | 0x02, - kMpcPowerPhaseB = kMpcMonitorPower | 0x03, - kMpcPowerPhaseC = kMpcMonitorPower | 0x04, - kMpcEnergyConsumed = kMpcMonitorEnergy | 0x01, - kMpcEnergyProduced = kMpcMonitorEnergy | 0x02, - kMpcCurrentPhaseA = kMpcMonitorCurrent | 0x01, - kMpcCurrentPhaseB = kMpcMonitorCurrent | 0x02, - kMpcCurrentPhaseC = kMpcMonitorCurrent | 0x03, - kMpcVoltagePhaseA = kMpcMonitorVoltage | 0x01, - kMpcVoltagePhaseB = kMpcMonitorVoltage | 0x02, - kMpcVoltagePhaseC = kMpcMonitorVoltage | 0x03, - kMpcVoltagePhaseAb = kMpcMonitorVoltage | 0x04, - kMpcVoltagePhaseBc = kMpcMonitorVoltage | 0x05, - kMpcVoltagePhaseAc = kMpcMonitorVoltage | 0x06, - kMpcFrequency = kMpcMonitorFrequency | 0x01, -}; +#define kMpcMonitorPower kEebusMeasurementMonitorPower +#define kMpcMonitorEnergy kEebusMeasurementMonitorEnergy +#define kMpcMonitorCurrent kEebusMeasurementMonitorCurrent +#define kMpcMonitorVoltage kEebusMeasurementMonitorVoltage +#define kMpcMonitorFrequency kEebusMeasurementMonitorFrequency +#define kMpcMonitorNameIdMask kEebusMeasurementMonitorNameIdMask -typedef enum MuMpcMeasurementNameId MuMpcMeasurementNameId; +#define kMpcPowerTotal kMuPowerTotal +#define kMpcPowerPhaseA kMuPowerPhaseA +#define kMpcPowerPhaseB kMuPowerPhaseB +#define kMpcPowerPhaseC kMuPowerPhaseC +#define kMpcEnergyConsumed kMuEnergyConsumed +#define kMpcEnergyProduced kMuEnergyProduced +#define kMpcCurrentPhaseA kMuCurrentPhaseA +#define kMpcCurrentPhaseB kMuCurrentPhaseB +#define kMpcCurrentPhaseC kMuCurrentPhaseC +#define kMpcVoltagePhaseA kMuVoltagePhaseA +#define kMpcVoltagePhaseB kMuVoltagePhaseB +#define kMpcVoltagePhaseC kMuVoltagePhaseC +#define kMpcVoltagePhaseAb kMuVoltagePhaseAb +#define kMpcVoltagePhaseBc kMuVoltagePhaseBc +#define kMpcVoltagePhaseAc kMuVoltagePhaseAc +#define kMpcFrequency kMuFrequency /** - * @brief Get the MU MPC state with the state name - * @param name Name of the state - * @return Pointer to the MU MPC state, or NULL if not found + * @brief Get the MU MPC measurement name id from its string name + * @param name String name (e.g. "power_total", "energy_consumed") + * @return Pointer to the matching id, or NULL if not found */ const MuMpcMeasurementNameId* MuMpcMeasurementGetNameId(const char* name); /** - * @brief Get the name of the MU MPC state - * @param state MU MPC state - * @return Name of the state + * @brief Get the string name of a MU MPC measurement + * @param state The measurement name identifier + * @return String name, or NULL if the identifier is unknown */ const char* MuMpcMeasurementGetName(MuMpcMeasurementNameId state); diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index 0246cb5..7f8fcd8 100644 --- a/tests/src/CMakeLists.txt +++ b/tests/src/CMakeLists.txt @@ -69,6 +69,12 @@ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/use_case/actor/eg/lpc add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/use_case/actor/eg/lpp ${EXECUTABLE_OUTPUT_PATH}/use_case/actor/eg/lpp) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/use_case/actor/gcp/mgcp + ${EXECUTABLE_OUTPUT_PATH}/use_case/actor/gcp/mgcp) + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/use_case/actor/ma/mgcp + ${EXECUTABLE_OUTPUT_PATH}/use_case/actor/ma/mgcp) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/use_case/actor/ma/mpc ${EXECUTABLE_OUTPUT_PATH}/use_case/actor/ma/mpc) diff --git a/tests/src/mocks/use_case/api/ma_mgcp_listener_mock.cpp b/tests/src/mocks/use_case/api/ma_mgcp_listener_mock.cpp new file mode 100644 index 0000000..ac9478c --- /dev/null +++ b/tests/src/mocks/use_case/api/ma_mgcp_listener_mock.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Ma Mgcp Listener mock implementation + */ + +#include "ma_mgcp_listener_mock.h" + +#include + +#include "src/use_case/api/ma_mgcp_listener_interface.h" + +static void Destruct(MaMgcpListenerObject* self); +static void OnRemoteEntityConnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr); +static void OnRemoteEntityDisconnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr); +static void OnMeasurementReceive( + MaMgcpListenerObject* self, + GcpMeasurementNameId name_id, + const ScaledValue* measurement_value, + const EntityAddressType* remote_entity_addr +); +static void OnPvCurtailmentLimitFactorReceive( + MaMgcpListenerObject* self, + const ScaledValue* value, + const EntityAddressType* remote_entity_addr +); + +static const MaMgcpListenerInterface ma_mgcp_listener_methods = { + .destruct = Destruct, + .on_remote_entity_connect = OnRemoteEntityConnect, + .on_remote_entity_disconnect = OnRemoteEntityDisconnect, + .on_measurement_receive = OnMeasurementReceive, + .on_pv_curtailment_limit_factor_receive = OnPvCurtailmentLimitFactorReceive, +}; + +static EebusError MaMgcpListenerMockConstruct(MaMgcpListenerMock* self); + +EebusError MaMgcpListenerMockConstruct(MaMgcpListenerMock* self) { + // Override "virtual functions table" + MA_MGCP_LISTENER_INTERFACE(self) = &ma_mgcp_listener_methods; + + self->gmock = new MaMgcpListenerGMock(); + if (self->gmock == nullptr) { + return kEebusErrorMemoryAllocate; + } + + return kEebusErrorOk; +} + +MaMgcpListenerMock* MaMgcpListenerMockCreate(void) { + MaMgcpListenerMock* const mock = (MaMgcpListenerMock*)EEBUS_MALLOC(sizeof(MaMgcpListenerMock)); + if (mock == nullptr) { + return nullptr; + } + + if (MaMgcpListenerMockConstruct(mock) != kEebusErrorOk) { + MaMgcpListenerMockDelete(mock); + return nullptr; + } + + return mock; +} + +void Destruct(MaMgcpListenerObject* self) { + MaMgcpListenerMock* const mock = MA_MGCP_LISTENER_MOCK(self); + mock->gmock->Destruct(self); + delete mock->gmock; +} + +void OnRemoteEntityConnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr) { + MaMgcpListenerMock* const mock = MA_MGCP_LISTENER_MOCK(self); + mock->gmock->OnRemoteEntityConnect(self, entity_addr); +} + +void OnRemoteEntityDisconnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr) { + MaMgcpListenerMock* const mock = MA_MGCP_LISTENER_MOCK(self); + mock->gmock->OnRemoteEntityDisconnect(self, entity_addr); +} + +void OnMeasurementReceive( + MaMgcpListenerObject* self, + GcpMeasurementNameId name_id, + const ScaledValue* measurement_value, + const EntityAddressType* remote_entity_addr +) { + MaMgcpListenerMock* const mock = MA_MGCP_LISTENER_MOCK(self); + mock->gmock->OnMeasurementReceive(self, name_id, measurement_value, remote_entity_addr); +} + +void OnPvCurtailmentLimitFactorReceive( + MaMgcpListenerObject* self, + const ScaledValue* value, + const EntityAddressType* remote_entity_addr +) { + MaMgcpListenerMock* const mock = MA_MGCP_LISTENER_MOCK(self); + mock->gmock->OnPvCurtailmentLimitFactorReceive(self, value, remote_entity_addr); +} diff --git a/tests/src/mocks/use_case/api/ma_mgcp_listener_mock.h b/tests/src/mocks/use_case/api/ma_mgcp_listener_mock.h new file mode 100644 index 0000000..ce0cec2 --- /dev/null +++ b/tests/src/mocks/use_case/api/ma_mgcp_listener_mock.h @@ -0,0 +1,82 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @file + * @brief Ma Mgcp Listener Mock "class" + */ + +#ifndef TESTS_SRC_MOCKS_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_LISTENER_MOCK_H_ +#define TESTS_SRC_MOCKS_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_LISTENER_MOCK_H_ + +#include + +#include + +#include "src/common/eebus_malloc.h" +#include "src/use_case/api/ma_mgcp_listener_interface.h" + +class MaMgcpListenerGMockInterface { + public: + virtual ~MaMgcpListenerGMockInterface() {}; + virtual void Destruct(MaMgcpListenerObject* self) = 0; + virtual void OnRemoteEntityConnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr) = 0; + virtual void OnRemoteEntityDisconnect(MaMgcpListenerObject* self, const EntityAddressType* entity_addr) = 0; + virtual void OnMeasurementReceive( + MaMgcpListenerObject* self, + GcpMeasurementNameId name_id, + const ScaledValue* measurement_value, + const EntityAddressType* remote_entity_addr + ) = 0; + virtual void OnPvCurtailmentLimitFactorReceive( + MaMgcpListenerObject* self, + const ScaledValue* value, + const EntityAddressType* remote_entity_addr + ) = 0; +}; + +class MaMgcpListenerGMock : public MaMgcpListenerGMockInterface { + public: + virtual ~MaMgcpListenerGMock() {}; + MOCK_METHOD1(Destruct, void(MaMgcpListenerObject*)); + MOCK_METHOD2(OnRemoteEntityConnect, void(MaMgcpListenerObject*, const EntityAddressType*)); + MOCK_METHOD2(OnRemoteEntityDisconnect, void(MaMgcpListenerObject*, const EntityAddressType*)); + MOCK_METHOD4( + OnMeasurementReceive, + void(MaMgcpListenerObject*, GcpMeasurementNameId, const ScaledValue*, const EntityAddressType*) + ); + MOCK_METHOD3( + OnPvCurtailmentLimitFactorReceive, + void(MaMgcpListenerObject*, const ScaledValue*, const EntityAddressType*) + ); +}; + +typedef struct MaMgcpListenerMock { + /** Implements the Ma Mgcp Listener Interface */ + MaMgcpListenerObject obj; + MaMgcpListenerGMock* gmock; +} MaMgcpListenerMock; + +#define MA_MGCP_LISTENER_MOCK(obj) ((MaMgcpListenerMock*)(obj)) + +MaMgcpListenerMock* MaMgcpListenerMockCreate(void); + +static inline void MaMgcpListenerMockDelete(MaMgcpListenerMock* listener_mock) { + if (listener_mock != NULL) { + MA_MGCP_LISTENER_DESTRUCT(MA_MGCP_LISTENER_OBJECT(listener_mock)); + EEBUS_FREE(listener_mock); + } +} +#endif // TESTS_SRC_MOCKS_USE_CASE_ACTOR_MA_MGCP_MA_MGCP_LISTENER_MOCK_H_ diff --git a/tests/src/use_case/actor/gcp/mgcp/CMakeLists.txt b/tests/src/use_case/actor/gcp/mgcp/CMakeLists.txt new file mode 100644 index 0000000..3a88f88 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/CMakeLists.txt @@ -0,0 +1,153 @@ +cmake_minimum_required(VERSION 3.15) + +set(TEST_NAME gcp_mgcp_test) + +project(${TEST_NAME} LANGUAGES C CXX) + +add_executable(${TEST_NAME}) + +# Set proper runtime library for Windows to avoid LIBCMT conflicts +if(WIN32) + set_property(TARGET ${TEST_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") +endif() + +include(${MAIN_PROJ_SOURCES_PATH}/common/eebus_date_time/eebus_date_time_sources.cmake) +include(${MAIN_PROJ_SOURCES_PATH}/common/eebus_data/eebus_data_sources.cmake) + +target_sources( + ${TEST_NAME} + PRIVATE + ${GTEST_SOURCES} + + # Main project sources + ${EEBUS_DATE_TIME_SOURCES} + ${EEBUS_DATA_SOURCES} + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_device_info.c + ${MAIN_PROJ_SOURCES_PATH}/common/json_impl_cjson.c + ${MAIN_PROJ_SOURCES_PATH}/common/message_buffer.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_math/eebus_math.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_mutex/eebus_mutex.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_queue/eebus_queue.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_thread/eebus_thread.c + ${MAIN_PROJ_SOURCES_PATH}/common/service_details.c + ${MAIN_PROJ_SOURCES_PATH}/common/string_lut.c + ${MAIN_PROJ_SOURCES_PATH}/common/string_util.c + ${MAIN_PROJ_SOURCES_PATH}/common/uint64_lut.c + ${MAIN_PROJ_SOURCES_PATH}/common/vector.c + ${MAIN_PROJ_SOURCES_PATH}/spine/binding/binding_manager.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/data_reader.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/device_local.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/device_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/device.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/sender.c + ${MAIN_PROJ_SOURCES_PATH}/spine/entity/entity_local.c + ${MAIN_PROJ_SOURCES_PATH}/spine/entity/entity_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/entity/entity.c + ${MAIN_PROJ_SOURCES_PATH}/spine/events/events.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_address_container.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_local.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_functions.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/operations.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/pending_write_request.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature_link/feature_link.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature_link/feature_link_container.c + ${MAIN_PROJ_SOURCES_PATH}/spine/function/function.c + ${MAIN_PROJ_SOURCES_PATH}/spine/heartbeat/heartbeat_manager.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/absolute_or_relative_time.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/binding_management_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/cmd.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/datagram.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/device_configuration_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/entity_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/feature_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/filter.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/function_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/measurement_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/model.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/node_management_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/possible_operations_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/specification_version.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/subscription_management_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/usecase_information_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_binding.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_destination_list.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_detailed_discovery.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_subscription.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_usecase.c + ${MAIN_PROJ_SOURCES_PATH}/spine/subscription/subscription_manager.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/gcp/mgcp/gcp_mgcp_measurement.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/gcp/mgcp/gcp_mgcp_monitor.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/gcp/mgcp/gcp_mgcp_public.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/gcp/mgcp/gcp_mgcp.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/model/scaled_value.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/model/mgcp_types.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/device_configuration/device_configuration_common.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/device_configuration/device_configuration_server.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/electrical_connection/electrical_connection_common.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/electrical_connection/electrical_connection_server.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_measurement_base.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_monitor_base.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_monitor_container.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_monitor_features.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/measurement/measurement_common.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/measurement/measurement_server.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/feature_info_client.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/feature_info_server.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/helper.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/use_case.c + + # Mocks sources + ${MOCKS_SOURCES_PATH}/ship/ship_connection/data_writer_mock.cpp + ${MOCKS_SOURCES_PATH}/common/eebus_timer/eebus_timer_mock.cpp + + # Test helpers + ${CMAKE_SOURCE_DIR}/src/spine/function_data.c + ${CMAKE_SOURCE_DIR}/src/use_case/use_case_test_fixture.cpp + + gcp_mgcp_test.cpp +) + +target_include_directories( + ${TEST_NAME} + PRIVATE + ${PROJECT_INCLUDES_PATH} +) + +target_compile_options( + ${TEST_NAME} + PRIVATE + ${PROJECT_COMPILE_OPTIONS} +) + +target_compile_definitions( + ${TEST_NAME} + PRIVATE + ${PROJECT_COMPILE_DEFINITIONS} + MEMORY_LEAKS_TEST +) + +target_link_options( + ${TEST_NAME} + PRIVATE + ${PROJECT_LINK_OPTIONS} +) + +target_link_libraries( + ${TEST_NAME} + PRIVATE + ${PROJECT_LINK_LIBRARIES} + cjson +) + +add_test( + NAME + ${TEST_NAME} + COMMAND + ${EXECUTABLE_OUTPUT_PATH}/${TEST_NAME} +) + +gtest_discover_tests(${TEST_NAME}) diff --git a/tests/src/use_case/actor/gcp/mgcp/gcp_mgcp_test.cpp b/tests/src/use_case/actor/gcp/mgcp/gcp_mgcp_test.cpp new file mode 100644 index 0000000..f86e9f0 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/gcp_mgcp_test.cpp @@ -0,0 +1,355 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @brief Currently it is not a regular unit test but more a "sand box" + * to feed the SPINE Device with specific datagrams and check the outgoing messages printed. + * @note Remember to enable the message printing in PrintMessage() before getting started + */ + +#include "src/use_case/actor/gcp/mgcp/gcp_mgcp.h" + +#include + +#include + +#include "mocks/common/eebus_timer/eebus_timer_mock.h" +#include "mocks/ship/ship_connection/data_writer_mock.h" +#include "src/common/array_util.h" +#include "src/common/eebus_malloc.h" +#include "src/common/eebus_timer/eebus_timer.h" +#include "src/common/message_buffer.h" +#include "src/spine/device/device_local.h" +#include "src/spine/device/device_local_internal.h" +#include "src/spine/entity/entity_local.h" +#include "tests/src/json.h" +#include "tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_description_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_key_value_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_subscription_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/discovery_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/discovery_response.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_parameter_description_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_subscription_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/measurement_constraints_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/measurement_description_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/measurement_subscription_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/node_management_subscription_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/result_data_msg_cnt_ref_3.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/use_case_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/receive/use_case_request.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/device_configuration_description_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/device_configuration_key_value_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/discovery_read.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/discovery_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_description_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_parameter_description_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/measurement_constraints_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/measurement_description_reply.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_current.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_energy.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_frequency.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_power.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_voltage.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/node_management_subscription_call.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_11.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_3.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_5.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_8.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/use_case_data_read.inc" +#include "tests/src/use_case/actor/gcp/mgcp/send/use_case_data_reply.inc" +#include "tests/src/use_case/use_case_test_fixture.h" + +using testing::_; +using testing::Return; + +namespace gcp_mgcp_test { + +class GcpMgcpTestFixture : public UseCaseTestFixture { + public: + GcpMgcpTestFixture() : UseCaseTestFixture("GridConnectionPoint", "GridConnectionPoint", "123456789") {}; + void SetUpUseCase() override { + uint32_t entity_ids[1]{static_cast(VectorGetSize(DEVICE_LOCAL_GET_ENTITIES(device_local_.get())))}; + + EntityLocalObject* const entity = EntityLocalCreate( + device_local_.get(), + kEntityTypeTypeGridConnectionPointOfPremises, + entity_ids, + ARRAY_SIZE(entity_ids), + kHeartbeatTimeout + ); + + static constexpr GcpMgcpMeasurementConfig measurement_default_cfg{ + .value_source = kMeasurementValueSourceTypeMeasuredValue, + }; + + static constexpr GcpMgcpMonitorEnergyConfig energy_cfg{ + .energy_feed_in_cfg = &measurement_default_cfg, + .energy_consumed_cfg = &measurement_default_cfg, + }; + + static constexpr GcpMgcpMonitorCurrentConfig current_cfg{ + .current_phase_a_cfg = &measurement_default_cfg, + .current_phase_b_cfg = &measurement_default_cfg, + .current_phase_c_cfg = &measurement_default_cfg, + }; + + static constexpr GcpMgcpMonitorVoltageConfig voltage_cfg{ + .voltage_phase_a_cfg = &measurement_default_cfg, + .voltage_phase_b_cfg = &measurement_default_cfg, + .voltage_phase_c_cfg = &measurement_default_cfg, + .voltage_phase_ab_cfg = &measurement_default_cfg, + .voltage_phase_bc_cfg = &measurement_default_cfg, + .voltage_phase_ac_cfg = &measurement_default_cfg, + }; + + static constexpr GcpMgcpMonitorFrequencyConfig frequency_cfg{ + .frequency_cfg = measurement_default_cfg, + }; + + static constexpr GcpMgcpPvCurtailmentConfig pv_curtailment_cfg{}; + + static constexpr GcpMgcpConfig cfg{ + .pv_curtailment_cfg = &pv_curtailment_cfg, + + .power_cfg = { + .phases = kElectricalConnectionPhaseNameTypeAbc, + .power_total_cfg = measurement_default_cfg, + }, + + .energy_cfg = &energy_cfg, + .current_cfg = ¤t_cfg, + .voltage_cfg = &voltage_cfg, + .frequency_cfg = &frequency_cfg, + }; + + use_case_.reset(GcpMgcpUseCaseCreate(entity, 0, &cfg)); + + static constexpr ScaledValue power_total{1000, 0}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpPowerTotal, &power_total, NULL, NULL); + + static constexpr ScaledValue energy_feed_in{200, 0}; + static constexpr EebusDateTime start_time{ + .date = {.year = 2025, .month = 9, .day = 1}, + .time = { .hour = 0, .min = 0, .sec = 0} + }; + + static constexpr EebusDateTime end_time{ + .date = {.year = 2025, .month = 10, .day = 2}, + .time = { .hour = 0, .min = 0, .sec = 0} + }; + + GcpMgcpSetEnergyFeedInCache(use_case_.get(), &energy_feed_in, NULL, NULL, &start_time, &end_time); + + static constexpr ScaledValue energy_consumed{800, 0}; + GcpMgcpSetEnergyConsumedCache(use_case_.get(), &energy_consumed, NULL, NULL, &start_time, &end_time); + + static constexpr ScaledValue current_phase_a{15, -1}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpCurrentPhaseA, ¤t_phase_a, NULL, NULL); + + static constexpr ScaledValue current_phase_b{16, -1}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpCurrentPhaseB, ¤t_phase_b, NULL, NULL); + + static constexpr ScaledValue current_phase_c{17, -1}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpCurrentPhaseC, ¤t_phase_c, NULL, NULL); + + static constexpr ScaledValue voltage_phase_a{23000, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseA, &voltage_phase_a, NULL, NULL); + static constexpr ScaledValue voltage_phase_b{23100, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseB, &voltage_phase_b, NULL, NULL); + static constexpr ScaledValue voltage_phase_c{22900, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseC, &voltage_phase_c, NULL, NULL); + static constexpr ScaledValue voltage_phase_ab{40000, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseAb, &voltage_phase_ab, NULL, NULL); + static constexpr ScaledValue voltage_phase_bc{40100, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseBc, &voltage_phase_bc, NULL, NULL); + static constexpr ScaledValue voltage_phase_ac{39900, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseAc, &voltage_phase_ac, NULL, NULL); + + static constexpr ScaledValue frequency{50, 0}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpFrequency, &frequency, NULL, NULL); + + static constexpr ScaledValue pv_curtailment_limit_factor{75, 0}; + GcpMgcpSetPvCurtailmentLimitFactor(use_case_.get(), &pv_curtailment_limit_factor); + + GcpMgcpUpdate(use_case_.get()); + + DEVICE_LOCAL_ADD_ENTITY(device_local_.get(), entity); + + ExpectSendMessage(send::discovery_read); + } + + void TearDownUseCase() override { + use_case_.reset(); + } + + protected: + std::unique_ptr use_case_{nullptr, GcpMgcpUseCaseDelete}; +}; + +TEST_F(GcpMgcpTestFixture, GcpMgcpTest) { + // 1. Check that values assigned during SetUpUseCase are correctly stored + ScaledValue value{0}; + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpPowerTotal, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 1000); + EXPECT_EQ(value.scale, 0); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpCurrentPhaseA, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 15); + EXPECT_EQ(value.scale, -1); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpCurrentPhaseB, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 16); + EXPECT_EQ(value.scale, -1); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpCurrentPhaseC, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 17); + EXPECT_EQ(value.scale, -1); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpVoltagePhaseA, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 23000); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpVoltagePhaseB, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 23100); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpVoltagePhaseC, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 22900); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpVoltagePhaseAb, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 40000); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpVoltagePhaseBc, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 40100); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpVoltagePhaseAc, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 39900); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(GcpMgcpGetMeasurementData(use_case_.get(), kGcpFrequency, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 50); + EXPECT_EQ(value.scale, 0); + + // 2. Receive the detailed discovery request and send the response + ExpectSendMessage(send::discovery_reply); + HandleMessage(receive::discovery_request); + + // 3. Receive the detailed discovery response and send subscriptions + use case read + ExpectSendMessage(send::node_management_subscription_call); + ExpectSendMessage(send::use_case_data_read); + HandleMessage(receive::discovery_response); + + // 4. Receive the Node Management subscription request and send result + ExpectSendMessage(send::result_data_msg_cnt_ref_3); + HandleMessage(receive::node_management_subscription_request); + + // 5. Receive the use case discovery request and send the reply + ExpectSendMessage(send::use_case_data_reply); + HandleMessage(receive::use_case_request); + + // 6. Receive the electrical connection subscription request and send result + ExpectSendMessage(send::result_data_msg_cnt_ref_5); + HandleMessage(receive::electrical_connection_subscription_request); + + // 7. Receive the electrical connection read request and send the response + ExpectSendMessage(send::electrical_connection_description_reply); + HandleMessage(receive::electrical_connection_request); + + // 8. Receive the electrical connection parameter description request and send the response + ExpectSendMessage(send::electrical_connection_parameter_description_reply); + HandleMessage(receive::electrical_connection_parameter_description_request); + + // 9. Receive the measurement subscription request and send result + ExpectSendMessage(send::result_data_msg_cnt_ref_8); + HandleMessage(receive::measurement_subscription_request); + + // 10. Receive the measurement description request and send the response + ExpectSendMessage(send::measurement_description_reply); + HandleMessage(receive::measurement_description_request); + + // 11. Receive the measurement constraints request and send the response + ExpectSendMessage(send::measurement_constraints_reply); + HandleMessage(receive::measurement_constraints_request); + + // 12. Receive the device configuration subscription request and send result + ExpectSendMessage(send::result_data_msg_cnt_ref_11); + HandleMessage(receive::device_configuration_subscription_request); + + // 13. Receive the device configuration description request and send the response + ExpectSendMessage(send::device_configuration_description_reply); + HandleMessage(receive::device_configuration_description_request); + + // 14. Receive the device configuration key value request and send the response + ExpectSendMessage(send::device_configuration_key_value_reply); + HandleMessage(receive::device_configuration_key_value_request); + + // 15. Receive the result with message counter reference 3 + HandleMessage(receive::result_data_msg_cnt_ref_3); + + // 16. Receive the Use Case reply + HandleMessage(receive::use_case_reply); + + // 17. Update scenario 2 (power) and expect the notify + static constexpr ScaledValue new_power_total{3000, 0}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpPowerTotal, &new_power_total, NULL, NULL); + ExpectSendMessage(send::measurement_notify_power); + GcpMgcpUpdate(use_case_.get()); + + // 18. Update scenarios 3+4 (energy) and expect the notify + static constexpr ScaledValue new_energy_feed_in{5000, 0}; + GcpMgcpSetEnergyFeedInCache(use_case_.get(), &new_energy_feed_in, NULL, NULL, NULL, NULL); + static constexpr ScaledValue new_energy_consumed{8000, 0}; + GcpMgcpSetEnergyConsumedCache(use_case_.get(), &new_energy_consumed, NULL, NULL, NULL, NULL); + ExpectSendMessage(send::measurement_notify_energy); + GcpMgcpUpdate(use_case_.get()); + + // 19. Update scenario 5 (current) and expect the notify + static constexpr ScaledValue new_current_a{200, -1}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpCurrentPhaseA, &new_current_a, NULL, NULL); + static constexpr ScaledValue new_current_b{210, -1}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpCurrentPhaseB, &new_current_b, NULL, NULL); + static constexpr ScaledValue new_current_c{220, -1}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpCurrentPhaseC, &new_current_c, NULL, NULL); + ExpectSendMessage(send::measurement_notify_current); + GcpMgcpUpdate(use_case_.get()); + + // 20. Update scenario 6 (voltage) and expect the notify + static constexpr ScaledValue new_voltage_a{24000, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseA, &new_voltage_a, NULL, NULL); + static constexpr ScaledValue new_voltage_b{24100, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseB, &new_voltage_b, NULL, NULL); + static constexpr ScaledValue new_voltage_c{23900, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseC, &new_voltage_c, NULL, NULL); + static constexpr ScaledValue new_voltage_ab{41000, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseAb, &new_voltage_ab, NULL, NULL); + static constexpr ScaledValue new_voltage_bc{41100, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseBc, &new_voltage_bc, NULL, NULL); + static constexpr ScaledValue new_voltage_ac{40900, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpVoltagePhaseAc, &new_voltage_ac, NULL, NULL); + ExpectSendMessage(send::measurement_notify_voltage); + GcpMgcpUpdate(use_case_.get()); + + // 21. Update scenario 7 (frequency) and expect the notify + static constexpr ScaledValue new_frequency{5000, -2}; + GcpMgcpSetMeasurementDataCache(use_case_.get(), kGcpFrequency, &new_frequency, NULL, NULL); + ExpectSendMessage(send::measurement_notify_frequency); + GcpMgcpUpdate(use_case_.get()); +} + +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_description_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_description_request.inc new file mode 100644 index 0000000..1a5c836 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_description_request.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char device_configuration_description_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 12 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_key_value_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_key_value_request.inc new file mode 100644 index 0000000..bbc2b8a --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_key_value_request.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char device_configuration_key_value_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 13 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_subscription_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_subscription_request.inc new file mode 100644 index 0000000..32b87b7 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/device_configuration_subscription_request.inc @@ -0,0 +1,107 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char device_configuration_subscription_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 11 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "serverFeatureType": "DeviceConfiguration" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/discovery_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/discovery_request.inc new file mode 100644 index 0000000..cb14e4b --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/discovery_request.inc @@ -0,0 +1,63 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char discovery_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 1 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/discovery_response.inc b/tests/src/use_case/actor/gcp/mgcp/receive/discovery_response.inc new file mode 100644 index 0000000..eb35438 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/discovery_response.inc @@ -0,0 +1,512 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char discovery_response[] = R"RAWFILE({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 2 + }, + { + "msgCounterReference": 1 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [ + { + "specificationVersionList": [ + { + "specificationVersion": [ + "1.3.0" + ] + } + ] + }, + { + "deviceInformation": [ + { + "description": [ + { + "deviceAddress": [ + { + "device": "d:_n:HEMS_123456789" + } + ] + }, + { + "deviceType": "Generic" + }, + { + "networkFeatureSet": "smart" + } + ] + } + ] + }, + { + "entityInformation": [ + [ + { + "description": [ + { + "entityAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + } + ] + }, + { + "entityType": "DeviceInformation" + } + ] + } + ], + [ + { + "description": [ + { + "entityAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + } + ] + }, + { + "entityType": "CEM" + } + ] + } + ] + ] + }, + { + "featureInformation": [ + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "featureType": "NodeManagement" + }, + { + "role": "special" + }, + { + "supportedFunction": [ + [ + { + "function": "nodeManagementSubscriptionData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementBindingData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementBindingRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementBindingDeleteCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementDestinationListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementDetailedDiscoveryData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementUseCaseData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementSubscriptionRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementSubscriptionDeleteCall" + }, + { + "possibleOperations": [] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "DeviceClassification" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "deviceClassificationManufacturerData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "DeviceDiagnosis" + }, + { + "role": "client" + }, + { + "description": "DeviceDiagnosis Client" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "featureType": "LoadControl" + }, + { + "role": "client" + }, + { + "description": "LoadControl Client" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "featureType": "DeviceConfiguration" + }, + { + "role": "client" + }, + { + "description": "DeviceConfiguration Client" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 4 + } + ] + }, + { + "featureType": "ElectricalConnection" + }, + { + "role": "client" + }, + { + "description": "ElectricalConnection Client" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 5 + } + ] + }, + { + "featureType": "DeviceDiagnosis" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "deviceDiagnosisHeartbeatData" + }, + { + "possibleOperations": [ + {)RAWFILE" R"RAWFILE( + "read": [] + } + ] + } + ] + ] + }, + { + "description": "DeviceDiagnosis Server" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "featureType": "Measurement" + }, + { + "role": "client" + }, + { + "description": "Measurement Client" + } + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})RAWFILE"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_parameter_description_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_parameter_description_request.inc new file mode 100644 index 0000000..5e4e679 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_parameter_description_request.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char electrical_connection_parameter_description_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 4 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "msgCounter": 7 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionParameterDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_request.inc new file mode 100644 index 0000000..823ba09 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_request.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char electrical_connection_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 4 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "msgCounter": 6 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_subscription_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_subscription_request.inc new file mode 100644 index 0000000..be61a23 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/electrical_connection_subscription_request.inc @@ -0,0 +1,107 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char electrical_connection_subscription_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 5 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 4 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "serverFeatureType": "ElectricalConnection" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/measurement_constraints_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/measurement_constraints_request.inc new file mode 100644 index 0000000..f4ec667 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/measurement_constraints_request.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char measurement_constraints_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 10 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementConstraintsListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/measurement_description_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/measurement_description_request.inc new file mode 100644 index 0000000..4631ad7 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/measurement_description_request.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char measurement_description_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 9 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/measurement_subscription_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/measurement_subscription_request.inc new file mode 100644 index 0000000..551589b --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/measurement_subscription_request.inc @@ -0,0 +1,107 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char measurement_subscription_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 8 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "serverFeatureType": "Measurement" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/node_management_subscription_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/node_management_subscription_request.inc new file mode 100644 index 0000000..1be3198 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/node_management_subscription_request.inc @@ -0,0 +1,107 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char node_management_subscription_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 3 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverFeatureType": "NodeManagement" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/result_data_msg_cnt_ref_3.inc b/tests/src/use_case/actor/gcp/mgcp/receive/result_data_msg_cnt_ref_3.inc new file mode 100644 index 0000000..0aa9f09 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/result_data_msg_cnt_ref_3.inc @@ -0,0 +1,73 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char result_data_msg_cnt_ref_3[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 13 + }, + { + "msgCounterReference": 3 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/use_case_reply.inc b/tests/src/use_case/actor/gcp/mgcp/receive/use_case_reply.inc new file mode 100644 index 0000000..a50a27b --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/use_case_reply.inc @@ -0,0 +1,163 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char use_case_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 12 + }, + { + "msgCounterReference": 4 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [ + { + "useCaseInformation": [ + [ + { + "address": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + } + ] + }, + { + "actor": "MonitoringAppliance" + }, + { + "useCaseSupport": [ + [ + { + "useCaseName": "monitoringOfGridConnectionPoint" + }, + { + "useCaseVersion": "1.0.0" + }, + { + "useCaseAvailable": true + }, + { + "scenarioSupport": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + { + "useCaseDocumentSubRevision": "release" + } + ] + ] + } + ], + [ + { + "address": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + } + ] + }, + { + "actor": "EnergyGuard" + }, + { + "useCaseSupport": [ + [ + { + "useCaseName": "limitationOfPowerConsumption" + }, + { + "useCaseVersion": "1.0.0" + }, + { + "useCaseAvailable": true + }, + { + "scenarioSupport": [ + 1, + 2, + 3, + 4 + ] + }, + { + "useCaseDocumentSubRevision": "release" + } + ] + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/receive/use_case_request.inc b/tests/src/use_case/actor/gcp/mgcp/receive/use_case_request.inc new file mode 100644 index 0000000..d7d391a --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/receive/use_case_request.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace receive { + +static constexpr char use_case_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 4 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/device_configuration_description_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/device_configuration_description_reply.inc new file mode 100644 index 0000000..bb7b8c7 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/device_configuration_description_reply.inc @@ -0,0 +1,88 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char device_configuration_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 14 + }, + { + "msgCounterReference": 12 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueDescriptionListData": [ + { + "deviceConfigurationKeyValueDescriptionData": [ + [ + { + "keyId": 0 + }, + { + "keyName": "pvCurtailmentLimitFactor" + }, + { + "valueType": "scaledNumber" + }, + { + "unit": "pct" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/device_configuration_key_value_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/device_configuration_key_value_reply.inc new file mode 100644 index 0000000..8c060fc --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/device_configuration_key_value_reply.inc @@ -0,0 +1,93 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char device_configuration_key_value_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 15 + }, + { + "msgCounterReference": 13 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueListData": [ + { + "deviceConfigurationKeyValueData": [ + [ + { + "keyId": 0 + }, + { + "value": [ + { + "scaledNumber": [ + { + "number": 75 + }, + { + "scale": 0 + } + ] + } + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/discovery_read.inc b/tests/src/use_case/actor/gcp/mgcp/send/discovery_read.inc new file mode 100644 index 0000000..27b0a4c --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/discovery_read.inc @@ -0,0 +1,63 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char discovery_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 1 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/discovery_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/discovery_reply.inc new file mode 100644 index 0000000..e76bb01 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/discovery_reply.inc @@ -0,0 +1,466 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char discovery_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 2 + }, + { + "msgCounterReference": 1 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [ + { + "specificationVersionList": [ + { + "specificationVersion": [ + "1.3.0" + ] + } + ] + }, + { + "deviceInformation": [ + { + "description": [ + { + "deviceAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + } + ] + }, + { + "networkFeatureSet": "smart" + } + ] + } + ] + }, + { + "entityInformation": [ + [ + { + "description": [ + { + "entityAddress": [ + { + "entity": [ + 0 + ] + } + ] + }, + { + "entityType": "DeviceInformation" + } + ] + } + ], + [ + { + "description": [ + { + "entityAddress": [ + { + "entity": [ + 1 + ] + } + ] + }, + { + "entityType": "GridConnectionPointOfPremises" + } + ] + } + ] + ] + }, + { + "featureInformation": [ + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "featureType": "NodeManagement" + }, + { + "role": "special" + }, + { + "supportedFunction": [ + [ + { + "function": "nodeManagementBindingData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementBindingDeleteCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementBindingRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementDestinationListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementDetailedDiscoveryData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementSubscriptionData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementSubscriptionDeleteCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementSubscriptionRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementUseCaseData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "DeviceClassification" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "deviceClassificationManufacturerData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "ElectricalConnection" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "electricalConnectionDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "electricalConnectionParameterDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + }, + { + "description": "ElectricalConnection server" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "featureType": "Measurement" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "measurementListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "measurementDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + }, + { + "description": "Measurement server" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "featureType": "DeviceConfiguration" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "deviceConfigurationKeyValueDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "deviceConfigurationKeyValueListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + }, + { + "description": "DeviceConfiguration server" + } + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_description_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_description_reply.inc new file mode 100644 index 0000000..a7a93f7 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_description_reply.inc @@ -0,0 +1,85 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char electrical_connection_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 4 + } + ] + }, + { + "msgCounter": 8 + }, + { + "msgCounterReference": 6 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionDescriptionListData": [ + { + "electricalConnectionDescriptionData": [ + [ + { + "electricalConnectionId": 0 + }, + { + "powerSupplyType": "ac" + }, + { + "positiveEnergyDirection": "consume" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_parameter_description_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_parameter_description_reply.inc new file mode 100644 index 0000000..41cad64 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/electrical_connection_parameter_description_reply.inc @@ -0,0 +1,373 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char electrical_connection_parameter_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 4 + } + ] + }, + { + "msgCounter": 9 + }, + { + "msgCounterReference": 7 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionParameterDescriptionListData": [ + { + "electricalConnectionParameterDescriptionData": [ + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 0 + }, + { + "measurementId": 0 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "abc" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 1 + }, + { + "measurementId": 1 + }, + { + "voltageType": "ac" + }, + { + "acMeasurementType": "real" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 2 + }, + { + "measurementId": 2 + }, + { + "voltageType": "ac" + }, + { + "acMeasurementType": "real" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 3 + }, + { + "measurementId": 3 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 4 + }, + { + "measurementId": 4 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 5 + }, + { + "measurementId": 5 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 6 + }, + { + "measurementId": 6 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 7 + }, + { + "measurementId": 7 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 8 + }, + { + "measurementId": 8 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 9 + }, + { + "measurementId": 9 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasuredInReferenceTo": "b" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 10 + }, + { + "measurementId": 10 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasuredInReferenceTo": "c" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 11 + }, + { + "measurementId": 11 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasuredInReferenceTo": "a" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 12 + }, + { + "measurementId": 12 + }, + { + "voltageType": "ac" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/measurement_constraints_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/measurement_constraints_reply.inc new file mode 100644 index 0000000..5fcce7b --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/measurement_constraints_reply.inc @@ -0,0 +1,69 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char measurement_constraints_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 12 + }, + { + "msgCounterReference": 10 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementConstraintsListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/measurement_description_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/measurement_description_reply.inc new file mode 100644 index 0000000..df7398f --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/measurement_description_reply.inc @@ -0,0 +1,295 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char measurement_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 11 + }, + { + "msgCounterReference": 9 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementDescriptionListData": [ + { + "measurementDescriptionData": [ + [ + { + "measurementId": 0 + }, + { + "measurementType": "power" + }, + { + "commodityType": "electricity" + }, + { + "unit": "W" + }, + { + "scopeType": "acPowerTotal" + } + ], + [ + { + "measurementId": 1 + }, + { + "measurementType": "energy" + }, + { + "commodityType": "electricity" + }, + { + "unit": "Wh" + }, + { + "scopeType": "gridFeedIn" + } + ], + [ + { + "measurementId": 2 + }, + { + "measurementType": "energy" + }, + { + "commodityType": "electricity" + }, + { + "unit": "Wh" + }, + { + "scopeType": "gridConsumption" + } + ], + [ + { + "measurementId": 3 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 4 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 5 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 6 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 7 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 8 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 9 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 10 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 11 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 12 + }, + { + "measurementType": "frequency" + }, + { + "commodityType": "electricity" + }, + { + "unit": "Hz" + }, + { + "scopeType": "acFrequency" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_current.inc b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_current.inc new file mode 100644 index 0000000..bde3641 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_current.inc @@ -0,0 +1,150 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char measurement_notify_current[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 18 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 3 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 200 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 4 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 210 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 5 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 220 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_energy.inc b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_energy.inc new file mode 100644 index 0000000..6558dba --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_energy.inc @@ -0,0 +1,129 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char measurement_notify_energy[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 17 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 1 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 5000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 2 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 8000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_frequency.inc b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_frequency.inc new file mode 100644 index 0000000..78ebcce --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_frequency.inc @@ -0,0 +1,108 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char measurement_notify_frequency[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 20 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 12 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 5000 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_power.inc b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_power.inc new file mode 100644 index 0000000..e48063f --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_power.inc @@ -0,0 +1,108 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char measurement_notify_power[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 16 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 3000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_voltage.inc b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_voltage.inc new file mode 100644 index 0000000..eb405de --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/measurement_notify_voltage.inc @@ -0,0 +1,213 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char measurement_notify_voltage[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 19 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 6 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 24000 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 7 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 24100 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 8 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 23900 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 9 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 41000 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 10 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 41100 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 11 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 40900 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/node_management_subscription_call.inc b/tests/src/use_case/actor/gcp/mgcp/send/node_management_subscription_call.inc new file mode 100644 index 0000000..e9469d8 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/node_management_subscription_call.inc @@ -0,0 +1,107 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char node_management_subscription_call[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 3 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverFeatureType": "NodeManagement" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_11.inc b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_11.inc new file mode 100644 index 0000000..9f1ec75 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_11.inc @@ -0,0 +1,73 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char result_data_msg_cnt_ref_11[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 13 + }, + { + "msgCounterReference": 11 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_3.inc b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_3.inc new file mode 100644 index 0000000..8b6c8d2 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_3.inc @@ -0,0 +1,73 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char result_data_msg_cnt_ref_3[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 5 + }, + { + "msgCounterReference": 3 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_5.inc b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_5.inc new file mode 100644 index 0000000..63c4d73 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_5.inc @@ -0,0 +1,73 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char result_data_msg_cnt_ref_5[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 7 + }, + { + "msgCounterReference": 5 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_8.inc b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_8.inc new file mode 100644 index 0000000..a2d292c --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/result_data_msg_cnt_ref_8.inc @@ -0,0 +1,73 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char result_data_msg_cnt_ref_8[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 10 + }, + { + "msgCounterReference": 8 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/use_case_data_read.inc b/tests/src/use_case/actor/gcp/mgcp/send/use_case_data_read.inc new file mode 100644 index 0000000..58de390 --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/use_case_data_read.inc @@ -0,0 +1,66 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char use_case_data_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 4 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/gcp/mgcp/send/use_case_data_reply.inc b/tests/src/use_case/actor/gcp/mgcp/send/use_case_data_reply.inc new file mode 100644 index 0000000..cc501bd --- /dev/null +++ b/tests/src/use_case/actor/gcp/mgcp/send/use_case_data_reply.inc @@ -0,0 +1,120 @@ +namespace gcp_mgcp_test { +namespace send { + +static constexpr char use_case_data_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 6 + }, + { + "msgCounterReference": 4 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [ + { + "useCaseInformation": [ + [ + { + "address": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + } + ] + }, + { + "actor": "GridConnectionPoint" + }, + { + "useCaseSupport": [ + [ + { + "useCaseName": "monitoringOfGridConnectionPoint" + }, + { + "useCaseVersion": "1.0.0" + }, + { + "useCaseAvailable": true + }, + { + "scenarioSupport": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + { + "useCaseDocumentSubRevision": "release" + } + ] + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace gcp_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/CMakeLists.txt b/tests/src/use_case/actor/ma/mgcp/CMakeLists.txt new file mode 100644 index 0000000..59ced84 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/CMakeLists.txt @@ -0,0 +1,151 @@ +cmake_minimum_required(VERSION 3.15) + +set(TEST_NAME ma_mgcp_test) + +project(${TEST_NAME} LANGUAGES C CXX) + +add_executable(${TEST_NAME}) + +# Set proper runtime library for Windows to avoid LIBCMT conflicts +if(WIN32) + set_property(TARGET ${TEST_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") +endif() + +include(${MAIN_PROJ_SOURCES_PATH}/common/eebus_date_time/eebus_date_time_sources.cmake) +include(${MAIN_PROJ_SOURCES_PATH}/common/eebus_data/eebus_data_sources.cmake) + +target_sources( + ${TEST_NAME} + PRIVATE + ${GTEST_SOURCES} + + # Main project sources + ${EEBUS_DATE_TIME_SOURCES} + ${EEBUS_DATA_SOURCES} + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_device_info.c + ${MAIN_PROJ_SOURCES_PATH}/common/json_impl_cjson.c + ${MAIN_PROJ_SOURCES_PATH}/common/message_buffer.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_math/eebus_math.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_mutex/eebus_mutex.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_queue/eebus_queue.c + ${MAIN_PROJ_SOURCES_PATH}/common/eebus_thread/eebus_thread.c + ${MAIN_PROJ_SOURCES_PATH}/common/service_details.c + ${MAIN_PROJ_SOURCES_PATH}/common/string_lut.c + ${MAIN_PROJ_SOURCES_PATH}/common/string_util.c + ${MAIN_PROJ_SOURCES_PATH}/common/uint64_lut.c + ${MAIN_PROJ_SOURCES_PATH}/common/vector.c + ${MAIN_PROJ_SOURCES_PATH}/spine/binding/binding_manager.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/data_reader.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/device_local.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/device_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/device.c + ${MAIN_PROJ_SOURCES_PATH}/spine/device/sender.c + ${MAIN_PROJ_SOURCES_PATH}/spine/entity/entity_local.c + ${MAIN_PROJ_SOURCES_PATH}/spine/entity/entity_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/entity/entity.c + ${MAIN_PROJ_SOURCES_PATH}/spine/events/events.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_address_container.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_local.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/feature_functions.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/operations.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature/pending_write_request.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature_link/feature_link.c + ${MAIN_PROJ_SOURCES_PATH}/spine/feature_link/feature_link_container.c + ${MAIN_PROJ_SOURCES_PATH}/spine/function/function.c + ${MAIN_PROJ_SOURCES_PATH}/spine/heartbeat/heartbeat_manager.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/absolute_or_relative_time.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/binding_management_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/cmd.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/datagram.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/device_configuration_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/entity_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/feature_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/filter.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/function_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/measurement_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/model.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/node_management_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/possible_operations_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/specification_version.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/subscription_management_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/model/usecase_information_types.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_binding.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_destination_list.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_detailed_discovery.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_remote.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_subscription.c + ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_usecase.c + ${MAIN_PROJ_SOURCES_PATH}/spine/subscription/subscription_manager.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/ma_events.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/ma_measurement_base.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/mgcp/ma_mgcp_events.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/mgcp/ma_mgcp_measurement.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/mgcp/ma_mgcp_public.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/mgcp/ma_mgcp.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/model/scaled_value.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/model/mgcp_types.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/device_configuration/device_configuration_client.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/device_configuration/device_configuration_common.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/electrical_connection/electrical_connection_client.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/electrical_connection/electrical_connection_common.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/measurement/measurement_client.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/measurement/measurement_common.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/feature_info_client.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/helper.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/use_case.c + + # Mocks sources + ${MOCKS_SOURCES_PATH}/ship/ship_connection/data_writer_mock.cpp + ${MOCKS_SOURCES_PATH}/common/eebus_timer/eebus_timer_mock.cpp + ${MOCKS_SOURCES_PATH}/use_case/api/ma_mgcp_listener_mock.cpp + + # Test helpers + ${CMAKE_SOURCE_DIR}/src/spine/function_data.c + ${CMAKE_SOURCE_DIR}/src/use_case/use_case_test_fixture.cpp + + ma_mgcp_test.cpp +) + +target_include_directories( + ${TEST_NAME} + PRIVATE + ${PROJECT_INCLUDES_PATH} +) + +target_compile_options( + ${TEST_NAME} + PRIVATE + ${PROJECT_COMPILE_OPTIONS} +) + +target_compile_definitions( + ${TEST_NAME} + PRIVATE + ${PROJECT_COMPILE_DEFINITIONS} + MEMORY_LEAKS_TEST +) + +target_link_options( + ${TEST_NAME} + PRIVATE + ${PROJECT_LINK_OPTIONS} +) + +target_link_libraries( + ${TEST_NAME} + PRIVATE + ${PROJECT_LINK_LIBRARIES} + cjson +) + +add_test( + NAME + ${TEST_NAME} + COMMAND + ${EXECUTABLE_OUTPUT_PATH}/${TEST_NAME} +) + +gtest_discover_tests(${TEST_NAME}) diff --git a/tests/src/use_case/actor/ma/mgcp/ma_mgcp_test.cpp b/tests/src/use_case/actor/ma/mgcp/ma_mgcp_test.cpp new file mode 100644 index 0000000..0460fa7 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/ma_mgcp_test.cpp @@ -0,0 +1,318 @@ +/* + * Copyright 2025 NIBE AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @brief Currently it is not a regular unit test but more a "sand box" + * to feed the SPINE Device with specific datagrams and check the outgoing messages printed. + * @note Remember to enable the message printing in PrintMessage() before getting started + */ + +#include "src/use_case/actor/ma/mgcp/ma_mgcp.h" + +#include + +#include +#include + +#include "mocks/common/eebus_timer/eebus_timer_mock.h" +#include "mocks/ship/ship_connection/data_writer_mock.h" +#include "mocks/use_case/api/ma_mgcp_listener_mock.h" +#include "src/common/array_util.h" +#include "src/common/eebus_malloc.h" +#include "src/common/eebus_timer/eebus_timer.h" +#include "src/common/message_buffer.h" +#include "src/spine/device/device_local.h" +#include "src/spine/device/device_local_internal.h" +#include "src/spine/entity/entity_local.h" +#include "tests/src/json.h" +#include "tests/src/use_case/actor/ma/mgcp/receive/device_configuration_description_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/device_configuration_key_value_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/discovery_request.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/discovery_response.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_description_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_parameter_description_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_constraints_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_description_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_current.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy_feed_in_only.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_frequency.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_mixed.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power_total_only.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_voltage.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/measurement_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/node_management_subscription_request.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_11.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_3.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_5.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_8.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/use_case_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/receive/use_case_request.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/device_configuration_description_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/device_configuration_key_value_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/device_configuration_subscription_call.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/discovery_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/discovery_reply.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/electrical_connection_description_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/electrical_connection_parameter_description_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/electrical_connection_subscription_call.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/measurement_constraints_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/measurement_description_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/measurement_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/measurement_subscription_call.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/node_management_subscription_call.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/result_data_msg_cnt_ref_3.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/use_case_data_read.inc" +#include "tests/src/use_case/actor/ma/mgcp/send/use_case_data_reply.inc" +#include "tests/src/use_case/use_case_test_fixture.h" + +using testing::_; +using testing::Return; + +namespace ma_mgcp_test { + +class MaMgcpTestFixture : public UseCaseTestFixture { + public: + MaMgcpTestFixture() : UseCaseTestFixture("HEMS", "HEMS", "123456789") {}; + void SetUpUseCase() override { + uint32_t entity_ids[1]{static_cast(VectorGetSize(DEVICE_LOCAL_GET_ENTITIES(device_local_.get())))}; + + EntityLocalObject* const entity = EntityLocalCreate( + device_local_.get(), + kEntityTypeTypeCEM, + entity_ids, + ARRAY_SIZE(entity_ids), + kHeartbeatTimeout + ); + + ma_mgcp_listener_mock_.reset(MaMgcpListenerMockCreate()); + use_case_.reset(MaMgcpUseCaseCreate(entity, MA_MGCP_LISTENER_OBJECT(ma_mgcp_listener_mock_.get()))); + + DEVICE_LOCAL_ADD_ENTITY(device_local_.get(), entity); + ExpectSendMessage(send::discovery_read); + }; + + void TearDownUseCase() override { + EXPECT_CALL(*ma_mgcp_listener_mock_->gmock, Destruct(_)).WillOnce(Return()); + use_case_.reset(); + ma_mgcp_listener_mock_.reset(); + }; + + void ExpectMeasurementsReceive( + MaMgcpListenerGMock* mock, + const std::map& expected_measurements + ) { + for (const auto& [name_id, scaled_value] : expected_measurements) { + const int64_t value{scaled_value.value}; + const int8_t scale{scaled_value.scale}; + EXPECT_CALL(*mock, OnMeasurementReceive(_, name_id, ScaledValueEq(value, scale), _)).WillOnce(Return()); + } + } + + protected: + std::unique_ptr ma_mgcp_listener_mock_{ + nullptr, + MaMgcpListenerMockDelete + }; + + std::unique_ptr use_case_{nullptr, MaMgcpUseCaseDelete}; +}; + +TEST_F(MaMgcpTestFixture, MaMgcpTest) { + // 1. Receive the detailed discovery request and send the response + ExpectSendMessage(send::discovery_reply); + HandleMessage(receive::discovery_request); + + // 2. Receive the detailed discovery response and send subscriptions + use case read + EXPECT_CALL(*ma_mgcp_listener_mock_->gmock, OnRemoteEntityConnect(_, _)).WillOnce(Return()); + ExpectSendMessage(send::node_management_subscription_call); + ExpectSendMessage(send::use_case_data_read); + HandleMessage(receive::discovery_response); + + // 3. Receive the Node Management subscription request and send result + ExpectSendMessage(send::result_data_msg_cnt_ref_3); + HandleMessage(receive::node_management_subscription_request); + + // 4. Receive the use case discovery request and send the reply + ExpectSendMessage(send::use_case_data_reply); + HandleMessage(receive::use_case_request); + + // 5. Receive the result with message counter reference 3 + HandleMessage(receive::result_data_msg_cnt_ref_3); + + // 6. Receive the Use Case reply and send electrical connection + measurement + device configuration subscriptions and + // reads + ExpectSendMessage(send::electrical_connection_subscription_call); + ExpectSendMessage(send::electrical_connection_description_read); + ExpectSendMessage(send::electrical_connection_parameter_description_read); + ExpectSendMessage(send::measurement_subscription_call); + ExpectSendMessage(send::measurement_description_read); + ExpectSendMessage(send::measurement_constraints_read); + ExpectSendMessage(send::device_configuration_subscription_call); + ExpectSendMessage(send::device_configuration_description_read); + HandleMessage(receive::use_case_reply); + + // 7. Receive the result with message counter reference 5 + HandleMessage(receive::result_data_msg_cnt_ref_5); + + // 8. Receive the electrical connection description reply + HandleMessage(receive::electrical_connection_description_reply); + + // 9. Receive the electrical connection parameter description reply + HandleMessage(receive::electrical_connection_parameter_description_reply); + + // 10. Receive the result with message counter reference 8 + HandleMessage(receive::result_data_msg_cnt_ref_8); + + // 11. Receive the measurement description reply and send the measurement read + ExpectSendMessage(send::measurement_read); + HandleMessage(receive::measurement_description_reply); + + // 12. Receive the measurement constraints reply + HandleMessage(receive::measurement_constraints_reply); + + // 13. Receive the device configuration subscription result + HandleMessage(receive::result_data_msg_cnt_ref_11); + + // 14. Receive the device configuration description reply and send the key value read + ExpectSendMessage(send::device_configuration_key_value_read); + HandleMessage(receive::device_configuration_description_reply); + + // 15. Receive the measurement reply (power_total) + EXPECT_CALL(*ma_mgcp_listener_mock_->gmock, OnMeasurementReceive(_, kGcpPowerTotal, ScaledValueEq(33000, -1), _)) + .WillOnce(Return()); + + HandleMessage(receive::measurement_reply); + + // 16. Receive the device configuration key value reply + EXPECT_CALL(*ma_mgcp_listener_mock_->gmock, OnPvCurtailmentLimitFactorReceive(_, ScaledValueEq(75, 0), _)) + .WillOnce(Return()); + + HandleMessage(receive::device_configuration_key_value_reply); + + // 17. Receive the measurement notify (power_total) + const std::map expected_power{ + {kGcpPowerTotal, {.value = 5000, .scale = 0}}, + }; + + ExpectMeasurementsReceive(ma_mgcp_listener_mock_->gmock, expected_power); + HandleMessage(receive::measurement_notify_power); + + // 18. Receive the measurement notify (energy) + const std::map expected_energy{ + { kGcpEnergyFeedIn, {.value = 200000, .scale = 0}}, + {kGcpEnergyConsumed, {.value = 800000, .scale = 0}}, + }; + + ExpectMeasurementsReceive(ma_mgcp_listener_mock_->gmock, expected_energy); + HandleMessage(receive::measurement_notify_energy); + + // 19. Receive the measurement notify (current — all three phases) + const std::map expected_current{ + {kGcpCurrentPhaseA, {.value = 15, .scale = -1}}, + {kGcpCurrentPhaseB, {.value = 12, .scale = -1}}, + {kGcpCurrentPhaseC, {.value = 16, .scale = -1}}, + }; + + ExpectMeasurementsReceive(ma_mgcp_listener_mock_->gmock, expected_current); + HandleMessage(receive::measurement_notify_current); + + // 20. Receive the measurement notify (voltage — all six phase combinations, scenario 6) + const std::map expected_voltage{ + { kGcpVoltagePhaseA, {.value = 2310, .scale = -1}}, + { kGcpVoltagePhaseB, {.value = 2295, .scale = -1}}, + { kGcpVoltagePhaseC, {.value = 2320, .scale = -1}}, + {kGcpVoltagePhaseAb, {.value = 3998, .scale = -1}}, + {kGcpVoltagePhaseBc, {.value = 4012, .scale = -1}}, + {kGcpVoltagePhaseAc, {.value = 3986, .scale = -1}}, + }; + + ExpectMeasurementsReceive(ma_mgcp_listener_mock_->gmock, expected_voltage); + HandleMessage(receive::measurement_notify_voltage); + + // 21. Receive the measurement notify (frequency) + const std::map expected_frequency{ + {kGcpFrequency, {.value = 500, .scale = -1}}, + }; + + ExpectMeasurementsReceive(ma_mgcp_listener_mock_->gmock, expected_frequency); + HandleMessage(receive::measurement_notify_frequency); + + // 22. Receive a partial notify with power total only — tests single-measurement partial + // and overwrites the kGcpPowerTotal initially received in the measurement reply (step 15) + EXPECT_CALL(*ma_mgcp_listener_mock_->gmock, OnMeasurementReceive(_, kGcpPowerTotal, ScaledValueEq(5500, 0), _)) + .WillOnce(Return()); + HandleMessage(receive::measurement_notify_power_total_only); + + // 23. Receive a cross-scenario notify — tests that one notify can carry measurements from + // different scenarios; also overwrites kGcpPowerTotal, kGcpCurrentPhaseB, + // kGcpVoltagePhaseAb and kGcpFrequency set in earlier notifies + const std::map expected_mixed{ + { kGcpPowerTotal, {.value = 6200, .scale = 0}}, + { kGcpCurrentPhaseB, {.value = 13, .scale = -1}}, + {kGcpVoltagePhaseAb, {.value = 4020, .scale = -1}}, + { kGcpFrequency, {.value = 4998, .scale = -2}}, + }; + + ExpectMeasurementsReceive(ma_mgcp_listener_mock_->gmock, expected_mixed); + HandleMessage(receive::measurement_notify_mixed); + + // 24. Receive a partial notify with energy feed-in only — tests single-measurement partial + // from a different scenario and overwrites kGcpEnergyFeedIn set in step 18 + EXPECT_CALL(*ma_mgcp_listener_mock_->gmock, OnMeasurementReceive(_, kGcpEnergyFeedIn, ScaledValueEq(250000, 0), _)) + .WillOnce(Return()); + HandleMessage(receive::measurement_notify_energy_feed_in_only); + + // 25. Get all 13 measurements via GetMeasurementData() and verify the final stored values + // after all notifies (overwritten values reflect the latest notify that touched each ID). + // Also verify the PV curtailment limit factor from step 16. + ScaledValue value{0}; + static constexpr uint32_t remote_entity_id{1}; + + static constexpr const uint32_t* const remote_entity_ids[]{&remote_entity_id}; + + const EntityAddressType remote_entity_addr + = {"d:_n:GridConnectionPoint_123456789", remote_entity_ids, ARRAY_SIZE(remote_entity_ids)}; + + const std::map expected_final{ + { kGcpPowerTotal, {.value = 6200, .scale = 0}}, // step 23 overwrites step 22 + { kGcpEnergyFeedIn, {.value = 250000, .scale = 0}}, // step 24 overwrites step 18 + {kGcpEnergyConsumed, {.value = 800000, .scale = 0}}, // step 18 + { kGcpCurrentPhaseA, {.value = 15, .scale = -1}}, // step 19 + { kGcpCurrentPhaseB, {.value = 13, .scale = -1}}, // step 23 overwrites step 19 + { kGcpCurrentPhaseC, {.value = 16, .scale = -1}}, // step 19 + { kGcpVoltagePhaseA, {.value = 2310, .scale = -1}}, // step 20 + { kGcpVoltagePhaseB, {.value = 2295, .scale = -1}}, // step 20 + { kGcpVoltagePhaseC, {.value = 2320, .scale = -1}}, // step 20 + {kGcpVoltagePhaseAb, {.value = 4020, .scale = -1}}, // step 23 overwrites step 20 + {kGcpVoltagePhaseBc, {.value = 4012, .scale = -1}}, // step 20 + {kGcpVoltagePhaseAc, {.value = 3986, .scale = -1}}, // step 20 + { kGcpFrequency, {.value = 4998, .scale = -2}}, // step 23 overwrites step 21 + }; + + for (const auto& [name_id, scaled_value] : expected_final) { + EXPECT_EQ(MaMgcpGetMeasurementData(use_case_.get(), name_id, &remote_entity_addr, &value), kEebusErrorOk); + EXPECT_THAT(&value, ScaledValueEq(scaled_value.value, scaled_value.scale)); + } + + EXPECT_EQ(MaMgcpGetPvCurtailmentLimitFactor(use_case_.get(), &remote_entity_addr, &value), kEebusErrorOk); + EXPECT_THAT(&value, ScaledValueEq(75, 0)); + + // 26. Expect the remote entity disconnect event while tearing down the use case + EXPECT_CALL(*ma_mgcp_listener_mock_->gmock, OnRemoteEntityDisconnect(_, _)); +} + +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/device_configuration_description_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/device_configuration_description_reply.inc new file mode 100644 index 0000000..bdae9b4 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/device_configuration_description_reply.inc @@ -0,0 +1,88 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char device_configuration_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 14 + }, + { + "msgCounterReference": 12 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueDescriptionListData": [ + { + "deviceConfigurationKeyValueDescriptionData": [ + [ + { + "keyId": 0 + }, + { + "keyName": "pvCurtailmentLimitFactor" + }, + { + "valueType": "scaledNumber" + }, + { + "unit": "pct" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/device_configuration_key_value_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/device_configuration_key_value_reply.inc new file mode 100644 index 0000000..7a962b5 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/device_configuration_key_value_reply.inc @@ -0,0 +1,90 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char device_configuration_key_value_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 16 + }, + { + "msgCounterReference": 16 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueListData": [ + { + "deviceConfigurationKeyValueData": [ + [ + { + "keyId": 0 + }, + { + "value": [ + { + "scaledNumber": [ + { + "number": 75 + } + ] + } + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/discovery_request.inc b/tests/src/use_case/actor/ma/mgcp/receive/discovery_request.inc new file mode 100644 index 0000000..fca5606 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/discovery_request.inc @@ -0,0 +1,63 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char discovery_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 1 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/discovery_response.inc b/tests/src/use_case/actor/ma/mgcp/receive/discovery_response.inc new file mode 100644 index 0000000..ff35e95 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/discovery_response.inc @@ -0,0 +1,499 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char discovery_response[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 2 + }, + { + "msgCounterReference": 1 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [ + { + "specificationVersionList": [ + { + "specificationVersion": [ + "1.3.0" + ] + } + ] + }, + { + "deviceInformation": [ + { + "description": [ + { + "deviceAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + } + ] + }, + { + "networkFeatureSet": "smart" + } + ] + } + ] + }, + { + "entityInformation": [ + [ + { + "description": [ + { + "entityAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + } + ] + }, + { + "entityType": "DeviceInformation" + } + ] + } + ], + [ + { + "description": [ + { + "entityAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + } + ] + }, + { + "entityType": "GridConnectionPointOfPremises" + } + ] + } + ] + ] + }, + { + "featureInformation": [ + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "featureType": "NodeManagement" + }, + { + "role": "special" + }, + { + "supportedFunction": [ + [ + { + "function": "nodeManagementBindingData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementBindingDeleteCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementBindingRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementDestinationListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementDetailedDiscoveryData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementSubscriptionData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementSubscriptionDeleteCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementSubscriptionRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementUseCaseData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "DeviceClassification" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "deviceClassificationManufacturerData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "ElectricalConnection" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "electricalConnectionDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "electricalConnectionParameterDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + }, + { + "description": "ElectricalConnection server" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "featureType": "Measurement" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "measurementListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "measurementDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "measurementConstraintsListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + }, + { + "description": "Measurement server" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "featureType": "DeviceConfiguration" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "deviceConfigurationKeyValueDescriptionListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "deviceConfigurationKeyValueListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + }, + { + "description": "DeviceConfiguration server" + } + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_description_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_description_reply.inc new file mode 100644 index 0000000..54f32ed --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_description_reply.inc @@ -0,0 +1,85 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char electrical_connection_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "msgCounter": 8 + }, + { + "msgCounterReference": 6 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionDescriptionListData": [ + { + "electricalConnectionDescriptionData": [ + [ + { + "electricalConnectionId": 0 + }, + { + "powerSupplyType": "ac" + }, + { + "positiveEnergyDirection": "consume" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_parameter_description_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_parameter_description_reply.inc new file mode 100644 index 0000000..966f6f1 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/electrical_connection_parameter_description_reply.inc @@ -0,0 +1,373 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char electrical_connection_parameter_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "msgCounter": 9 + }, + { + "msgCounterReference": 7 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionParameterDescriptionListData": [ + { + "electricalConnectionParameterDescriptionData": [ + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 0 + }, + { + "measurementId": 0 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "abc" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 1 + }, + { + "measurementId": 1 + }, + { + "voltageType": "ac" + }, + { + "acMeasurementType": "real" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 2 + }, + { + "measurementId": 2 + }, + { + "voltageType": "ac" + }, + { + "acMeasurementType": "real" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 3 + }, + { + "measurementId": 3 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 4 + }, + { + "measurementId": 4 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 5 + }, + { + "measurementId": 5 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 6 + }, + { + "measurementId": 6 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 7 + }, + { + "measurementId": 7 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 8 + }, + { + "measurementId": 8 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 9 + }, + { + "measurementId": 9 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasuredInReferenceTo": "b" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 10 + }, + { + "measurementId": 10 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasuredInReferenceTo": "c" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 11 + }, + { + "measurementId": 11 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasuredInReferenceTo": "a" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 12 + }, + { + "measurementId": 12 + }, + { + "voltageType": "ac" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_constraints_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_constraints_reply.inc new file mode 100644 index 0000000..e094db7 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_constraints_reply.inc @@ -0,0 +1,69 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_constraints_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 12 + }, + { + "msgCounterReference": 10 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementConstraintsListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_description_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_description_reply.inc new file mode 100644 index 0000000..79a9379 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_description_reply.inc @@ -0,0 +1,295 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_description_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 11 + }, + { + "msgCounterReference": 9 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementDescriptionListData": [ + { + "measurementDescriptionData": [ + [ + { + "measurementId": 0 + }, + { + "measurementType": "power" + }, + { + "commodityType": "electricity" + }, + { + "unit": "W" + }, + { + "scopeType": "acPowerTotal" + } + ], + [ + { + "measurementId": 1 + }, + { + "measurementType": "energy" + }, + { + "commodityType": "electricity" + }, + { + "unit": "Wh" + }, + { + "scopeType": "gridFeedIn" + } + ], + [ + { + "measurementId": 2 + }, + { + "measurementType": "energy" + }, + { + "commodityType": "electricity" + }, + { + "unit": "Wh" + }, + { + "scopeType": "gridConsumption" + } + ], + [ + { + "measurementId": 3 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 4 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 5 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 6 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 7 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 8 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 9 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 10 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 11 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 12 + }, + { + "measurementType": "frequency" + }, + { + "commodityType": "electricity" + }, + { + "unit": "Hz" + }, + { + "scopeType": "acFrequency" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_current.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_current.inc new file mode 100644 index 0000000..56ddf9b --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_current.inc @@ -0,0 +1,150 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_notify_current[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 19 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 3 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 15 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 4 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 12 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 5 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 16 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy.inc new file mode 100644 index 0000000..59169f1 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy.inc @@ -0,0 +1,129 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_notify_energy[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 18 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 1 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 200000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 2 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 800000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy_feed_in_only.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy_feed_in_only.inc new file mode 100644 index 0000000..8f736da --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_energy_feed_in_only.inc @@ -0,0 +1,108 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_notify_energy_feed_in_only[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 24 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 1 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 250000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_frequency.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_frequency.inc new file mode 100644 index 0000000..a609475 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_frequency.inc @@ -0,0 +1,108 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_notify_frequency[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 21 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 12 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 500 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_mixed.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_mixed.inc new file mode 100644 index 0000000..41eb466 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_mixed.inc @@ -0,0 +1,174 @@ +namespace ma_mgcp_test { +namespace receive { + +// Cross-scenario partial notify: power total (0), current phase B (4), +// voltage phase AB (9), frequency (12) — tests that a single notify can +// carry measurements from multiple scenarios and overwrite stored values. +static constexpr char measurement_notify_mixed[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 23 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 6200 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 4 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 13 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 9 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 4020 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 12 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 4998 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power.inc new file mode 100644 index 0000000..4b23f61 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power.inc @@ -0,0 +1,108 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_notify_power[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 17 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 5000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power_total_only.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power_total_only.inc new file mode 100644 index 0000000..7dbeca6 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_power_total_only.inc @@ -0,0 +1,108 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_notify_power_total_only[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 22 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 5500 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_voltage.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_voltage.inc new file mode 100644 index 0000000..28af7f2 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_notify_voltage.inc @@ -0,0 +1,213 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_notify_voltage[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 20 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 6 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 2310 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 7 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 2295 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 8 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 2320 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 9 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 3998 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 10 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 4012 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 11 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 3986 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/measurement_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/measurement_reply.inc new file mode 100644 index 0000000..678c3ff --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/measurement_reply.inc @@ -0,0 +1,95 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char measurement_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 15 + }, + { + "msgCounterReference": 15 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 33000 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/node_management_subscription_request.inc b/tests/src/use_case/actor/ma/mgcp/receive/node_management_subscription_request.inc new file mode 100644 index 0000000..7613104 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/node_management_subscription_request.inc @@ -0,0 +1,107 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char node_management_subscription_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 3 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverFeatureType": "NodeManagement" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_11.inc b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_11.inc new file mode 100644 index 0000000..c2a59df --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_11.inc @@ -0,0 +1,73 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char result_data_msg_cnt_ref_11[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 13 + }, + { + "msgCounterReference": 11 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_3.inc b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_3.inc new file mode 100644 index 0000000..edef0a0 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_3.inc @@ -0,0 +1,73 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char result_data_msg_cnt_ref_3[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 5 + }, + { + "msgCounterReference": 3 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_5.inc b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_5.inc new file mode 100644 index 0000000..0364212 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_5.inc @@ -0,0 +1,73 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char result_data_msg_cnt_ref_5[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 7 + }, + { + "msgCounterReference": 5 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_8.inc b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_8.inc new file mode 100644 index 0000000..de697be --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/result_data_msg_cnt_ref_8.inc @@ -0,0 +1,73 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char result_data_msg_cnt_ref_8[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 10 + }, + { + "msgCounterReference": 8 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/use_case_reply.inc b/tests/src/use_case/actor/ma/mgcp/receive/use_case_reply.inc new file mode 100644 index 0000000..52b6abb --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/use_case_reply.inc @@ -0,0 +1,119 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char use_case_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 6 + }, + { + "msgCounterReference": 4 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [ + { + "useCaseInformation": [ + [ + { + "address": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + } + ] + }, + { + "actor": "GridConnectionPoint" + }, + { + "useCaseSupport": [ + [ + { + "useCaseName": "monitoringOfGridConnectionPoint" + }, + { + "useCaseVersion": "1.0.0" + }, + { + "useCaseAvailable": true + }, + { + "scenarioSupport": [ + 1, + 2, + 3, + 4, + 5, + 7 + ] + }, + { + "useCaseDocumentSubRevision": "release" + } + ] + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/receive/use_case_request.inc b/tests/src/use_case/actor/ma/mgcp/receive/use_case_request.inc new file mode 100644 index 0000000..a61d4a8 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/receive/use_case_request.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace receive { + +static constexpr char use_case_request[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 4 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/device_configuration_description_read.inc b/tests/src/use_case/actor/ma/mgcp/send/device_configuration_description_read.inc new file mode 100644 index 0000000..e2722e6 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/device_configuration_description_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char device_configuration_description_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 14 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/device_configuration_key_value_read.inc b/tests/src/use_case/actor/ma/mgcp/send/device_configuration_key_value_read.inc new file mode 100644 index 0000000..aee3df1 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/device_configuration_key_value_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char device_configuration_key_value_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "msgCounter": 16 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "deviceConfigurationKeyValueListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/device_configuration_subscription_call.inc b/tests/src/use_case/actor/ma/mgcp/send/device_configuration_subscription_call.inc new file mode 100644 index 0000000..60de8eb --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/device_configuration_subscription_call.inc @@ -0,0 +1,107 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char device_configuration_subscription_call[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 13 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "serverFeatureType": "DeviceConfiguration" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/discovery_read.inc b/tests/src/use_case/actor/ma/mgcp/send/discovery_read.inc new file mode 100644 index 0000000..d3b1ee0 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/discovery_read.inc @@ -0,0 +1,63 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char discovery_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 1 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/discovery_reply.inc b/tests/src/use_case/actor/ma/mgcp/send/discovery_reply.inc new file mode 100644 index 0000000..b6d4c39 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/discovery_reply.inc @@ -0,0 +1,382 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char discovery_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 2 + }, + { + "msgCounterReference": 1 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementDetailedDiscoveryData": [ + { + "specificationVersionList": [ + { + "specificationVersion": [ + "1.3.0" + ] + } + ] + }, + { + "deviceInformation": [ + { + "description": [ + { + "deviceAddress": [ + { + "device": "d:_n:HEMS_123456789" + } + ] + }, + { + "networkFeatureSet": "smart" + } + ] + } + ] + }, + { + "entityInformation": [ + [ + { + "description": [ + { + "entityAddress": [ + { + "entity": [ + 0 + ] + } + ] + }, + { + "entityType": "DeviceInformation" + } + ] + } + ], + [ + { + "description": [ + { + "entityAddress": [ + { + "entity": [ + 1 + ] + } + ] + }, + { + "entityType": "CEM" + } + ] + } + ] + ] + }, + { + "featureInformation": [ + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "featureType": "NodeManagement" + }, + { + "role": "special" + }, + { + "supportedFunction": [ + [ + { + "function": "nodeManagementBindingData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementBindingDeleteCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementBindingRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementDestinationListData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementDetailedDiscoveryData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementSubscriptionData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ], + [ + { + "function": "nodeManagementSubscriptionDeleteCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementSubscriptionRequestCall" + }, + { + "possibleOperations": [] + } + ], + [ + { + "function": "nodeManagementUseCaseData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 0 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "DeviceClassification" + }, + { + "role": "server" + }, + { + "supportedFunction": [ + [ + { + "function": "deviceClassificationManufacturerData" + }, + { + "possibleOperations": [ + { + "read": [] + } + ] + } + ] + ] + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "featureType": "ElectricalConnection" + }, + { + "role": "client" + }, + { + "description": "ElectricalConnection client" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "featureType": "Measurement" + }, + { + "role": "client" + }, + { + "description": "Measurement client" + } + ] + } + ], + [ + { + "description": [ + { + "featureAddress": [ + { + "entity": [ + 1 + ] + }, + { + "feature": 3 + } + ] + }, + { + "featureType": "DeviceConfiguration" + }, + { + "role": "client" + }, + { + "description": "DeviceConfiguration client" + } + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_description_read.inc b/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_description_read.inc new file mode 100644 index 0000000..d8a1c49 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_description_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char electrical_connection_description_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "msgCounter": 8 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_parameter_description_read.inc b/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_parameter_description_read.inc new file mode 100644 index 0000000..7893e41 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_parameter_description_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char electrical_connection_parameter_description_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "msgCounter": 9 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "electricalConnectionParameterDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_subscription_call.inc b/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_subscription_call.inc new file mode 100644 index 0000000..b6945c4 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/electrical_connection_subscription_call.inc @@ -0,0 +1,107 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char electrical_connection_subscription_call[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 7 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 1 + } + ] + }, + { + "serverFeatureType": "ElectricalConnection" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/measurement_constraints_read.inc b/tests/src/use_case/actor/ma/mgcp/send/measurement_constraints_read.inc new file mode 100644 index 0000000..22b2d55 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/measurement_constraints_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char measurement_constraints_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 12 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementConstraintsListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/measurement_description_read.inc b/tests/src/use_case/actor/ma/mgcp/send/measurement_description_read.inc new file mode 100644 index 0000000..356f3d1 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/measurement_description_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char measurement_description_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 11 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementDescriptionListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/measurement_read.inc b/tests/src/use_case/actor/ma/mgcp/send/measurement_read.inc new file mode 100644 index 0000000..39b3a3b --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/measurement_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char measurement_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 15 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "measurementListData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/measurement_subscription_call.inc b/tests/src/use_case/actor/ma/mgcp/send/measurement_subscription_call.inc new file mode 100644 index 0000000..8231573 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/measurement_subscription_call.inc @@ -0,0 +1,107 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char measurement_subscription_call[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 10 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "serverFeatureType": "Measurement" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/node_management_subscription_call.inc b/tests/src/use_case/actor/ma/mgcp/send/node_management_subscription_call.inc new file mode 100644 index 0000000..d3632ce --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/node_management_subscription_call.inc @@ -0,0 +1,107 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char node_management_subscription_call[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 3 + }, + { + "cmdClassifier": "call" + }, + { + "ackRequest": true + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementSubscriptionRequestCall": [ + { + "subscriptionRequest": [ + { + "clientAddress": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverAddress": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "serverFeatureType": "NodeManagement" + } + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/result_data_msg_cnt_ref_3.inc b/tests/src/use_case/actor/ma/mgcp/send/result_data_msg_cnt_ref_3.inc new file mode 100644 index 0000000..2265958 --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/result_data_msg_cnt_ref_3.inc @@ -0,0 +1,73 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char result_data_msg_cnt_ref_3[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 5 + }, + { + "msgCounterReference": 3 + }, + { + "cmdClassifier": "result" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "resultData": [ + { + "errorNumber": 0 + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/use_case_data_read.inc b/tests/src/use_case/actor/ma/mgcp/send/use_case_data_read.inc new file mode 100644 index 0000000..b9876da --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/use_case_data_read.inc @@ -0,0 +1,66 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char use_case_data_read[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 4 + }, + { + "cmdClassifier": "read" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mgcp/send/use_case_data_reply.inc b/tests/src/use_case/actor/ma/mgcp/send/use_case_data_reply.inc new file mode 100644 index 0000000..f41acfc --- /dev/null +++ b/tests/src/use_case/actor/ma/mgcp/send/use_case_data_reply.inc @@ -0,0 +1,120 @@ +namespace ma_mgcp_test { +namespace send { + +static constexpr char use_case_data_reply[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:GridConnectionPoint_123456789" + }, + { + "entity": [ + 0 + ] + }, + { + "feature": 0 + } + ] + }, + { + "msgCounter": 6 + }, + { + "msgCounterReference": 4 + }, + { + "cmdClassifier": "reply" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "nodeManagementUseCaseData": [ + { + "useCaseInformation": [ + [ + { + "address": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + } + ] + }, + { + "actor": "MonitoringAppliance" + }, + { + "useCaseSupport": [ + [ + { + "useCaseName": "monitoringOfGridConnectionPoint" + }, + { + "useCaseVersion": "1.0.0" + }, + { + "useCaseAvailable": true + }, + { + "scenarioSupport": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + { + "useCaseDocumentSubRevision": "release" + } + ] + ] + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace ma_mgcp_test diff --git a/tests/src/use_case/actor/ma/mpc/CMakeLists.txt b/tests/src/use_case/actor/ma/mpc/CMakeLists.txt index 39ff07b..c577abe 100644 --- a/tests/src/use_case/actor/ma/mpc/CMakeLists.txt +++ b/tests/src/use_case/actor/ma/mpc/CMakeLists.txt @@ -79,6 +79,8 @@ target_sources( ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_subscription.c ${MAIN_PROJ_SOURCES_PATH}/spine/node_management/node_management_usecase.c ${MAIN_PROJ_SOURCES_PATH}/spine/subscription/subscription_manager.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/ma_events.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/ma_measurement_base.c ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/mpc/ma_mpc_events.c ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/mpc/ma_mpc_measurement.c ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/ma/mpc/ma_mpc_public.c diff --git a/tests/src/use_case/actor/ma/mpc/ma_mpc_test.cpp b/tests/src/use_case/actor/ma/mpc/ma_mpc_test.cpp index f36bc1b..cacd324 100644 --- a/tests/src/use_case/actor/ma/mpc/ma_mpc_test.cpp +++ b/tests/src/use_case/actor/ma/mpc/ma_mpc_test.cpp @@ -45,8 +45,11 @@ #include "tests/src/use_case/actor/ma/mpc/receive/measurement_description_reply.inc" #include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_current.inc" #include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_energy.inc" +#include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_energy_consumed_only.inc" #include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_frequency.inc" +#include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_mixed.inc" #include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_power.inc" +#include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_power_total_only.inc" #include "tests/src/use_case/actor/ma/mpc/receive/measurement_notify_voltage.inc" #include "tests/src/use_case/actor/ma/mpc/receive/measurement_reply.inc" #include "tests/src/use_case/actor/ma/mpc/receive/node_management_subscription_request.inc" @@ -228,27 +231,66 @@ TEST_F(MaMpcTestFixture, MaMpcTest) { ExpectMeasurementsReceive(ma_mpc_listener_mock_->gmock, expected_frequency); HandleMessage(receive::measurement_notify_frequency); - // 19. Get all of the measurements received via GetMeasurementData() and check the values - ScaledValue value; - static constexpr uint32_t remote_entity_id = 1; + // 19. Receive a partial notify with power total only — tests single-measurement partial and + // overwrites the kMpcPowerTotal value initially received in the measurement reply (step 13) + EXPECT_CALL(*ma_mpc_listener_mock_->gmock, OnMeasurementReceive(_, kMpcPowerTotal, ScaledValueEq(4000, 0), _)) + .WillOnce(Return()); + HandleMessage(receive::measurement_notify_power_total_only); + + // 20. Receive a cross-scenario notify — tests that one notify can carry measurements from + // different scenarios; also overwrites kMpcPowerTotal, kMpcEnergyProduced, + // kMpcVoltagePhaseAb and kMpcFrequency set in earlier notifies + const std::map expected_mixed{ + { kMpcPowerTotal, {.value = 5500, .scale = 0}}, + {kMpcEnergyProduced, {.value = 999999, .scale = -3}}, + {kMpcVoltagePhaseAb, {.value = 39600, .scale = -2}}, + { kMpcFrequency, {.value = 4999, .scale = -2}}, + }; - static constexpr const uint32_t* const remote_entity_ids[] = {&remote_entity_id}; + ExpectMeasurementsReceive(ma_mpc_listener_mock_->gmock, expected_mixed); + HandleMessage(receive::measurement_notify_mixed); + + // 21. Receive a partial notify with energy consumed only — tests single-measurement partial + // from a different scenario and overwrites kMpcEnergyConsumed set in step 15 + EXPECT_CALL(*ma_mpc_listener_mock_->gmock, OnMeasurementReceive(_, kMpcEnergyConsumed, ScaledValueEq(600000, 0), _)) + .WillOnce(Return()); + HandleMessage(receive::measurement_notify_energy_consumed_only); + + // 22. Get all 16 measurements via GetMeasurementData() and verify the final stored values + // after all notifies (overwritten values reflect the latest notify that touched each ID) + ScaledValue value{0}; + static constexpr uint32_t remote_entity_id{1}; + + static constexpr const uint32_t* const remote_entity_ids[]{&remote_entity_id}; const EntityAddressType remote_entity_addr = {"d:_n:HeatPump_123456789", remote_entity_ids, ARRAY_SIZE(remote_entity_ids)}; - std::map expected_data = expected_power; - expected_data.insert(expected_energy.begin(), expected_energy.end()); - expected_data.insert(expected_current.begin(), expected_current.end()); - expected_data.insert(expected_voltage.begin(), expected_voltage.end()); - expected_data.insert(expected_frequency.begin(), expected_frequency.end()); + const std::map expected_final{ + { kMpcPowerTotal, {.value = 5500, .scale = 0}}, // step 20 overwrites step 19 + { kMpcPowerPhaseA, {.value = 1000, .scale = 0}}, // step 14 + { kMpcPowerPhaseB, {.value = 1100, .scale = 0}}, // step 14 + { kMpcPowerPhaseC, {.value = 1200, .scale = 0}}, // step 14 + {kMpcEnergyConsumed, {.value = 600000, .scale = 0}}, // step 21 overwrites step 15 + {kMpcEnergyProduced, {.value = 999999, .scale = -3}}, // step 20 overwrites step 15 + { kMpcCurrentPhaseA, {.value = 33, .scale = -2}}, // step 16 + { kMpcCurrentPhaseB, {.value = 51, .scale = -2}}, // step 16 + { kMpcCurrentPhaseC, {.value = 13, .scale = -3}}, // step 16 + { kMpcVoltagePhaseA, {.value = 110, .scale = 0}}, // step 17 + { kMpcVoltagePhaseB, {.value = 1205, .scale = -1}}, // step 17 + { kMpcVoltagePhaseC, {.value = 130, .scale = 0}}, // step 17 + {kMpcVoltagePhaseAb, {.value = 39600, .scale = -2}}, // step 20 overwrites step 17 + {kMpcVoltagePhaseBc, {.value = 150, .scale = 0}}, // step 17 + {kMpcVoltagePhaseAc, {.value = 16, .scale = 1}}, // step 17 + { kMpcFrequency, {.value = 4999, .scale = -2}}, // step 20 overwrites step 18 + }; - for (const auto& [name_id, scaled_value] : expected_data) { + for (const auto& [name_id, scaled_value] : expected_final) { EXPECT_EQ(MaMpcGetMeasurementData(use_case_.get(), name_id, &remote_entity_addr, &value), kEebusErrorOk); EXPECT_THAT(&value, ScaledValueEq(scaled_value.value, scaled_value.scale)); } - // 20. Expect the remote entity disconnect event while tearing down the use case + // 23. Expect the remote entity disconnect event while tearing down the use case EXPECT_CALL(*ma_mpc_listener_mock_->gmock, OnRemoteEntityDisconnect(_, _)); } diff --git a/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_energy_consumed_only.inc b/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_energy_consumed_only.inc new file mode 100644 index 0000000..3e51b49 --- /dev/null +++ b/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_energy_consumed_only.inc @@ -0,0 +1,108 @@ +namespace ma_mpc_test { +namespace receive { + +static constexpr char measurement_notify_energy_consumed_only[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 21 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 4 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 600000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mpc_test diff --git a/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_mixed.inc b/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_mixed.inc new file mode 100644 index 0000000..bfb6cf4 --- /dev/null +++ b/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_mixed.inc @@ -0,0 +1,174 @@ +namespace ma_mpc_test { +namespace receive { + +// Cross-scenario partial notify: power total (0), energy produced (5), +// voltage phase AB (12), frequency (15) — tests that a single notify can +// carry measurements from multiple scenarios and overwrite stored values. +static constexpr char measurement_notify_mixed[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 20 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 5500 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 5 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 999999 + }, + { + "scale": -3 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 12 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 39600 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 15 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 4999 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mpc_test diff --git a/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_power_total_only.inc b/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_power_total_only.inc new file mode 100644 index 0000000..b950c63 --- /dev/null +++ b/tests/src/use_case/actor/ma/mpc/receive/measurement_notify_power_total_only.inc @@ -0,0 +1,108 @@ +namespace ma_mpc_test { +namespace receive { + +static constexpr char measurement_notify_power_total_only[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "msgCounter": 19 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 4000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace receive +} // namespace ma_mpc_test diff --git a/tests/src/use_case/actor/mu/mpc/CMakeLists.txt b/tests/src/use_case/actor/mu/mpc/CMakeLists.txt index 32f6a3c..e7ffc09 100644 --- a/tests/src/use_case/actor/mu/mpc/CMakeLists.txt +++ b/tests/src/use_case/actor/mu/mpc/CMakeLists.txt @@ -88,6 +88,10 @@ target_sources( ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/device_configuration/device_configuration_server.c ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/electrical_connection/electrical_connection_common.c ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/electrical_connection/electrical_connection_server.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_measurement_base.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_monitor_base.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_monitor_container.c + ${MAIN_PROJ_SOURCES_PATH}/use_case/actor/common/eebus_monitor_features.c ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/measurement/measurement_common.c ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/measurement/measurement_server.c ${MAIN_PROJ_SOURCES_PATH}/use_case/specialization/feature_info_client.c diff --git a/tests/src/use_case/actor/mu/mpc/mu_mpc_test.cpp b/tests/src/use_case/actor/mu/mpc/mu_mpc_test.cpp index f3dfd65..80fcc14 100644 --- a/tests/src/use_case/actor/mu/mpc/mu_mpc_test.cpp +++ b/tests/src/use_case/actor/mu/mpc/mu_mpc_test.cpp @@ -53,6 +53,11 @@ #include "tests/src/use_case/actor/mu/mpc/send/electrical_connection_parameter_description_reply.inc" #include "tests/src/use_case/actor/mu/mpc/send/measurement_constraints_reply.inc" #include "tests/src/use_case/actor/mu/mpc/send/measurement_description_reply.inc" +#include "tests/src/use_case/actor/mu/mpc/send/measurement_notify_current.inc" +#include "tests/src/use_case/actor/mu/mpc/send/measurement_notify_energy.inc" +#include "tests/src/use_case/actor/mu/mpc/send/measurement_notify_frequency.inc" +#include "tests/src/use_case/actor/mu/mpc/send/measurement_notify_power.inc" +#include "tests/src/use_case/actor/mu/mpc/send/measurement_notify_voltage.inc" #include "tests/src/use_case/actor/mu/mpc/send/node_management_subscription_call.inc" #include "tests/src/use_case/actor/mu/mpc/send/result_data_msg_cnt_ref_3.inc" #include "tests/src/use_case/actor/mu/mpc/send/result_data_msg_cnt_ref_5.inc" @@ -80,7 +85,7 @@ class MuMpcTestFixture : public UseCaseTestFixture { kHeartbeatTimeout ); - static constexpr MuMpcMeasurementConfig measurement_default_cfg = { + static constexpr MuMpcMeasurementConfig measurement_default_cfg{ .value_source = kMeasurementValueSourceTypeMeasuredValue, }; @@ -89,11 +94,22 @@ class MuMpcTestFixture : public UseCaseTestFixture { .energy_consumption_cfg = &measurement_default_cfg, }; - static constexpr MuMpcMonitorCurrentConfig current_cfg = { + static constexpr MuMpcMonitorCurrentConfig current_cfg{ .current_phase_a_cfg = &measurement_default_cfg, + .current_phase_b_cfg = &measurement_default_cfg, + .current_phase_c_cfg = &measurement_default_cfg, }; - static constexpr MuMpcMonitorFrequencyConfig frequency_cfg = { + static constexpr MuMpcMonitorVoltageConfig voltage_cfg{ + .voltage_phase_a_cfg = &measurement_default_cfg, + .voltage_phase_b_cfg = &measurement_default_cfg, + .voltage_phase_c_cfg = &measurement_default_cfg, + .voltage_phase_ab_cfg = &measurement_default_cfg, + .voltage_phase_bc_cfg = &measurement_default_cfg, + .voltage_phase_ac_cfg = &measurement_default_cfg, + }; + + static constexpr MuMpcMonitorFrequencyConfig frequency_cfg{ .frequency_cfg = measurement_default_cfg, }; @@ -101,43 +117,79 @@ class MuMpcTestFixture : public UseCaseTestFixture { .power_cfg = { .power_total_cfg = measurement_default_cfg, .power_phase_a_cfg = &measurement_default_cfg, + .power_phase_b_cfg = &measurement_default_cfg, + .power_phase_c_cfg = &measurement_default_cfg, }, .energy_cfg = &energy_cfg, .current_cfg = ¤t_cfg, + .voltage_cfg = &voltage_cfg, .frequency_cfg = &frequency_cfg }; use_case_.reset(MuMpcUseCaseCreate(entity, 0, &cfg)); - static constexpr ScaledValue power_total = {1000, 0}; + static constexpr ScaledValue power_total{1000, 0}; MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerTotal, &power_total, NULL, NULL); - static constexpr ScaledValue current_phase_a = {33, -1}; - static constexpr EebusDateTime timestamp = { - .date = {.year = 2025, .month = 7, .day = 1}, - .time = { .hour = 12, .min = 0, .sec = 0} + static constexpr ScaledValue power_phase_a{500, 0}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerPhaseA, &power_phase_a, NULL, NULL); + + static constexpr ScaledValue power_phase_b{1200, 0}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerPhaseB, &power_phase_b, NULL, NULL); + + static constexpr ScaledValue power_phase_c{800, 0}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerPhaseC, &power_phase_c, NULL, NULL); + + static constexpr ScaledValue current_phase_a{33, -1}; + static constexpr EebusDateTime timestamp{ + .date = {.year = 2025, .month = 7, .day = 1}, + .time = { .hour = 12, .min = 0, .sec = 0} }; MuMpcSetMeasurementDataCache(use_case_.get(), kMpcCurrentPhaseA, ¤t_phase_a, ×tamp, NULL); - static constexpr ScaledValue energy_consumed = {5000, 0}; - static constexpr EebusDateTime start_time = { - .date = {.year = 2025, .month = 9, .day = 1}, - .time = { .hour = 0, .min = 0, .sec = 0} + static constexpr ScaledValue current_phase_b{20, -1}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcCurrentPhaseB, ¤t_phase_b, NULL, NULL); + + static constexpr ScaledValue current_phase_c{18, -1}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcCurrentPhaseC, ¤t_phase_c, NULL, NULL); + + static constexpr ScaledValue voltage_phase_a{23000, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseA, &voltage_phase_a, NULL, NULL); + + static constexpr ScaledValue voltage_phase_b{23100, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseB, &voltage_phase_b, NULL, NULL); + + static constexpr ScaledValue voltage_phase_c{22900, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseC, &voltage_phase_c, NULL, NULL); + + static constexpr ScaledValue voltage_phase_ab{40000, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseAb, &voltage_phase_ab, NULL, NULL); + + static constexpr ScaledValue voltage_phase_bc{40100, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseBc, &voltage_phase_bc, NULL, NULL); + + static constexpr ScaledValue voltage_phase_ac{39900, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseAc, &voltage_phase_ac, NULL, NULL); + + static constexpr ScaledValue energy_consumed{5000, 0}; + static constexpr EebusDateTime start_time{ + .date = {.year = 2025, .month = 9, .day = 1}, + .time = { .hour = 0, .min = 0, .sec = 0} }; - static constexpr EebusDateTime end_time = { + static constexpr EebusDateTime end_time{ .date = {.year = 2025, .month = 10, .day = 2}, .time = { .hour = 0, .min = 0, .sec = 0} }; MuMpcSetEnergyConsumedCache(use_case_.get(), &energy_consumed, NULL, NULL, &start_time, &end_time); - static constexpr ScaledValue energy_produced = {2000, 0}; + static constexpr ScaledValue energy_produced{2000, 0}; MuMpcSetEnergyProducedCache(use_case_.get(), &energy_produced, NULL, NULL, &start_time, &end_time); - static constexpr ScaledValue frequency = {50, 0}; + static constexpr ScaledValue frequency{50, 0}; MuMpcSetMeasurementDataCache(use_case_.get(), kMpcFrequency, &frequency, NULL, NULL); MuMpcUpdate(use_case_.get()); @@ -166,6 +218,38 @@ TEST_F(MuMpcTestFixture, MuMpcTest) { EXPECT_EQ(value.value, 33); EXPECT_EQ(value.scale, -1); + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcCurrentPhaseB, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 20); + EXPECT_EQ(value.scale, -1); + + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcCurrentPhaseC, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 18); + EXPECT_EQ(value.scale, -1); + + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcVoltagePhaseA, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 23000); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcVoltagePhaseB, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 23100); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcVoltagePhaseC, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 22900); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcVoltagePhaseAb, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 40000); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcVoltagePhaseBc, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 40100); + EXPECT_EQ(value.scale, -2); + + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcVoltagePhaseAc, &value), kEebusErrorOk); + EXPECT_EQ(value.value, 39900); + EXPECT_EQ(value.scale, -2); + EXPECT_EQ(MuMpcGetMeasurementData(use_case_.get(), kMpcFrequency, &value), kEebusErrorOk); EXPECT_EQ(value.value, 50); EXPECT_EQ(value.scale, 0); @@ -216,6 +300,63 @@ TEST_F(MuMpcTestFixture, MuMpcTest) { // 13. Receive the Use Case reply HandleMessage(receive::use_case_reply); + + // 14. Update scenario 1 (power) and expect the notify + static constexpr ScaledValue new_power_total{2000, 0}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerTotal, &new_power_total, NULL, NULL); + static constexpr ScaledValue new_power_a{700, 0}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerPhaseA, &new_power_a, NULL, NULL); + static constexpr ScaledValue new_power_b{750, 0}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerPhaseB, &new_power_b, NULL, NULL); + static constexpr ScaledValue new_power_c{550, 0}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcPowerPhaseC, &new_power_c, NULL, NULL); + + ExpectSendMessage(send::measurement_notify_power); + MuMpcUpdate(use_case_.get()); + + // 15. Update scenario 2 (energy) and expect the notify + static constexpr ScaledValue new_energy_consumed{6000, 0}; + MuMpcSetEnergyConsumedCache(use_case_.get(), &new_energy_consumed, NULL, NULL, NULL, NULL); + static constexpr ScaledValue new_energy_produced{2500, 0}; + MuMpcSetEnergyProducedCache(use_case_.get(), &new_energy_produced, NULL, NULL, NULL, NULL); + + ExpectSendMessage(send::measurement_notify_energy); + MuMpcUpdate(use_case_.get()); + + // 16. Update scenario 3 (current) and expect the notify + static constexpr ScaledValue new_current_a{150, -1}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcCurrentPhaseA, &new_current_a, NULL, NULL); + static constexpr ScaledValue new_current_b{160, -1}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcCurrentPhaseB, &new_current_b, NULL, NULL); + static constexpr ScaledValue new_current_c{170, -1}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcCurrentPhaseC, &new_current_c, NULL, NULL); + + ExpectSendMessage(send::measurement_notify_current); + MuMpcUpdate(use_case_.get()); + + // 17. Update scenario 4 (voltage) and expect the notify + static constexpr ScaledValue new_voltage_a{23500, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseA, &new_voltage_a, NULL, NULL); + static constexpr ScaledValue new_voltage_b{23600, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseB, &new_voltage_b, NULL, NULL); + static constexpr ScaledValue new_voltage_c{23400, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseC, &new_voltage_c, NULL, NULL); + static constexpr ScaledValue new_voltage_ab{40500, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseAb, &new_voltage_ab, NULL, NULL); + static constexpr ScaledValue new_voltage_bc{40600, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseBc, &new_voltage_bc, NULL, NULL); + static constexpr ScaledValue new_voltage_ac{40400, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcVoltagePhaseAc, &new_voltage_ac, NULL, NULL); + + ExpectSendMessage(send::measurement_notify_voltage); + MuMpcUpdate(use_case_.get()); + + // 18. Update scenario 5 (frequency) and expect the notify + static constexpr ScaledValue new_frequency{6000, -2}; + MuMpcSetMeasurementDataCache(use_case_.get(), kMpcFrequency, &new_frequency, NULL, NULL); + + ExpectSendMessage(send::measurement_notify_frequency); + MuMpcUpdate(use_case_.get()); } } // namespace mu_mpc_test diff --git a/tests/src/use_case/actor/mu/mpc/send/electrical_connection_parameter_description_reply.inc b/tests/src/use_case/actor/mu/mpc/send/electrical_connection_parameter_description_reply.inc index 6ca67db..d7ee706 100644 --- a/tests/src/use_case/actor/mu/mpc/send/electrical_connection_parameter_description_reply.inc +++ b/tests/src/use_case/actor/mu/mpc/send/electrical_connection_parameter_description_reply.inc @@ -72,7 +72,7 @@ static constexpr char electrical_connection_parameter_description_reply[] = R"({ "voltageType": "ac" }, { - "acMeasuredPhases": "a" + "acMeasuredPhases": "abc" }, { "acMeasuredInReferenceTo": "neutral" @@ -123,8 +123,17 @@ static constexpr char electrical_connection_parameter_description_reply[] = R"({ { "voltageType": "ac" }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, { "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" } ], [ @@ -140,8 +149,17 @@ static constexpr char electrical_connection_parameter_description_reply[] = R"({ { "voltageType": "ac" }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, { "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" } ], [ @@ -157,6 +175,40 @@ static constexpr char electrical_connection_parameter_description_reply[] = R"({ { "voltageType": "ac" }, + { + "acMeasurementType": "real" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 5 + }, + { + "measurementId": 5 + }, + { + "voltageType": "ac" + }, + { + "acMeasurementType": "real" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 6 + }, + { + "measurementId": 6 + }, + { + "voltageType": "ac" + }, { "acMeasuredPhases": "a" }, @@ -172,10 +224,212 @@ static constexpr char electrical_connection_parameter_description_reply[] = R"({ "electricalConnectionId": 0 }, { - "parameterId": 5 + "parameterId": 7 }, { - "measurementId": 5 + "measurementId": 7 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 8 + }, + { + "measurementId": 8 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasurementType": "real" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 9 + }, + { + "measurementId": 9 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 10 + }, + { + "measurementId": 10 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 11 + }, + { + "measurementId": 11 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasuredInReferenceTo": "neutral" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 12 + }, + { + "measurementId": 12 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "a" + }, + { + "acMeasuredInReferenceTo": "b" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 13 + }, + { + "measurementId": 13 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "b" + }, + { + "acMeasuredInReferenceTo": "c" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 14 + }, + { + "measurementId": 14 + }, + { + "voltageType": "ac" + }, + { + "acMeasuredPhases": "c" + }, + { + "acMeasuredInReferenceTo": "a" + }, + { + "acMeasurementType": "apparent" + }, + { + "acMeasurementVariant": "rms" + } + ], + [ + { + "electricalConnectionId": 0 + }, + { + "parameterId": 15 + }, + { + "measurementId": 15 }, { "voltageType": "ac" diff --git a/tests/src/use_case/actor/mu/mpc/send/measurement_description_reply.inc b/tests/src/use_case/actor/mu/mpc/send/measurement_description_reply.inc index 1bcdb74..040b54f 100644 --- a/tests/src/use_case/actor/mu/mpc/send/measurement_description_reply.inc +++ b/tests/src/use_case/actor/mu/mpc/send/measurement_description_reply.inc @@ -96,6 +96,40 @@ static constexpr char measurement_description_reply[] = R"({ { "measurementId": 2 }, + { + "measurementType": "power" + }, + { + "commodityType": "electricity" + }, + { + "unit": "W" + }, + { + "scopeType": "acPower" + } + ], + [ + { + "measurementId": 3 + }, + { + "measurementType": "power" + }, + { + "commodityType": "electricity" + }, + { + "unit": "W" + }, + { + "scopeType": "acPower" + } + ], + [ + { + "measurementId": 4 + }, { "measurementType": "energy" }, @@ -111,7 +145,7 @@ static constexpr char measurement_description_reply[] = R"({ ], [ { - "measurementId": 3 + "measurementId": 5 }, { "measurementType": "energy" @@ -128,7 +162,7 @@ static constexpr char measurement_description_reply[] = R"({ ], [ { - "measurementId": 4 + "measurementId": 6 }, { "measurementType": "current" @@ -145,7 +179,143 @@ static constexpr char measurement_description_reply[] = R"({ ], [ { - "measurementId": 5 + "measurementId": 7 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 8 + }, + { + "measurementType": "current" + }, + { + "commodityType": "electricity" + }, + { + "unit": "A" + }, + { + "scopeType": "acCurrent" + } + ], + [ + { + "measurementId": 9 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 10 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 11 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 12 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 13 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 14 + }, + { + "measurementType": "voltage" + }, + { + "commodityType": "electricity" + }, + { + "unit": "V" + }, + { + "scopeType": "acVoltage" + } + ], + [ + { + "measurementId": 15 }, { "measurementType": "frequency" diff --git a/tests/src/use_case/actor/mu/mpc/send/measurement_notify_current.inc b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_current.inc new file mode 100644 index 0000000..67df962 --- /dev/null +++ b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_current.inc @@ -0,0 +1,150 @@ +namespace mu_mpc_test { +namespace send { + +static constexpr char measurement_notify_current[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 15 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 6 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 150 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 7 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 160 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 8 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 170 + }, + { + "scale": -1 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace mu_mpc_test diff --git a/tests/src/use_case/actor/mu/mpc/send/measurement_notify_energy.inc b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_energy.inc new file mode 100644 index 0000000..48cac38 --- /dev/null +++ b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_energy.inc @@ -0,0 +1,129 @@ +namespace mu_mpc_test { +namespace send { + +static constexpr char measurement_notify_energy[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 14 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 4 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 6000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 5 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 2500 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace mu_mpc_test diff --git a/tests/src/use_case/actor/mu/mpc/send/measurement_notify_frequency.inc b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_frequency.inc new file mode 100644 index 0000000..45c2353 --- /dev/null +++ b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_frequency.inc @@ -0,0 +1,108 @@ +namespace mu_mpc_test { +namespace send { + +static constexpr char measurement_notify_frequency[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 17 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 15 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 6000 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace mu_mpc_test diff --git a/tests/src/use_case/actor/mu/mpc/send/measurement_notify_power.inc b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_power.inc new file mode 100644 index 0000000..972c8d0 --- /dev/null +++ b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_power.inc @@ -0,0 +1,171 @@ +namespace mu_mpc_test { +namespace send { + +static constexpr char measurement_notify_power[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 13 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 0 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 2000 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 1 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 700 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 2 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 750 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 3 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 550 + }, + { + "scale": 0 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace mu_mpc_test diff --git a/tests/src/use_case/actor/mu/mpc/send/measurement_notify_voltage.inc b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_voltage.inc new file mode 100644 index 0000000..a2e6dff --- /dev/null +++ b/tests/src/use_case/actor/mu/mpc/send/measurement_notify_voltage.inc @@ -0,0 +1,213 @@ +namespace mu_mpc_test { +namespace send { + +static constexpr char measurement_notify_voltage[] = R"({ + "datagram": [ + { + "header": [ + { + "specificationVersion": "1.3.0" + }, + { + "addressSource": [ + { + "device": "d:_n:HeatPump_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 2 + } + ] + }, + { + "addressDestination": [ + { + "device": "d:_n:HEMS_123456789" + }, + { + "entity": [ + 1 + ] + }, + { + "feature": 6 + } + ] + }, + { + "msgCounter": 16 + }, + { + "cmdClassifier": "notify" + } + ] + }, + { + "payload": [ + { + "cmd": [ + [ + { + "function": "measurementListData" + }, + { + "filter": [ + [ + { + "cmdControl": [ + { + "partial": [] + } + ] + } + ] + ] + }, + { + "measurementListData": [ + { + "measurementData": [ + [ + { + "measurementId": 9 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 23500 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 10 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 23600 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 11 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 23400 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 12 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 40500 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 13 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 40600 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ], + [ + { + "measurementId": 14 + }, + { + "valueType": "value" + }, + { + "value": [ + { + "number": 40400 + }, + { + "scale": -2 + } + ] + }, + { + "valueSource": "measuredValue" + } + ] + ] + } + ] + } + ] + ] + } + ] + } + ] +})"; + +} // namespace send +} // namespace mu_mpc_test diff --git a/tests/src/use_case/actor/mu/mpc/send/use_case_data_reply.inc b/tests/src/use_case/actor/mu/mpc/send/use_case_data_reply.inc index 1e5a56d..939d020 100644 --- a/tests/src/use_case/actor/mu/mpc/send/use_case_data_reply.inc +++ b/tests/src/use_case/actor/mu/mpc/send/use_case_data_reply.inc @@ -91,6 +91,7 @@ static constexpr char use_case_data_reply[] = R"({ 1, 2, 3, + 4, 5 ] },