Skip to content

Commit

Permalink
[DEV-2009] Add cuts and clamps. (#202)
Browse files Browse the repository at this point in the history
Signed-off-by: Anthony Charlton <[email protected]>
  • Loading branch information
charlta authored Jan 16, 2025
1 parent 56af3a5 commit b2eef64
Show file tree
Hide file tree
Showing 50 changed files with 1,424 additions and 882 deletions.
35 changes: 24 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ Our main style requirements are:

## Checklist for model change ##

Make sure you double (or triple) check the ones listed in the "most important" list below.

1. Update `pom.xml` to import the correct version of `evolve-grpc`.
2. Model updated and tested.
3. Add/remove methods added to *Service class if new class was added.
4. Descriptions copied from CIM and added as doc comments to new changes (on class, property etc)
5. `FillFields.kt` updated to populate data for tests. Utilise `includeRuntime` if required.
6. Database:
2. Model updated and tested. Pay attention to:
1. Ensure descriptions are copied from CIM and added as doc comments.
2. All extension classes/enums are under the `com.zepben.evolve.cim.extensions` package in their appropriate sub-package.
3. Mark all extensions (classes, enums and properties) with `@ZBEX` and documented with `[ZBEX]`
3. Add/remove methods added to *Service class if any new classes were added.
4. `FillFields.kt` updated to populate data for tests. Utilise `includeRuntime` if required.
5. Database:
1. Table class(es) updated. - `com.zepben.evolve.database.sqlite.cim.tables`
2. New tables added to appropriate database table collections:
* `com.zepben.evolve.database.sqlite.cim.customer.CustomerDatabaseTables`
Expand Down Expand Up @@ -61,18 +65,27 @@ Our main style requirements are:
* `com.zepben.evolve.database.sqlite.cim.customer.CustomerDatabaseSchemaTest`
* `com.zepben.evolve.database.sqlite.cim.diagram.DiagramDatabaseSchemaTest`
* `com.zepben.evolve.database.sqlite.cim.network.NetworkDatabaseSchemaTest`
7. Reference resolver(s) added (if new associations).
8. Protobuf/gRPC
6. Reference resolver(s) added (if new associations).
7. Protobuf/gRPC
1. *CimToProto(s) updated (including java wrapper).
2. *ProtoToCim(s) updated (including java wrapper).
3. *TranslatorTest(s) updated.
8. *ServiceComparator(s) updated.
9. *ServiceComparatorTest(s) added for each new class and property.
10. Exhaustive when functions in *ServiceUtils updated if a new class is added. Update *ServiceUtilsTest to match.
11. Release notes updated.

NOTE: Do not update the StupidlyLargeNetwork file, this will be phased out.

1. *ServiceComparator(s) updated.
2. *ServiceComparatorTest(s) added for each new class and property.
3. Exhaustive when functions in *ServiceUtils updated if a new class is added. Update *ServiceUtilsTest to match.
4. Release notes updated.
### Most Important ###

These are the most important changes to pay attention to:
1. `Model updated with descriptions copied from CIM` - commonly done incorrectly.
2. `FillFields` - fills in the data that many other tests rely on to detect differences in default vs filled classes.
3. `*ServiceComparator(s) and *ServiceComparatorTest(s)` - used by many tests to validate things, many which can pass with false positive results if not done.
4. `*TranslatorTest(s)` - ensure the classes will work correctly in user code
5. `*DatabaseSchemaTest(s)` - ensure migrators will produce databases that load correctly.
6. `ChangeSetValidator` - must test upgrading existing database tables with populated data to ensure old databases will work.

## Adding support for new services ##

Expand Down
31 changes: 21 additions & 10 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,37 @@

### New Features
* Network state services for updating and querying network state events via gRPC.
* Client functionality for updating and querying network states via gRPC service stub.
* Client functionality for updating and querying network states via gRPC service stub.
* `BaseService` now contains a `MetadataCollection` to tightly couple the metadata to the associated service.
* Added `Services`, a new class which contains a copy of each `BaseService` supported by the SDK.
* Added `connectWithAccessTokenInsecure()` for connecting to a gRPC service using an access token without SSL/TLS.
* Added `connectWithAccessToken()` for connecting to a gRPC service using an access token with SSL/TLS.
* Added `PanDemandResponseFunction`, a new class which contains `EndDeviceFunctionKind` and the identity of the `ControlledAppliance` of this function.
* Added `BatteryControl`, a new class which describes behaviour specific to controlling a `BatteryUnit`.
* Added `StaticVarCompensator` a new class representing a facility for providing variable and controllable shunt reactive power.
* Added `ControlledAppliance` a new class representing the identity of the appliance controlled by a specific `EndDeviceFunction`.
* Added `PerLengthPhaseImpedance` a new class used for representing the impedance of individual wires on an AcLineSegment.
* Added `PhaseImpedanceData` a data class with a link to `PerLengthPhaseImpedance`, for capturing the phase impedance data of an individual wire.
* Added the following new CIM classes:
* `AssetFunction`, the function performed by an asset.
* `BatteryControl`, a new class which describes behaviour specific to controlling a `BatteryUnit`.
* `Clamp`: A Clamp is a galvanic connection at a line segment where other equipment is connected. A Clamp does not cut the line segment. A Clamp is
ConductingEquipment and has one Terminal with an associated ConnectivityNode. Any other ConductingEquipment can be connected to the Clamp ConnectivityNode.
__NOT CURRENTLY FULLY SUPPORTED BY TRACING__
* `ControlledAppliance`, a new class representing the identity of the appliance controlled by a specific `EndDeviceFunction`.
* `Cut`: A cut separates a line segment into two parts. The cut appears as a switch inserted between these two parts and connects them together. As the cut is
normally open there is no galvanic connection between the two line segment parts. But it is possible to close the cut to get galvanic connection. The cut
terminals are oriented towards the line segment terminals with the same sequence number. Hence the cut terminal with sequence number equal to 1 is oriented
to the line segment's terminal with sequence number equal to 1. The cut terminals also act as connection points for jumpers and other equipment, e.g. a
mobile generator. To enable this, connectivity nodes are placed at the cut terminals. Once the connectivity nodes are in place any conducting equipment can
be connected at them.
__NOT CURRENTLY FULLY SUPPORTED BY TRACING__
* `EndDeviceFunction`, the function performed by an end device such as a meter, communication equipment, controllers, etc.
* `PanDemandResponseFunction`, a new class which contains `EndDeviceFunctionKind` and the identity of the `ControlledAppliance` of this function.
* `PerLengthPhaseImpedance`, a new class used for representing the impedance of individual wires on an AcLineSegment.
* `PhaseImpedanceData`, a data class with a link to `PerLengthPhaseImpedance`, for capturing the phase impedance data of an individual wire.
* `StaticVarCompensator`, a new class representing a facility for providing variable and controllable shunt reactive power.
* Added new enums:
* `BatteryControlMode`
* `EndDeviceFunctionKind`
* `SVCControlMode`

### Enhancements
* Added `ctPrimary` and `minTargetDeadband` to `RegulatingContrl`.
* Added collection of `BatteryControl` to `BatteryUnit`
* Added collection of `EndDeviceFunctionKind` to `EndDevice`
* Added an unordered collection comparator.
* Added the energized relationship for the current state of network between `Feeder` and `LvFeeder`.
* Updated `NetworkConsumer`'s `getEquipmentForContainers`, `getEquipmentContainers` and `getEquipmentForLoop` to allow requesting normal, current or all
Expand All @@ -37,7 +48,7 @@
* None.

### Notes
* None.
* `Cut` and `Clamp` have been added to the model, but no processing for them has been added to the tracing, so results will not be what you expect.

## [0.23.0] - 2024-10-18
### Breaking Changes
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<dependency>
<groupId>com.zepben.protobuf</groupId>
<artifactId>evolve-grpc</artifactId>
<version>0.33.0-SNAPSHOT5</version>
<version>0.34.0-SNAPSHOT1</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.zepben.evolve.cim.extensions.ZBEX
import com.zepben.evolve.cim.iec61970.base.wires.RegulatingControl

/**
* [ZBEX]
* Describes behaviour specific to controlling batteries.
*
* @property chargingRate [ZBEX] Charging rate (input power) in percentage of maxP. (Unit: PerCent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

package com.zepben.evolve.cim.iec61970.base.wires

import com.zepben.evolve.services.common.extensions.*

/**
* A wire or combination of wires, with consistent electrical characteristics, building a single electrical system, used to carry alternating current
* between points in the power system.
Expand All @@ -21,10 +23,14 @@ package com.zepben.evolve.cim.iec61970.base.wires
* @property perLengthImpedance Per-length impedance of this line segment.
* @property perLengthPhaseImpedance Per-length phase impedance of this line segment.
* @property perLengthSequenceImpedance Per-length sequence impedance of this line segment.
* @property cuts Cuts applied to the line segment.
* @property clamps The clamps connected to the line segment.
*/
class AcLineSegment @JvmOverloads constructor(mRID: String = "") : Conductor(mRID) {

var perLengthImpedance: PerLengthImpedance? = null
private var _cuts: MutableList<Cut>? = null
private var _clamps: MutableList<Clamp>? = null

var perLengthSequenceImpedance: PerLengthSequenceImpedance?
get() = perLengthImpedance as? PerLengthSequenceImpedance
Expand All @@ -38,4 +44,134 @@ class AcLineSegment @JvmOverloads constructor(mRID: String = "") : Conductor(mRI
perLengthImpedance = it
}

val cuts: List<Cut> get() = _cuts.asUnmodifiable()

/**
* Get the number of entries in the [Cut] collection.
*/
fun numCuts(): Int = _cuts?.size ?: 0

/**
* Get the [Cut] of this [AcLineSegment] represented by [mRID]
*
* @param mRID the mRID of the required [Cut]
* @return The [Cut] with the specified [mRID] if it exists, otherwise null
*/
fun getCut(mRID: String): Cut? = _cuts.getByMRID(mRID)

/**
* Add a [Cut] to this [AcLineSegment]
*
* @return This [AcLineSegment] for fluent use
*/
fun addCut(cut: Cut): AcLineSegment {
if (validateCut(cut))
return this

_cuts = _cuts ?: mutableListOf()
_cuts!!.add(cut)

return this
}

/**
* Remove a [Cut] from this [AcLineSegment]
*
* @param cut The [Cut] to remove
* @return true if [cut] is removed from the collection
*/
fun removeCut(cut: Cut): Boolean {
val ret = _cuts.safeRemove(cut)
if (_cuts.isNullOrEmpty()) _cuts = null
return ret
}

/**
* Clear all [Cut]'s from this [AcLineSegment]
*
* @return This [AcLineSegment] for fluent use
*/
fun clearCuts(): AcLineSegment {
_cuts = null
return this
}

val clamps: List<Clamp> get() = _clamps.asUnmodifiable()

/**
* Get the number of entries in the [Clamp] collection.
*/
fun numClamps(): Int = _clamps?.size ?: 0

/**
* Get the [Clamp] of this [AcLineSegment] represented by [mRID]
*
* @param mRID the mRID of the required [Clamp]
* @return The [Clamp] with the specified [mRID] if it exists, otherwise null
*/
fun getClamp(mRID: String): Clamp? = _clamps.getByMRID(mRID)

/**
* Add a [Clamp] to this [AcLineSegment]
*
* @return This [AcLineSegment] for fluent use
*/
fun addClamp(clamp: Clamp): AcLineSegment {
if (validateClamp(clamp))
return this

_clamps = _clamps ?: mutableListOf()
_clamps!!.add(clamp)

return this
}

/**
* Remove a [Clamp] from this [AcLineSegment]
*
* @param clamp The [Clamp] to remove
* @return true if [clamp] is removed from the collection
*/
fun removeClamp(clamp: Clamp): Boolean {
val ret = _clamps.safeRemove(clamp)
if (_clamps.isNullOrEmpty()) _clamps = null
return ret
}

/**
* Clear all [Clamp]'s from this [AcLineSegment]
*
* @return This [AcLineSegment] for fluent use
*/
fun clearClamps(): AcLineSegment {
_clamps = null
return this
}

private fun validateCut(cut: Cut): Boolean {
if (validateReference(cut, ::getCut, "A Cut"))
return true

if (cut.acLineSegment == null)
cut.acLineSegment = this

require(cut.acLineSegment === this) {
"${cut.typeNameAndMRID()} `acLineSegment` property references ${cut.acLineSegment!!.typeNameAndMRID()}, expected ${typeNameAndMRID()}."
}
return false
}

private fun validateClamp(clamp: Clamp): Boolean {
if (validateReference(clamp, ::getClamp, "A Clamp"))
return true

if (clamp.acLineSegment == null)
clamp.acLineSegment = this

require(clamp.acLineSegment === this) {
"${clamp.typeNameAndMRID()} `acLineSegment` property references ${clamp.acLineSegment!!.typeNameAndMRID()}, expected ${typeNameAndMRID()}."
}
return false
}

}
26 changes: 26 additions & 0 deletions src/main/kotlin/com/zepben/evolve/cim/iec61970/base/wires/Clamp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2020 Zeppelin Bend Pty Ltd
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

package com.zepben.evolve.cim.iec61970.base.wires

import com.zepben.evolve.cim.iec61970.base.core.ConductingEquipment

/**
* A Clamp is a galvanic connection at a line segment where other equipment is connected. A Clamp does not cut the line segment. A Clamp is ConductingEquipment
* and has one Terminal with an associated ConnectivityNode. Any other ConductingEquipment can be connected to the Clamp ConnectivityNode.
*
* @property lengthFromTerminal1 The length to the place where the clamp is located starting from side one of the line segment, i.e. the line segment terminal
* with sequence number equal to 1.
* @property acLineSegment The line segment to which the clamp is connected.
*/
class Clamp @JvmOverloads constructor(mRID: String = "") : ConductingEquipment(mRID) {

var lengthFromTerminal1: Double? = null
var acLineSegment: AcLineSegment? = null

}
28 changes: 28 additions & 0 deletions src/main/kotlin/com/zepben/evolve/cim/iec61970/base/wires/Cut.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2020 Zeppelin Bend Pty Ltd
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

package com.zepben.evolve.cim.iec61970.base.wires

/**
* A cut separates a line segment into two parts. The cut appears as a switch inserted between these two parts and connects them together. As the cut is
* normally open there is no galvanic connection between the two line segment parts. But it is possible to close the cut to get galvanic connection. The cut
* terminals are oriented towards the line segment terminals with the same sequence number. Hence the cut terminal with sequence number equal to 1 is oriented
* to the line segment's terminal with sequence number equal to 1. The cut terminals also act as connection points for jumpers and other equipment, e.g. a
* mobile generator. To enable this, connectivity nodes are placed at the cut terminals. Once the connectivity nodes are in place any conducting equipment can
* be connected at them.
*
* @property lengthFromTerminal1 The length to the place where the cut is located starting from side one of the cut line segment, i.e. the line segment Terminal
* with sequenceNumber equal to 1.
* @property acLineSegment The line segment to which the cut is applied.
*/
class Cut @JvmOverloads constructor(mRID: String = "") : Switch(mRID) {

var lengthFromTerminal1: Double? = null
var acLineSegment: AcLineSegment? = null

}
Original file line number Diff line number Diff line change
Expand Up @@ -1629,6 +1629,27 @@ class NetworkCimReader(
return loadConnector(busbarSection, table, resultSet) && service.addOrThrow(busbarSection)
}

/**
* Create a [Clamp] and populate its fields from [TableClamps].
*
* @param table The database table to read the [Clamp] fields from.
* @param resultSet The record in the database table containing the fields for this [Clamp].
* @param setIdentifier A callback to register the mRID of this [Clamp] for logging purposes.
*
* @return true if the [Clamp] was successfully read from the database and added to the service.
* @throws SQLException For any errors encountered reading from the database.
*/
@Throws(SQLException::class)
fun load(table: TableClamps, resultSet: ResultSet, setIdentifier: (String) -> String): Boolean {
val clamp = Clamp(setIdentifier(resultSet.getString(table.MRID.queryIndex))).apply {
lengthFromTerminal1 = resultSet.getNullableDouble(table.LENGTH_FROM_TERMINAL_1.queryIndex)
acLineSegment = service.ensureGet(resultSet.getString(table.AC_LINE_SEGMENT_MRID.queryIndex), typeNameAndMRID())
acLineSegment?.addClamp(this)
}

return loadConductingEquipment(clamp, table, resultSet) && service.addOrThrow(clamp)
}

@Throws(SQLException::class)
private fun loadConductor(conductor: Conductor, table: TableConductors, resultSet: ResultSet): Boolean {
conductor.apply {
Expand All @@ -1648,6 +1669,27 @@ class NetworkCimReader(
private fun loadConnector(connector: Connector, table: TableConnectors, resultSet: ResultSet): Boolean =
loadConductingEquipment(connector, table, resultSet)

/**
* Create a [Cut] and populate its fields from [TableCuts].
*
* @param table The database table to read the [Cut] fields from.
* @param resultSet The record in the database table containing the fields for this [Cut].
* @param setIdentifier A callback to register the mRID of this [Cut] for logging purposes.
*
* @return true if the [Cut] was successfully read from the database and added to the service.
* @throws SQLException For any errors encountered reading from the database.
*/
@Throws(SQLException::class)
fun load(table: TableCuts, resultSet: ResultSet, setIdentifier: (String) -> String): Boolean {
val cut = Cut(setIdentifier(resultSet.getString(table.MRID.queryIndex))).apply {
lengthFromTerminal1 = resultSet.getNullableDouble(table.LENGTH_FROM_TERMINAL_1.queryIndex)
acLineSegment = service.ensureGet(resultSet.getString(table.AC_LINE_SEGMENT_MRID.queryIndex), typeNameAndMRID())
acLineSegment?.addCut(this)
}

return loadSwitch(cut, table, resultSet) && service.addOrThrow(cut)
}

/**
* Create a [Disconnector] and populate its fields from [TableDisconnectors].
*
Expand Down
Loading

0 comments on commit b2eef64

Please sign in to comment.