1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import asyncio
5
+ import json
6
+ import os .path
7
+ import platform
8
+ import shlex
9
+ import sys
10
+ import time
11
+ import uuid
12
+ from dataclasses import dataclass
13
+ from datetime import datetime , timedelta
14
+ from pathlib import Path
15
+ from typing import Any , Optional
16
+
17
+ sys .path .insert (0 , str (Path (__file__ ).parent .parent ))
18
+
19
+ from nicegui import background_tasks , ui
20
+ from session import backend_session , endpoint
21
+
22
+ import ieee_2030_5 .models as m
23
+ from ieee_2030_5 .utils import dataclass_to_xml , xml_to_dataclass
24
+
25
+ tasks = []
26
+
27
+ debug = False
28
+
29
+ def _get_active (program ):
30
+ resp = backend_session .get (endpoint (f"derp/{ program } /derca" ))
31
+ active_txt .value = resp .text
32
+
33
+ def _get_all_active ():
34
+ resp = backend_session .get (endpoint (f"derp" ))
35
+ derps : m .DERProgramList = xml_to_dataclass (resp .text )
36
+
37
+ value = ''
38
+ for index , derps in enumerate (derps .DERProgram ):
39
+ resp = backend_session .get (endpoint (f"derp/{ index } /derca" ))
40
+ active : m .DERControlList = xml_to_dataclass (resp .text )
41
+ if active :
42
+ value += f"\n Program { index } \n "
43
+ value += f"{ active } "
44
+ active_txt .value = value
45
+
46
+ def _submit_control_event (program , control ):
47
+ resp = backend_session .post (endpoint (f"derp/{ program } /derc" ),
48
+ data = control ,
49
+ headers = {"Content-Type" : "application/xml" })
50
+ _get_all_active ()
51
+
52
+ def _show_text_area (label , value , only_when_debug = True ):
53
+ if debug == only_when_debug :
54
+ ui .textarea (label = label , value = value ).props ('rows=20' ).props ('cols=120' ).classes ('w-full, h-80' )
55
+
56
+ def get_control_event_default ():
57
+ derbase = m .DERControlBase (opModConnect = True , opModEnergize = False , opModFixedPFInjectW = 80 )
58
+
59
+ time_plus_10 = int (time .mktime ((datetime .utcnow () + timedelta (seconds = 10 )).timetuple ()))
60
+
61
+ derc = m .DERControl (mRID = str (uuid .uuid4 ()),
62
+ description = "New DER Control Event" ,
63
+ DERControlBase = derbase ,
64
+ interval = m .DateTimeInterval (duration = 20 , start = time_plus_10 ))
65
+
66
+
67
+ # setESLowVolt=0.917,
68
+ # setESHighVolt=1.05,
69
+ # setESLowFreq=59.5,
70
+ # setESHighFreq=60.1,
71
+ # setESRampTms=300,
72
+ # setESRandomDelay=0,
73
+ #DERControlBase=derbase)
74
+ # dderc = m.DefaultDERControl(href=hrefs.get_dderc_href(),
75
+ # mRID=str(uuid.uuid4()),
76
+ # description="Default DER Control Mode",
77
+ # setESDelay=300,
78
+ # setESLowVolt=0.917,
79
+ # setESHighVolt=1.05,
80
+ # setESLowFreq=59.5,
81
+ # setESHighFreq=60.1,
82
+ # setESRampTms=300,
83
+ # setESRandomDelay=0,
84
+ # DERControlBase=derbase)
85
+ return dataclass_to_xml (derc )
86
+
87
+ resp = backend_session .get (endpoint ('derp' ))
88
+ derps : m .DERProgramList = xml_to_dataclass (resp .text )
89
+ resp = backend_session .get (endpoint ("enddevices" ))
90
+ enddevices : m .EndDeviceList = xml_to_dataclass (resp .text )
91
+
92
+ with_ders = filter (lambda x : x .DERListLink is not None , enddevices .EndDevice )
93
+
94
+ print ([x for x in with_ders ])
95
+
96
+ with ui .column ():
97
+ ui .label (f"# End Devices: { len (enddevices .EndDevice )} " )
98
+ _show_text_area ("enddevices" , dataclass_to_xml (enddevices ))
99
+ _show_text_area ("derps" , dataclass_to_xml (derps ))
100
+
101
+ ui .label (f"# Derps: { len (derps .DERProgram )} " )
102
+ ui .label ("FSA" )
103
+ for ed_index , ed in enumerate (enddevices .EndDevice ):
104
+ resp = backend_session .get (endpoint (f"edev/{ ed_index } /fsa" ))
105
+ fsalist :m .FunctionSetAssignmentsList = xml_to_dataclass (resp .text )
106
+ _show_text_area (f"edev/{ ed_index } /fsa" , resp .text )
107
+
108
+ for fsa_index , fsa in enumerate (fsalist .FunctionSetAssignments ):
109
+ resp = backend_session .get (endpoint (f"edev/{ ed_index } /fsa/{ fsa_index } " ))
110
+ _show_text_area (f"edev/{ ed_index } /fsa/{ fsa_index } " , resp .text )
111
+
112
+ resp = backend_session .get (endpoint (f"edev/{ ed_index } /fsa/{ fsa_index } /derp" ))
113
+ _show_text_area (f"edev/{ ed_index } /fsa/{ fsa_index } /derp" , resp .text )
114
+
115
+ select_list = {index : value .description for index , value in enumerate (derps .DERProgram )}
116
+
117
+ program = ui .select (options = select_list , value = list (select_list .keys ())[0 ]).classes ('w-full' )
118
+ xml_text = ui .textarea (label = "xml" , value = get_control_event_default ()).props ('rows=20' ).props ('cols=120' ).classes ('w-full, h-80' )
119
+
120
+ ui .button ("Assign DER Control Event" , on_click = lambda : _submit_control_event (program .value , xml_text .value )).props ('no-caps' )
121
+ ui .button ("Show Active" , on_click = lambda : _get_all_active ()).props ('no-caps' )
122
+
123
+ active_txt = ui .textarea (label = "Active" ).props ("rows=20" ).props ('cols=120' ).classes ('w-full, h-80' )
124
+ #ui.select(options=[d for d in with_ders])
125
+
126
+ # def add_my_task(task):
127
+ # tasks.append(task)
128
+
129
+
130
+ # def _send_control_event():
131
+ # default_pf = 0.99
132
+ # import requests
133
+ # session = requests.Session()
134
+ # session.cert = ('/home/os2004/tls/certs/admin.pem', '/home/os2004/tls/private/admin.pem')
135
+ # session.verify = "/home/os2004/tls/certs/ca.pem"
136
+
137
+ # control_path = Path('inverter.ctl')
138
+ # derc: m.DERControl = xml_to_dataclass(xml_text.value)
139
+
140
+
141
+ # time_now = int(time.mktime((datetime.utcnow()).timetuple()))
142
+
143
+ # while time_now < derc.interval.start:
144
+ # time.sleep(0.1)
145
+ # time_now = int(time.mktime((datetime.utcnow()).timetuple()))
146
+
147
+ # with open(str(control_path), 'wt') as fp:
148
+ # fp.write(json.dumps(dict(pf=derc.DERControlBase.opModFixedPFInjectW)))
149
+
150
+ # while time_now < derc.interval.start + derc.interval.duration:
151
+ # time.sleep(0.1)
152
+ # time_now = int(time.mktime((datetime.utcnow()).timetuple()))
153
+
154
+ # control_path.write(json.dump(dict(pf=default_pf)))
155
+
156
+
157
+
158
+
159
+
160
+ # def _setup_event(element):
161
+ # derbase = m.DERControlBase(opModConnect=True, opModEnergize=False, opModFixedPFInjectW=80)
162
+
163
+ # time_plus_60 = int(time.mktime((datetime.utcnow() + timedelta(seconds=60)).timetuple()))
164
+
165
+ # derc = m.DERControl(mRID=str(uuid.uuid4()),
166
+ # description="New DER Control Event",
167
+ # DERControlBase=derbase,
168
+ # interval=m.DateTimeInterval(duration=10, start=time_plus_60))
169
+ # element.value=dataclass_to_xml(derc)
170
+
171
+
172
+ # async def _reset_tasks():
173
+ # for task in tasks:
174
+ # print(task.cancel())
175
+
176
+ # await asyncio.sleep(0.1)
177
+
178
+ # # while not task.cancelled():
179
+ # # asyncio.sleep(0.1)
180
+ # # print(task.cancelled())
181
+
182
+
183
+ # tasks.clear()
184
+ # agent_log.clear()
185
+ # proxy_log.clear()
186
+ # inverter_log.clear()
187
+
188
+ # #background_tasks.running_tasks.clear()
189
+
190
+ # async def run_command(command: LabeledCommand) -> None:
191
+ # '''Run a command in the background and display the output in the pre-created dialog.'''
192
+
193
+ # process = await asyncio.create_subprocess_exec(
194
+ # *shlex.split(command.command),
195
+ # stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT,
196
+ # cwd=command.working_dir
197
+ # )
198
+
199
+ # # NOTE we need to read the output in chunks, otherwise the process will block
200
+ # output = ''
201
+ # while True:
202
+ # new = await process.stdout.readline()
203
+ # if not new:
204
+ # break
205
+ # output = new.decode()
206
+
207
+ # try:
208
+ # jsonparsed = json.loads(output)
209
+ # if command.output_element is not None:
210
+ # command.output_element().push(output.strip())
211
+ # except json.decoder.JSONDecodeError:
212
+ # if not command.output_only_json:
213
+ # command.output_element().push(output.strip())
214
+
215
+ # # NOTE the content of the markdown element is replaced every time we have new output
216
+ # #result.content = f'```\n{output}\n```'
217
+
218
+ # with ui.dialog() as dialog, ui.card():
219
+ # result = ui.markdown()
220
+
221
+ # @dataclass
222
+ # class LabeledCommand:
223
+ # label: str
224
+ # command: str
225
+ # output_element: Any
226
+ # working_dir: str = str(Path(__file__).parent)
227
+ # output_only_json: bool = True
228
+
229
+ # commands = [
230
+ # LabeledCommand("Start Inverter", f'{sys.executable} inverter_runner.py', lambda: inverter_log),
231
+ # LabeledCommand("Start Proxy", f'/home/os2004/repos/gridappsd-2030_5/.venv/bin/python -m ieee_2030_5.basic_proxy config.yml ', lambda: proxy_log, "/home/os2004/repos/gridappsd-2030_5",
232
+ # output_only_json=False),
233
+ # LabeledCommand("Start Agent", f'{sys.executable} -m ieee_2030_5.agent', lambda: agent_log, "/home/os2004/repos/volttron/services/core/IEEE_2030_5",
234
+ # output_only_json=False),
235
+ # ]
236
+
237
+ # with ui.column():
238
+ # # commands = [f'{sys.executable} inverter_runner.py']
239
+ # with ui.row():
240
+
241
+ # for command in commands:
242
+ # ui.button(command.label, on_click=lambda _, c=command: add_my_task(background_tasks.create(run_command(c)))).props('no-caps')
243
+
244
+ # ui.button("Reset", on_click=lambda: _reset_tasks()).props('no-caps')
245
+ # #ui.button("Update Control Time", on_click=lambda: _setup_event(xml_text)).props('no-caps')
246
+ # #ui.button("Send Control", on_click=lambda: _send_control_event()).props('no-caps')
247
+ # # with ui.row():
248
+ # # xml_text = ui.textarea(label="xml", value=get_control_event_default()).props('rows=20').props('cols=120').classes('w-full, h-80')
249
+ # with ui.row():
250
+ # ui.label("Inverter Log")
251
+ # inverter_log = ui.log(10).props('rows=5').props('cols=120').classes('w-full h-80')
252
+ # with ui.row():
253
+ # ui.label("Proxy Log")
254
+ # proxy_log = ui.log(10).props('rows=5').props('cols=120').classes('w-full h-80')
255
+ # with ui.row():
256
+ # ui.label("Agent Log")
257
+ # agent_log = ui.log(10).props('rows=5').props('cols=120').classes('w-full h-80')
258
+
259
+
260
+ # NOTE on windows reload must be disabled to make asyncio.create_subprocess_exec work (see https://github.com/zauberzeug/nicegui/issues/486)
261
+ ui .run (reload = platform .system () != "Windows" )
0 commit comments