Skip to content

Commit 75ffad3

Browse files
chestm007kgreav
andcommitted
Endeavour tracing api v2 (#21)
Signed-off-by: Kurt Greaves <kurt.greaves@zepben.com> Co-authored-by: Kurt Greaves <kurt.greaves@zepben.com>
1 parent 63c3fe2 commit 75ffad3

File tree

4 files changed

+206
-1
lines changed

4 files changed

+206
-1
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
python_requires='>=3.9, <3.13',
2222
install_requires=[
2323
"zepben.eas==0.17.1",
24-
"zepben.evolve==0.45.0",
24+
"zepben.evolve==0.48.0b2",
2525
"numba==0.60.0",
2626
"geojson==2.5.0",
2727
"gql[requests]==3.4.1",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Copyright 2025 Zeppelin Bend Pty Ltd
2+
#
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
"""
8+
Example trace showing methods to traverse upstream of a given `IdentifiedObject` to find the first occurrence
9+
of another specified `IdentifiedObject`
10+
"""
11+
12+
import asyncio
13+
import json
14+
15+
from zepben.evolve import NetworkStateOperators, NetworkTraceActionType, NetworkTraceStep, StepContext, \
16+
NetworkConsumerClient, ConductingEquipment, connect_with_token
17+
from zepben.evolve import PowerTransformer, UsagePoint, Tracing, Switch
18+
from zepben.protobuf.nc.nc_requests_pb2 import INCLUDE_ENERGIZED_LV_FEEDERS
19+
20+
with open("config.json") as f:
21+
c = json.loads(f.read())
22+
23+
24+
def _trace(start_item, results, stop_condition):
25+
"""Returns a `NetworkTrace` configured with our parameters"""
26+
27+
def step_action(step: NetworkTraceStep, context: StepContext):
28+
if context.is_stopping: # if the trace is stopping, we have found the equipment we're looking for
29+
results.append(step.path.to_equipment)
30+
31+
state_operators = NetworkStateOperators.NORMAL
32+
33+
return (
34+
Tracing.network_trace(
35+
network_state_operators=state_operators,
36+
action_step_type=NetworkTraceActionType.ALL_STEPS
37+
).add_condition(state_operators.upstream())
38+
.add_stop_condition(stop_condition)
39+
.add_step_action(step_action)
40+
.add_start_item(start_item)
41+
)
42+
43+
44+
async def main(mrid: str, feeder_mrid: str):
45+
channel = connect_with_token(host=c["host"], access_token=c["access_token"], rpc_port=c["rpc_port"])
46+
client = NetworkConsumerClient(channel)
47+
await client.get_equipment_container(feeder_mrid, include_energized_containers=INCLUDE_ENERGIZED_LV_FEEDERS)
48+
network = client.service
49+
50+
try:
51+
usage_point = network.get(mrid, UsagePoint)
52+
# get the `ConductingEquipment` from the `UsagePoint`
53+
start_item = next(filter(lambda ce: isinstance(ce, ConductingEquipment), usage_point.equipment))
54+
except TypeError:
55+
start_item = network.get(mrid, ConductingEquipment)
56+
57+
results = []
58+
59+
# Get DSUB from which any given customer is supplied from using a basic upstream trace
60+
def dsub_stop_condition(step: NetworkTraceStep, context: StepContext):
61+
return isinstance(step.path.to_equipment, PowerTransformer)
62+
63+
# Get Circuit Breaker from which any given customer is supplied from using a basic upstream trace
64+
# Uncomment stop condition below to use
65+
def circuit_breaker_stop_condition(step: NetworkTraceStep, context: StepContext):
66+
return isinstance(step.path.to_equipment, Switch)
67+
68+
await _trace(
69+
start_item=start_item,
70+
results=results,
71+
stop_condition=dsub_stop_condition,
72+
# stop_condition=circuit_breaker_stop_condition,
73+
).run()
74+
75+
print(results)
76+
77+
78+
if __name__ == "__main__":
79+
# EnergyConsumer: 50763684
80+
asyncio.run(main(mrid='4310990779', feeder_mrid='RW1292'))
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Copyright 2025 Zeppelin Bend Pty Ltd
2+
#
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
"""
8+
Example trace showing method to traverse outwards from any given `IdentifiableObject` to the next `Switch` object, and
9+
build a list of all contained equipment (isolate-able section)
10+
"""
11+
12+
import asyncio
13+
import json
14+
15+
from zepben.evolve import NetworkStateOperators, NetworkTraceActionType, NetworkTraceStep, StepContext, \
16+
NetworkConsumerClient, AcLineSegment, connect_with_token
17+
from zepben.evolve import Tracing, Switch
18+
from zepben.protobuf.nc.nc_requests_pb2 import INCLUDE_ENERGIZED_LV_FEEDERS
19+
20+
with open("config.json") as f:
21+
c = json.loads(f.read())
22+
23+
24+
async def main(conductor_mrid: str, feeder_mrid: str):
25+
channel = connect_with_token(host=c["host"], access_token=c["access_token"], rpc_port=c["rpc_port"])
26+
client = NetworkConsumerClient(channel)
27+
await client.get_equipment_container(feeder_mrid, include_energized_containers=INCLUDE_ENERGIZED_LV_FEEDERS)
28+
network = client.service
29+
30+
hv_acls = network.get(conductor_mrid, AcLineSegment)
31+
32+
found_equip = set()
33+
34+
def queue_condition(step: NetworkTraceStep, context: StepContext, _, __):
35+
"""Queue the next step unless it's a `Switch`"""
36+
return not isinstance(step.path.to_equipment, Switch)
37+
38+
def step_action(step: NetworkTraceStep, context: StepContext):
39+
"""Add to our list of equipment, and equipment stepped on during this trace"""
40+
found_equip.add(step.path.to_equipment.mrid)
41+
42+
state_operators = NetworkStateOperators.NORMAL
43+
44+
await (
45+
Tracing.network_trace(
46+
network_state_operators=state_operators,
47+
action_step_type=NetworkTraceActionType.ALL_STEPS
48+
).add_condition(state_operators.stop_at_open())
49+
.add_queue_condition(queue_condition)
50+
.add_step_action(step_action)
51+
.add_start_item(hv_acls)
52+
).run()
53+
54+
print(found_equip) # prints a list of all mRID's for all equipment in the isolation area.
55+
56+
57+
if __name__ == "__main__":
58+
asyncio.run(main(conductor_mrid='50434998', feeder_mrid='RW1292'))
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright 2025 Zeppelin Bend Pty Ltd
2+
#
3+
# This Source Code Form is subject to the terms of the Mozilla Public
4+
# License, v. 2.0. If a copy of the MPL was not distributed with this
5+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
7+
"""
8+
Example trace showing method to traverse between any 2 given `IdentifiableObject` and build a list of `ProtectedSwitch` objects found, if any
9+
"""
10+
11+
import asyncio
12+
import json
13+
from typing import Tuple, Type
14+
15+
from zepben.evolve import NetworkStateOperators, NetworkTraceActionType, NetworkTraceStep, StepContext, \
16+
NetworkConsumerClient, ProtectedSwitch, Recloser, LoadBreakSwitch, connect_with_token
17+
from zepben.evolve import Tracing
18+
from zepben.protobuf.nc.nc_requests_pb2 import INCLUDE_ENERGIZED_LV_FEEDERS
19+
20+
with open("config.json") as f:
21+
c = json.loads(f.read())
22+
23+
24+
async def main(mrids: Tuple[str, str], io_type: Type[ProtectedSwitch], feeder_mrid):
25+
channel = connect_with_token(host=c["host"], access_token=c["access_token"], rpc_port=c["rpc_port"])
26+
client = NetworkConsumerClient(channel)
27+
await client.get_equipment_container(feeder_mrid, include_energized_containers=INCLUDE_ENERGIZED_LV_FEEDERS)
28+
network = client.service
29+
30+
nodes = [network.get(_id) for _id in mrids]
31+
32+
state_operators = NetworkStateOperators.NORMAL
33+
34+
found_switch = set()
35+
found_node = []
36+
37+
def stop_condition(step: NetworkTraceStep, context: StepContext):
38+
"""if we encounter any of the equipment we have specified, we stop the trace and mark the `found_switch` list as valid"""
39+
if step.path.to_equipment in nodes:
40+
found_node.append(True)
41+
return True
42+
43+
def step_action(step: NetworkTraceStep, context: StepContext):
44+
"""Add any equipment matching the type passed in to the list, this list is invalid unless we trace onto our other node"""
45+
if isinstance(step.path.to_equipment, io_type):
46+
found_switch.add(step.path.to_equipment)
47+
48+
trace = (
49+
Tracing.network_trace(
50+
network_state_operators=state_operators,
51+
action_step_type=NetworkTraceActionType.ALL_STEPS
52+
).add_condition(state_operators.upstream())
53+
.add_stop_condition(stop_condition)
54+
.add_step_action(step_action)
55+
)
56+
57+
queue = iter(nodes)
58+
while not found_node: # run an upstream trace for every node specified until we encounter another specified node
59+
await trace.run(start=next(queue), can_stop_on_start_item=False)
60+
61+
all(map(print, found_switch)) # print the list of switches
62+
print(bool(found_switch)) # print whether we found what we were looking for
63+
64+
65+
if __name__ == "__main__":
66+
asyncio.run(main(mrids=('50735858', '66598892'), io_type=LoadBreakSwitch, feeder_mrid='RW1292'))
67+
asyncio.run(main(mrids=('50735858', '50295424'), io_type=Recloser, feeder_mrid='RW1292'))

0 commit comments

Comments
 (0)