From eb25587ab24c9d2a93eed1cae9feeabed81573b0 Mon Sep 17 00:00:00 2001 From: "C. Allwardt" <3979063+craig8@users.noreply.github.com> Date: Tue, 6 Aug 2024 12:41:03 -0700 Subject: [PATCH] Use model for cim measurement output. --- ieee_2030_5/adapters/gridappsd_adapter.py | 21 +- ieee_2030_5/models/output/__init__.py | 28 + ieee_2030_5/models/output/output_models.py | 2076 ++++++++++++++++++++ tests/put_soc.py | 17 + 4 files changed, 2131 insertions(+), 11 deletions(-) create mode 100644 ieee_2030_5/models/output/__init__.py create mode 100644 ieee_2030_5/models/output/output_models.py create mode 100644 tests/put_soc.py diff --git a/ieee_2030_5/adapters/gridappsd_adapter.py b/ieee_2030_5/adapters/gridappsd_adapter.py index efd74df..4a5b444 100644 --- a/ieee_2030_5/adapters/gridappsd_adapter.py +++ b/ieee_2030_5/adapters/gridappsd_adapter.py @@ -151,16 +151,12 @@ def get_house_and_utility_inverters(self) -> list[HouseLookup]: lfdi=self.tls.lfdi(ec['mRID']) except FileNotFoundError: lfdi = None - self._inverters.append(HouseLookup(mRID=ec['mRID'], - name=match_house.group(0), - lfdi=lfdi)) + self._inverters.append( + HouseLookup(mRID=ec['mRID'], name=match_house.group(0), lfdi=lfdi)) elif match_utility := re.match(re_utility, ec['name']): - pass - # TODO: Figure out about mrid for utilities. - # for phase in ec['phases']: - # self._inverters.append(HouseLookup(mRID=ec['mRID'], - # name=match_utility.group(0) + f"_{phase}", - # lfdi=self.tls.lfdi(ec['mRID']))) + self._inverters.append( + HouseLookup(mRID=ec['mRID'], name=match_utility.group(0), lfdi=lfdi)) + return self._inverters def get_power_electronic_connections(self) -> list[cim.PowerElectronicsConnection]: @@ -218,6 +214,8 @@ def get_device_configurations(self) -> list[DeviceConfiguration]: def get_message_for_bus(self) -> dict: import random + import ieee_2030_5.models.output as mo + msg = {} # TODO Get from list adapter for each house. @@ -247,10 +245,11 @@ def get_message_for_bus(self) -> dict: break if inverter: + msg[inverter.mRID] = asdict(mo.AnalogValue(mRID=inverter.mRID, timeStamp=status.readingTime, name=inverter.name, value=status.stateOfChargeStatus.value)) - msg[inverter.mRID] = dict(mrid=inverter.mRID, name=inverter.name) + # msg[inverter.mRID] = dict(mrid=inverter.mRID, name=inverter.name) - msg[inverter.mRID].update(asdict(status)) + # msg[inverter.mRID].update(asdict(status)) # for inv in self._inverters: diff --git a/ieee_2030_5/models/output/__init__.py b/ieee_2030_5/models/output/__init__.py new file mode 100644 index 0000000..ca9df36 --- /dev/null +++ b/ieee_2030_5/models/output/__init__.py @@ -0,0 +1,28 @@ +''' + Annotated CIMantic Graphs data profile init file for Profile + Generated by CIMTool http://cimtool.org +''' +from ieee_2030_5.models.output.output_models import ( + Identity, ACDCTerminal, Analog, AnalogControl, AnalogValue, BatteryState, BatteryUnit, Command, + ConductingEquipment, Discrete, DiscreteValue, GeneratingUnit, + IEEE1547AbnormalPerfomanceCategory, IEEE1547ControlSettings, IEEE1547Info, + IEEE1547IslandingCategory, IEEE1547NormalPerformanceCategory, IEEE1547Setting, + IEEE1547TripSettings, IOPoint, IdentifiedObject, Measurement, MeasurementValue, + MeasurementValueQuality, MeasurementValueSource, PhaseCode, PhotoVoltaicUnit, + PowerElectronicsConnection, PowerElectronicsConnectionPhase, PowerElectronicsUnit, + PowerSystemResource, RotatingMachine, SinglePhaseKind, SmartInverterMode, Terminal, + ReactivePower, Susceptance, Seconds, Voltage, ApparentPower, PerCent, RealEnergy, PU, + Frequency, ActivePower) + +__all__ = [ + Identity, ACDCTerminal, Analog, AnalogControl, AnalogValue, BatteryState, BatteryUnit, Command, + ConductingEquipment, Discrete, DiscreteValue, GeneratingUnit, + IEEE1547AbnormalPerfomanceCategory, IEEE1547ControlSettings, IEEE1547Info, + IEEE1547IslandingCategory, IEEE1547NormalPerformanceCategory, IEEE1547Setting, + IEEE1547TripSettings, IOPoint, IdentifiedObject, Measurement, MeasurementValue, + MeasurementValueQuality, MeasurementValueSource, PhaseCode, PhotoVoltaicUnit, + PowerElectronicsConnection, PowerElectronicsConnectionPhase, PowerElectronicsUnit, + PowerSystemResource, RotatingMachine, SinglePhaseKind, SmartInverterMode, Terminal, + ReactivePower, Susceptance, Seconds, Voltage, ApparentPower, PerCent, RealEnergy, PU, + Frequency, ActivePower +] diff --git a/ieee_2030_5/models/output/output_models.py b/ieee_2030_5/models/output/output_models.py new file mode 100644 index 0000000..e784f0c --- /dev/null +++ b/ieee_2030_5/models/output/output_models.py @@ -0,0 +1,2076 @@ +from __future__ import annotations +from dataclasses import dataclass, field, is_dataclass +from typing import Optional +from enum import Enum +from uuid import UUID, uuid4 +from random import Random +import json +import logging + +_log = logging.getLogger(__name__) +''' + Annotated CIMantic Graphs data profile for Profile + Generated by CIMTool http://cimtool.org +''' + + +@dataclass +class Identity(): + ''' + This is the new root class from CIM 18 to provide common identification + for all classes needing identification and naming attributes. + IdentifiedObject is now a child class of Identity. + mRID is superseded by Identity.identifier, which is typed to be a UUID. + ''' + identifier: Optional[str | UUID] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '1', + 'maxOccurs': '1' + }) + + # Override python __repr__ method with JSON-LD representation + # This is needed to avoid infinite loops in object previews + def __repr__(self) -> str: + return json.dumps({ + '@id': f'{str(self.identifier)}', + '@type': f'{self.__class__.__name__}' + }) + + # Override python string for printing with JSON representation + def __str__(self) -> str: + # Create JSON-LD dump with repr and all attributes + dump = dict(json.loads(self.__repr__()) | self.__dict__) + attribute_list = list(self.__dataclass_fields__.keys()) + for attribute in attribute_list: + # Delete attributes from print that are empty + if dump[attribute] is None or dump[attribute] == []: + del dump[attribute] + # If a dataclass, replace with custom repr + elif is_dataclass(dump[attribute]): + dump[attribute] = json.loads(dump[attribute].__repr__()) + # If a list, convert elements to string + elif type(dump[attribute]) == list: + values = [] + for value in dump[attribute]: + # If a dataclass, replace with custom repr + if is_dataclass(dump[attribute]): + values.append(json.loads(value.__repr__())) + else: + values.append(str(value)) + elif type[dump[attribute]] != str: + # Reformat all attributes as string for JSON + dump[attribute] = str(dump[attribute]) + # Fix python ' vs JSON " + dump = str(dump).replace('\'', '\"') + # Add 4 spaces indentation + dump = json.dumps(json.loads(dump), indent=4) + return dump + + # Create UUID from inconsistent mRIDs + def uuid(self, mRID: str = None, name: str = None) -> UUID: + invalid_mrid = False + # If mRID is specified, try creating from UUID from mRID + if mRID is not None: + try: + self.identifier = UUID(mRID.strip('_').lower(), version=4) + except: + invalid_mrid = True + name = mRID + _log.warning(f'mRID {mRID} not a valid UUID, generating new UUID') + # Otherwise, build UUID using unique name as a seed + elif invalid_mrid or name is not None: + seedStr = f"{self.__class__.__name__}:{name}" + randomGenerator = Random(seedStr) + self.identifier = UUID(int=randomGenerator.getrandbits(128), version=4) + else: + self.identifier = uuid4() + if 'mRID' in self.__dataclass_fields__: + if mRID is not None: + self.mRID = mRID + else: + self.mRID = str(self.identifier) + + +@dataclass(repr=False) +class AnalogControl(Identity): + ''' + An analog control used for supervisory control. + ''' + + +@dataclass(repr=False) +class Command(Identity): + ''' + A Command is a discrete control used for supervisory control. + ''' + + +@dataclass(repr=False) +class IEEE1547ControlSettings(Identity): + ''' + ''' + constantPowerFactor: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + constantReactivePower: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceIntentionalDelay: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMaxFrequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMaxVoltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMinFrequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMinVoltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + frequencyDroopResponseTime: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + openLoopResponseTimeP: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + overFrequencyDeadband: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + overFrequencyDroop: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + timeConstantOpenLoop: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + timeConstantReferenceVoltage: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + underFrequencyDeadband: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + underFrequencyDroop: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarQ1: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarQ2: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarQ3: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarQ4: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarV1: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarV2: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarV3: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltVarV4: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltWattP1: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltWattP2: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltWattV1: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + voltWattV2: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarP1: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarP2: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarP3: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarP4: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarQ1: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarQ2: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarQ3: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + wattVarQ4: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + PowerElectronicsConnections: list[str | PowerElectronicsConnection] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'PowerElectronicsConnection.IEEE1547ControlSettings' + }) + ''' + ''' + RotatingMachines: list[str | RotatingMachine] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'RotatingMachine.IEEE1547ControlSettings' + }) + ''' + ''' + + +@dataclass(repr=False) +class IEEE1547Info(Identity): + ''' + ''' + manufacturer: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + model: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + overExcitedPF: Optional[float] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + serialNumber: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsDynamicReactiveCurrent: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsIEC61850: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsIEEE1815: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsIEEE20305: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsIslanding: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsSunSpecModBusEthernet: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsSunSpecModBusRS485: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsVoltWatt: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + supportsWattVar: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + underExcitedPF: Optional[float] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + version: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + abnormalPerformanceCategory: Optional[str | IEEE1547AbnormalPerfomanceCategory] = field( + default=None, metadata={ + 'type': 'Enumeration', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + islandingCategory: Optional[str | IEEE1547IslandingCategory] = field(default=None, + metadata={ + 'type': 'enum', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + maximumU: Optional[float | Voltage] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + minimumU: Optional[float | Voltage] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + normalPerformanceCategory: Optional[str | IEEE1547NormalPerformanceCategory] = field( + default=None, metadata={ + 'type': 'enum', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedPatUnityPF: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedPcharge: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedPoverExcited: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedPunderExcited: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedQabsorbed: Optional[float | ReactivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedQinjected: Optional[float | ReactivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedS: Optional[float | ApparentPower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedScharge: Optional[float | ApparentPower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + ratedU: Optional[float | Voltage] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + susceptanceCeaseToEnergize: Optional[float | Susceptance] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + PowerElectronicsConnections: list[str | PowerElectronicsConnection] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'PowerElectronicsConnection.IEEE1547Info' + }) + ''' + ''' + RotatingMachines: list[str | RotatingMachine] = field(default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': + 'RotatingMachine.IEEE1547Info' + }) + ''' + ''' + + +@dataclass(repr=False) +class IEEE1547Setting(Identity): + ''' + ''' + constantPowerFactor: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + constantReactivePower: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceIntentionalDelay: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMaxFrequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMaxVoltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMinFrequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + enterServiceMinVoltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + frequencyDroopResponseTime: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + islandClearingTime: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + openLoopResponseTimeP: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + overFrequencyDeadband: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + overFrequencyDroop: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + timeConstantOpenLoop: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + timeConstantReferenceVoltage: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + underFrequencyDeadband: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + underFrequencyDroop: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + PowerElectronicsConnections: list[str | PowerElectronicsConnection] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'PowerElectronicsConnection.IEEE1547Setting' + }) + ''' + ''' + RotatingMachines: list[str | RotatingMachine] = field(default_factory=list, + metadata={ + 'type': + 'Association', + 'minOccurs': + '0', + 'maxOccurs': + 'unbounded', + 'inverse': + 'RotatingMachine.IEEE1547Setting' + }) + ''' + ''' + + +@dataclass(repr=False) +class IEEE1547TripSettings(Identity): + ''' + ''' + OF1frequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + OF1time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + OF2frequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + OF2time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + OV1time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + OV1voltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + OV2time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + OV2voltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UF1frequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UF1time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UF2frequency: Optional[float | Frequency] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UF2time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UV1time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UV1voltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UV2time: Optional[float | Seconds] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + UV2voltage: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + PowerElectronicsConnections: list[str | PowerElectronicsConnection] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'PowerElectronicsConnection.IEEE1547TripSettings' + }) + ''' + ''' + RotatingMachines: list[str | RotatingMachine] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'RotatingMachine.IEEE1547TripSettings' + }) + ''' + ''' + + +@dataclass(repr=False) +class IdentifiedObject(Identity): + ''' + This is a root class to provide common identification for all classes needing + identification and naming attributes. + ''' + mRID: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Master resource identifier issued by a model authority. The mRID is unique + within an exchange context. Global uniqueness is easily achieved by using + a UUID, as specified in RFC 4122, for the mRID. The use of UUID is strongly + recommended. + For CIMXML data files in RDF syntax conforming to IEC 61970-552 Edition + 1, the mRID is mapped to rdf:ID or rdf:about attributes that identify CIM + object elements. + ''' + aliasName: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The aliasName is free text human readable name of the object alternative + to IdentifiedObject.name. It may be non unique and may not correlate to + a naming hierarchy. + The attribute aliasName is retained because of backwards compatibility + between CIM relases. It is however recommended to replace aliasName with + the Name class as aliasName is planned for retirement at a future time. + ''' + description: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The description is a free human readable text describing or naming the + object. It may be non unique and may not correlate to a naming hierarchy. + ''' + name: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The name is any free human readable and possibly non unique text naming + the object. + ''' + + +@dataclass(repr=False) +class ACDCTerminal(IdentifiedObject): + ''' + An electrical connection point (AC or DC) to a piece of conducting equipment. + Terminals are connected at physical connection points called connectivity + nodes. + ''' + connected: Optional[bool] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The connected status is related to a bus-branch model and the topological + node to terminal relation. True implies the terminal is connected to the + related topological node and false implies it is not. + In a bus-branch model, the connected status is used to tell if equipment + is disconnected without having to change the connectivity described by + the topological node to terminal relation. A valid case is that conducting + equipment can be connected in one end and open in the other. In particular + for an AC line segment, where the reactive line charging can be significant, + this is a relevant case. + ''' + Measurements: list[str | Measurement] = field(default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'Measurement.Terminal' + }) + ''' + Measurements associated with this terminal defining where the measurement + is placed in the network topology. It may be used, for instance, to capture + the sensor position, such as a voltage transformer (PT) at a busbar or + a current transformer (CT) at the bar between a breaker and an isolator. + ''' + + +@dataclass(repr=False) +class Terminal(ACDCTerminal): + ''' + An AC electrical connection point to a piece of conducting equipment. Terminals + are connected at physical connection points called connectivity nodes. + ''' + ConductingEquipment: Optional[str | ConductingEquipment] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'ConductingEquipment.Terminals' + }) + ''' + The conducting equipment of the terminal. Conducting equipment have terminals + that may be connected to other conducting equipment terminals via connectivity + nodes or topological nodes. + ''' + + +@dataclass(repr=False) +class IOPoint(IdentifiedObject): + ''' + The class describe a measurement or control value. The purpose is to enable + having attributes and associations common for measurement and control. + ''' + + +@dataclass(repr=False) +class MeasurementValue(IOPoint): + ''' + The current state for a measurement. A state value is an instance of a + measurement from a specific source. Measurements can be associated with + many state values, each representing a different source for the measurement. + ''' + timeStamp: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The time when the value was last updated + ''' + sensorAccuracy: Optional[float | PerCent] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The limit, expressed as a percentage of the sensor maximum, that errors + will not exceed when the sensor is used under reference conditions. + ''' + MeasurementValueQuality: Optional[str | MeasurementValueQuality] = field( + default=None, + metadata={ + 'type': 'Aggregate Of', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'MeasurementValueQuality.MeasurementValue' + }) + ''' + A MeasurementValue has a MeasurementValueQuality associated with it. + ''' + MeasurementValueSource: Optional[str | MeasurementValueSource] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'MeasurementValueSource.MeasurementValues' + }) + ''' + A reference to the type of source that updates the MeasurementValue, e.g. + SCADA, CCLink, manual, etc. User conventions for the names of sources are + contained in the introduction to IEC 61970-301. + ''' + + +@dataclass(repr=False) +class AnalogValue(MeasurementValue): + ''' + AnalogValue represents an analog MeasurementValue. + ''' + value: Optional[float] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The value to supervise. + ''' + Analog: Optional[str | Analog] = field(default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'Analog.AnalogValues' + }) + ''' + Measurement to which this value is connected. + ''' + AnalogControl: Optional[str | AnalogControl] = field(default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'AnalogControl.AnalogValue' + }) + ''' + The Control variable associated with the MeasurementValue. + ''' + + +@dataclass(repr=False) +class DiscreteValue(MeasurementValue): + ''' + DiscreteValue represents a discrete MeasurementValue. + ''' + value: Optional[int] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + The value to supervise. + ''' + Command: Optional[str | Command] = field(default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'Command.DiscreteValue' + }) + ''' + The Control variable associated with the MeasurementValue. + ''' + Discrete: Optional[str | Discrete] = field(default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'Discrete.DiscreteValues' + }) + ''' + Measurement to which this value is connected. + ''' + + +@dataclass(repr=False) +class Measurement(IdentifiedObject): + ''' + A Measurement represents any measured, calculated or non-measured non-calculated + quantity. Any piece of equipment may contain Measurements, e.g. a substation + may have temperature measurements and door open indications, a transformer + may have oil temperature and tank pressure measurements, a bay may contain + a number of power flow measurements and a Breaker may contain a switch + status measurement. + The PSR - Measurement association is intended to capture this use of Measurement + and is included in the naming hierarchy based on EquipmentContainer. The + naming hierarchy typically has Measurements as leafs, e.g. Substation-VoltageLevel-Bay-Switch-Measurement. + Some Measurements represent quantities related to a particular sensor location + in the network, e.g. a voltage transformer (PT) at a busbar or a current + transformer (CT) at the bar between a breaker and an isolator. The sensing + position is not captured in the PSR - Measurement association. Instead + it is captured by the Measurement - Terminal association that is used to + define the sensing location in the network topology. The location is defined + by the connection of the Terminal to ConductingEquipment. + If both a Terminal and PSR are associated, and the PSR is of type ConductingEquipment, + the associated Terminal should belong to that ConductingEquipment instance. + When the sensor location is needed both Measurement-PSR and Measurement-Terminal + are used. The Measurement-Terminal association is never used alone. + ''' + measurementType: Optional[str] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Specifies the type of measurement. For example, this specifies if the measurement + represents an indoor temperature, outdoor temperature, bus voltage, line + flow, etc. + When the measurementType is set to "Specialization", the type of Measurement + is defined in more detail by the specialized class which inherits from + Measurement. + ''' + phases: Optional[str | PhaseCode] = field(default=None, + metadata={ + 'type': 'Enumeration', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Indicates to which phases the measurement applies and avoids the need to + use 'measurementType' to also encode phase information (which would explode + the types). The phase information in Measurement, along with 'measurementType' + and 'phases' uniquely defines a Measurement for a device, based on normal + network phase. Their meaning will not change when the computed energizing + phasing is changed due to jumpers or other reasons. + If the attribute is missing three phases (ABC) shall be assumed. + ''' + PowerSystemResource: Optional[str | PowerSystemResource] = field( + default=None, + metadata={ + 'type': 'Of Aggregate', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'PowerSystemResource.Measurements' + }) + ''' + The power system resource that contains the measurement. + ''' + Terminal: Optional[str | ACDCTerminal] = field(default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'ACDCTerminal.Measurements' + }) + ''' + One or more measurements may be associated with a terminal in the network. + ''' + + +@dataclass(repr=False) +class Analog(Measurement): + ''' + Analog represents an analog Measurement. + ''' + normalValue: Optional[float] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Normal measurement value, e.g., used for percentage calculations. + ''' + + +@dataclass(repr=False) +class Discrete(Measurement): + ''' + Discrete represents a discrete Measurement, i.e. a Measurement representing + discrete values, e.g. a Breaker position. + ''' + normalValue: Optional[int] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Normal measurement value, e.g., used for percentage calculations. + ''' + + +@dataclass(repr=False) +class MeasurementValueSource(IdentifiedObject): + ''' + MeasurementValueSource describes the alternative sources updating a MeasurementValue. + User conventions for how to use the MeasurementValueSource attributes are + described in the introduction to IEC 61970-301. + ''' + + +@dataclass(repr=False) +class PowerSystemResource(IdentifiedObject): + ''' + A power system resource can be an item of equipment such as a switch, an + equipment container containing many individual items of equipment such + as a substation, or an organisational entity such as sub-control area. + Power system resources can have measurements associated. + ''' + Measurements: list[str | Measurement] = field(default_factory=list, + metadata={ + 'type': 'Aggregate Of', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'Measurement.PowerSystemResource' + }) + ''' + The measurements associated with this power system resource. + ''' + + +@dataclass(repr=False) +class ConductingEquipment(PowerSystemResource): + ''' + The parts of the AC power system that are designed to carry current or + that are conductively connected through terminals. + ''' + + +@dataclass(repr=False) +class PowerElectronicsConnection(ConductingEquipment): + ''' + A connection to the AC network for energy production or consumption that + uses power electronics rather than rotating machines. + ''' + inverterMode: Optional[str | SmartInverterMode] = field(default=None, + metadata={ + 'type': 'enum', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + ''' + maxIFault: Optional[float | PU] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Maximum fault current this device will contribute, in per-unit of rated + current, before the converter protection will trip or bypass. + ''' + maxQ: Optional[float | ReactivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Maximum reactive power limit. This is the maximum (nameplate) limit for + the unit. + ''' + minQ: Optional[float | ReactivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Minimum reactive power limit for the unit. This is the minimum (nameplate) + limit for the unit. + ''' + p: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Active power injection. Load sign convention is used, i.e. positive sign + means flow out from a node. + Starting value for a steady state solution. + ''' + q: Optional[float | ReactivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Reactive power injection. Load sign convention is used, i.e. positive sign + means flow out from a node. + Starting value for a steady state solution. + ''' + ratedS: Optional[float | ApparentPower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Nameplate apparent power rating for the unit. + The attribute shall have a positive value. + ''' + ratedU: Optional[float | Voltage] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Rated voltage (nameplate data, Ur in IEC 60909-0). It is primarily used + for short circuit data exchange according to IEC 60909. + ''' + IEEE1547ControlSettings: Optional[str | IEEE1547ControlSettings] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'IEEE1547ControlSettings.PowerElectronicsConnections' + }) + ''' + ''' + IEEE1547Info: Optional[str | IEEE1547Info] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'IEEE1547Info.PowerElectronicsConnections' + }) + ''' + ''' + IEEE1547Setting: Optional[str | IEEE1547Setting] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'IEEE1547Setting.PowerElectronicsConnections' + }) + ''' + ''' + IEEE1547TripSettings: Optional[str | IEEE1547TripSettings] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'IEEE1547TripSettings.PowerElectronicsConnections' + }) + ''' + ''' + PowerElectronicsConnectionPhases: list[str | PowerElectronicsConnectionPhase] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'PowerElectronicsConnectionPhase.PowerElectronicsConnection' + }) + ''' + ''' + PowerElectronicsUnit: list[str | PowerElectronicsUnit] = field( + default_factory=list, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': 'unbounded', + 'inverse': 'PowerElectronicsUnit.PowerElectronicsConnection' + }) + ''' + ''' + + +@dataclass(repr=False) +class RotatingMachine(ConductingEquipment): + ''' + A rotating machine which may be used as a generator or motor. + ''' + ratedPowerFactor: Optional[float] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Power factor (nameplate data). It is primarily used for short circuit data + exchange according to IEC 60909. + ''' + p: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Active power injection. Load sign convention is used, i.e. positive sign + means flow out from a node. + Starting value for a steady state solution. + ''' + q: Optional[float | ReactivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Reactive power injection. Load sign convention is used, i.e. positive sign + means flow out from a node. + Starting value for a steady state solution. + ''' + ratedS: Optional[float | ApparentPower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Nameplate apparent power rating for the unit. + The attribute shall have a positive value. + ''' + ratedU: Optional[float | Voltage] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Rated voltage (nameplate data, Ur in IEC 60909-0). It is primarily used + for short circuit data exchange according to IEC 60909. + ''' + GeneratingUnit: Optional[str | GeneratingUnit] = field(default=None, + metadata={ + 'type': + 'Association', + 'minOccurs': + '0', + 'maxOccurs': + '1', + 'inverse': + 'GeneratingUnit.RotatingMachine' + }) + ''' + A synchronous machine may operate as a generator and as such becomes a + member of a generating unit. + ''' + IEEE1547ControlSettings: Optional[str | IEEE1547ControlSettings] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'IEEE1547ControlSettings.RotatingMachines' + }) + ''' + ''' + IEEE1547Info: Optional[str | IEEE1547Info] = field(default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': + 'IEEE1547Info.RotatingMachines' + }) + ''' + ''' + IEEE1547Setting: Optional[str | IEEE1547Setting] = field(default=None, + metadata={ + 'type': + 'Association', + 'minOccurs': + '0', + 'maxOccurs': + '1', + 'inverse': + 'IEEE1547Setting.RotatingMachines' + }) + ''' + ''' + IEEE1547TripSettings: Optional[str | IEEE1547TripSettings] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'IEEE1547TripSettings.RotatingMachines' + }) + ''' + ''' + + +@dataclass(repr=False) +class GeneratingUnit(PowerSystemResource): + ''' + A single or set of synchronous machines for converting mechanical power + into alternating-current power. For example, individual machines within + a set may be defined for scheduling purposes while a single control signal + is derived for the set. In this case there would be a GeneratingUnit for + each member of the set and an additional GeneratingUnit corresponding to + the set. + ''' + RotatingMachine: list[str | RotatingMachine] = field(default_factory=list, + metadata={ + 'type': + 'Association', + 'minOccurs': + '0', + 'maxOccurs': + 'unbounded', + 'inverse': + 'RotatingMachine.GeneratingUnit' + }) + ''' + A synchronous machine may operate as a generator and as such becomes a + member of a generating unit. + ''' + + +@dataclass(repr=False) +class PowerElectronicsConnectionPhase(PowerSystemResource): + ''' + ''' + p: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Active power injection. Load sign convention is used, i.e. positive sign + means flow into the equipment from the network. + ''' + phase: Optional[str | SinglePhaseKind] = field(default=None, + metadata={ + 'type': 'Enumeration', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Phase of this energy producer component. If the energy producer is wye + connected, the connection is from the indicated phase to the central ground + or neutral point. If the energy producer is delta connected, the phase + indicates an energy producer connected from the indicated phase to the + next logical non-neutral phase. + ''' + q: Optional[float | ReactivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Reactive power injection. Load sign convention is used, i.e. positive sign + means flow into the equipment from the network. + ''' + PowerElectronicsConnection: Optional[str | PowerElectronicsConnection] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'PowerElectronicsConnection.PowerElectronicsConnectionPhases' + }) + ''' + ''' + + +@dataclass(repr=False) +class PowerElectronicsUnit(PowerSystemResource): + ''' + A generating unit or battery or aggregation that connects to the AC network + using power electronics rather than rotating machines. + ''' + maxP: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Maximum active power limit. This is the maximum (nameplate) limit for the + unit. + ''' + minP: Optional[float | ActivePower] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + Minimum active power limit. This is the minimum (nameplate) limit for the + unit. + ''' + PowerElectronicsConnection: Optional[str | PowerElectronicsConnection] = field( + default=None, + metadata={ + 'type': 'Association', + 'minOccurs': '0', + 'maxOccurs': '1', + 'inverse': 'PowerElectronicsConnection.PowerElectronicsUnit' + }) + ''' + ''' + + +@dataclass(repr=False) +class BatteryUnit(PowerElectronicsUnit): + ''' + An electrochemical energy storage device + ''' + batteryState: Optional[str | BatteryState] = field(default=None, + metadata={ + 'type': 'Enumeration', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + indicates whether the battery is charging, discharging or idle + ''' + ratedE: Optional[float | RealEnergy] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + full energy storage capacity of the battery + ''' + storedE: Optional[float | RealEnergy] = field(default=None, + metadata={ + 'type': 'Attribute', + 'minOccurs': '0', + 'maxOccurs': '1' + }) + ''' + amount of energy currently stored; no more than ratedE + ''' + + +@dataclass(repr=False) +class PhotoVoltaicUnit(PowerElectronicsUnit): + ''' + A photovoltaic device or an aggregation of such devices + ''' + + +@dataclass(repr=False) +class MeasurementValueQuality(Identity): + ''' + Measurement quality flags. Bits 0-10 are defined for substation automation + in draft IEC 61850 part 7-3. Bits 11-15 are reserved for future expansion + by that document. Bits 16-31 are reserved for EMS applications. + ''' + + +class BatteryState(Enum): + ''' + unable to Charge, and not Discharging + ''' + + +class IEEE1547AbnormalPerfomanceCategory(Enum): + ''' + ''' + + +class IEEE1547IslandingCategory(Enum): + ''' + See clause 8.2 + ''' + + +class IEEE1547NormalPerformanceCategory(Enum): + ''' + ''' + + +class PhaseCode(Enum): + ''' + An unordered enumeration of phase identifiers. Allows designation of phases + for both transmission and distribution equipment, circuits and loads. The + enumeration, by itself, does not describe how the phases are connected + together or connected to ground. Ground is not explicitly denoted as a + phase. + Residential and small commercial loads are often served from single-phase, + or split-phase, secondary circuits. For example of s12N, phases 1 and 2 + refer to hot wires that are 180 degrees out of phase, while N refers to + the neutral wire. Through single-phase transformer connections, these secondary + circuits may be served from one or two of the primary phases A, B, and + C. For three-phase loads, use the A, B, C phase codes instead of s12N. + ''' + A = 'A' + ''' + Phase A. + ''' + AB = 'AB' + ''' + Phases A and B. + ''' + ABC = 'ABC' + ''' + Phases A, B, and C. + ''' + ABCN = 'ABCN' + ''' + Phases A, B, C, and N. + ''' + ABN = 'ABN' + ''' + Phases A, B, and neutral. + ''' + AC = 'AC' + ''' + Phases A and C. + ''' + ACN = 'ACN' + ''' + Phases A, C and neutral. + ''' + AN = 'AN' + ''' + Phases A and neutral. + ''' + B = 'B' + ''' + Phase B. + ''' + BC = 'BC' + ''' + Phases B and C. + ''' + BCN = 'BCN' + ''' + Phases B, C, and neutral. + ''' + BN = 'BN' + ''' + Phases B and neutral. + ''' + C = 'C' + ''' + Phase C. + ''' + CN = 'CN' + ''' + Phases C and neutral. + ''' + N = 'N' + ''' + Neutral phase. + ''' + X = 'X' + ''' + Unknown non-neutral phase. + ''' + XN = 'XN' + ''' + Unknown non-neutral phase plus neutral. + ''' + XY = 'XY' + ''' + Two unknown non-neutral phases. + ''' + XYN = 'XYN' + ''' + Two unknown non-neutral phases plus neutral. + ''' + none = 'none' + ''' + No phases specified. + ''' + s1 = 's1' + ''' + Secondary phase 1. + ''' + s12 = 's12' + ''' + Secondary phase 1 and 2. + ''' + s12N = 's12N' + ''' + Secondary phases 1, 2, and neutral. + ''' + s1N = 's1N' + ''' + Secondary phase 1 and neutral. + ''' + s2 = 's2' + ''' + Secondary phase 2. + ''' + s2N = 's2N' + ''' + Secondary phase 2 and neutral. + ''' + + +class SinglePhaseKind(Enum): + ''' + Enumeration of single phase identifiers. Allows designation of single phases + for both transmission and distribution equipment, circuits and loads. + ''' + A = 'A' + ''' + Phase A. + ''' + B = 'B' + ''' + Phase B. + ''' + C = 'C' + ''' + Phase C. + ''' + N = 'N' + ''' + Neutral. + ''' + s1 = 's1' + ''' + Secondary phase 1. + ''' + s2 = 's2' + ''' + Secondary phase 2. + ''' + + +class SmartInverterMode(Enum): + ''' + Required to dispatch P and Q + ''' + + +@dataclass +class ReactivePower(): + value: float = field(default=None) + ''' + Product of RMS value of the voltage and the RMS value of the quadrature + component of the current. + ''' + + +@dataclass +class Susceptance(): + value: float = field(default=None) + ''' + Imaginary part of admittance. + ''' + + +@dataclass +class Seconds(): + value: float = field(default=None) + ''' + Time, in seconds. + ''' + + +@dataclass +class Voltage(): + value: float = field(default=None) + ''' + Electrical voltage, can be both AC and DC. + ''' + + +@dataclass +class ApparentPower(): + value: float = field(default=None) + ''' + Product of the RMS value of the voltage and the RMS value of the current. + ''' + + +@dataclass +class PerCent(): + value: float = field(default=None) + ''' + Percentage on a defined base. For example, specify as 100 to indicate at + the defined base. + ''' + + +@dataclass +class RealEnergy(): + value: float = field(default=None) + ''' + Real electrical energy. + ''' + + +@dataclass +class PU(): + value: float = field(default=None) + ''' + Per Unit - a positive or negative value referred to a defined base. Values + typically range from -10 to +10. + ''' + + +@dataclass +class Frequency(): + value: float = field(default=None) + ''' + Cycles per second. + ''' + + +@dataclass +class ActivePower(): + value: float = field(default=None) + ''' + Product of RMS value of the voltage and the RMS value of the in-phase component + of the current. + ''' diff --git a/tests/put_soc.py b/tests/put_soc.py new file mode 100644 index 0000000..8c16e62 --- /dev/null +++ b/tests/put_soc.py @@ -0,0 +1,17 @@ +from pathlib import Path + +from ieee_2030_5.client import IEEE2030_5_Client + +server = "WE48687" +port = 8090 +ca_path = (Path("~/tls") / "certs/ca.crt").expanduser() +public = (Path("~/tls") / "certs/dev1.crt").expanduser() +private = (Path("~/tls") / "private/dev1.pem").expanduser() + +client = IEEE2030_5_Client(cafile=ca_path, server_hostname=server, keyfile=private, certfile=public, server_ssl_port=port, debug=True) + +dcap = client.device_capability() +der_list = client.der_list() + +client.disconnect() +