arthexis 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of arthexis might be problematic. Click here for more details.
- {arthexis-0.1.11.dist-info → arthexis-0.1.13.dist-info}/METADATA +2 -2
- {arthexis-0.1.11.dist-info → arthexis-0.1.13.dist-info}/RECORD +50 -44
- config/asgi.py +15 -1
- config/celery.py +8 -1
- config/settings.py +49 -78
- config/settings_helpers.py +109 -0
- core/admin.py +293 -78
- core/apps.py +21 -0
- core/auto_upgrade.py +2 -2
- core/form_fields.py +75 -0
- core/models.py +203 -47
- core/reference_utils.py +1 -1
- core/release.py +42 -20
- core/system.py +6 -3
- core/tasks.py +92 -40
- core/tests.py +75 -1
- core/views.py +178 -29
- core/widgets.py +43 -0
- nodes/admin.py +583 -10
- nodes/apps.py +15 -0
- nodes/feature_checks.py +133 -0
- nodes/models.py +287 -49
- nodes/reports.py +411 -0
- nodes/tests.py +990 -42
- nodes/urls.py +1 -0
- nodes/utils.py +32 -0
- nodes/views.py +173 -5
- ocpp/admin.py +424 -17
- ocpp/consumers.py +630 -15
- ocpp/evcs.py +7 -94
- ocpp/evcs_discovery.py +158 -0
- ocpp/models.py +236 -4
- ocpp/routing.py +4 -2
- ocpp/simulator.py +346 -26
- ocpp/status_display.py +26 -0
- ocpp/store.py +110 -2
- ocpp/tests.py +1425 -33
- ocpp/transactions_io.py +27 -3
- ocpp/views.py +344 -38
- pages/admin.py +138 -3
- pages/context_processors.py +15 -1
- pages/defaults.py +1 -2
- pages/forms.py +67 -0
- pages/models.py +136 -1
- pages/tests.py +379 -4
- pages/urls.py +1 -0
- pages/views.py +64 -7
- {arthexis-0.1.11.dist-info → arthexis-0.1.13.dist-info}/WHEEL +0 -0
- {arthexis-0.1.11.dist-info → arthexis-0.1.13.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.11.dist-info → arthexis-0.1.13.dist-info}/top_level.txt +0 -0
ocpp/admin.py
CHANGED
|
@@ -11,12 +11,16 @@ from django.urls import path
|
|
|
11
11
|
from django.http import HttpResponse, HttpResponseRedirect
|
|
12
12
|
from django.template.response import TemplateResponse
|
|
13
13
|
|
|
14
|
+
import uuid
|
|
15
|
+
from asgiref.sync import async_to_sync
|
|
16
|
+
|
|
14
17
|
from .models import (
|
|
15
18
|
Charger,
|
|
16
19
|
Simulator,
|
|
17
20
|
MeterValue,
|
|
18
21
|
Transaction,
|
|
19
22
|
Location,
|
|
23
|
+
DataTransferMessage,
|
|
20
24
|
)
|
|
21
25
|
from .simulator import ChargePointSimulator
|
|
22
26
|
from . import store
|
|
@@ -24,6 +28,8 @@ from .transactions_io import (
|
|
|
24
28
|
export_transactions,
|
|
25
29
|
import_transactions as import_transactions_data,
|
|
26
30
|
)
|
|
31
|
+
from .status_display import STATUS_BADGE_MAP, ERROR_OK_VALUES
|
|
32
|
+
from core.admin import SaveBeforeChangeAction
|
|
27
33
|
from core.user_data import EntityModelAdmin
|
|
28
34
|
|
|
29
35
|
|
|
@@ -108,6 +114,44 @@ class LocationAdmin(EntityModelAdmin):
|
|
|
108
114
|
change_form_template = "admin/ocpp/location/change_form.html"
|
|
109
115
|
|
|
110
116
|
|
|
117
|
+
@admin.register(DataTransferMessage)
|
|
118
|
+
class DataTransferMessageAdmin(admin.ModelAdmin):
|
|
119
|
+
list_display = (
|
|
120
|
+
"charger",
|
|
121
|
+
"connector_id",
|
|
122
|
+
"direction",
|
|
123
|
+
"vendor_id",
|
|
124
|
+
"message_id",
|
|
125
|
+
"status",
|
|
126
|
+
"created_at",
|
|
127
|
+
"responded_at",
|
|
128
|
+
)
|
|
129
|
+
list_filter = ("direction", "status")
|
|
130
|
+
search_fields = (
|
|
131
|
+
"charger__charger_id",
|
|
132
|
+
"ocpp_message_id",
|
|
133
|
+
"vendor_id",
|
|
134
|
+
"message_id",
|
|
135
|
+
)
|
|
136
|
+
readonly_fields = (
|
|
137
|
+
"charger",
|
|
138
|
+
"connector_id",
|
|
139
|
+
"direction",
|
|
140
|
+
"ocpp_message_id",
|
|
141
|
+
"vendor_id",
|
|
142
|
+
"message_id",
|
|
143
|
+
"payload",
|
|
144
|
+
"status",
|
|
145
|
+
"response_data",
|
|
146
|
+
"error_code",
|
|
147
|
+
"error_description",
|
|
148
|
+
"error_details",
|
|
149
|
+
"responded_at",
|
|
150
|
+
"created_at",
|
|
151
|
+
"updated_at",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
111
155
|
@admin.register(Charger)
|
|
112
156
|
class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
113
157
|
fieldsets = (
|
|
@@ -122,6 +166,13 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
122
166
|
"last_path",
|
|
123
167
|
"last_heartbeat",
|
|
124
168
|
"last_meter_values",
|
|
169
|
+
)
|
|
170
|
+
},
|
|
171
|
+
),
|
|
172
|
+
(
|
|
173
|
+
"Firmware",
|
|
174
|
+
{
|
|
175
|
+
"fields": (
|
|
125
176
|
"firmware_status",
|
|
126
177
|
"firmware_status_info",
|
|
127
178
|
"firmware_timestamp",
|
|
@@ -138,6 +189,20 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
138
189
|
)
|
|
139
190
|
},
|
|
140
191
|
),
|
|
192
|
+
(
|
|
193
|
+
"Availability",
|
|
194
|
+
{
|
|
195
|
+
"fields": (
|
|
196
|
+
"availability_state",
|
|
197
|
+
"availability_state_updated_at",
|
|
198
|
+
"availability_requested_state",
|
|
199
|
+
"availability_requested_at",
|
|
200
|
+
"availability_request_status",
|
|
201
|
+
"availability_request_status_at",
|
|
202
|
+
"availability_request_details",
|
|
203
|
+
)
|
|
204
|
+
},
|
|
205
|
+
),
|
|
141
206
|
(
|
|
142
207
|
"Configuration",
|
|
143
208
|
{"fields": ("public_display", "require_rfid")},
|
|
@@ -148,6 +213,13 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
148
213
|
"fields": ("reference",),
|
|
149
214
|
},
|
|
150
215
|
),
|
|
216
|
+
(
|
|
217
|
+
"Owner",
|
|
218
|
+
{
|
|
219
|
+
"fields": ("owner_users", "owner_groups"),
|
|
220
|
+
"classes": ("collapse",),
|
|
221
|
+
},
|
|
222
|
+
),
|
|
151
223
|
)
|
|
152
224
|
readonly_fields = (
|
|
153
225
|
"last_heartbeat",
|
|
@@ -155,16 +227,21 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
155
227
|
"firmware_status",
|
|
156
228
|
"firmware_status_info",
|
|
157
229
|
"firmware_timestamp",
|
|
230
|
+
"availability_state",
|
|
231
|
+
"availability_state_updated_at",
|
|
232
|
+
"availability_requested_state",
|
|
233
|
+
"availability_requested_at",
|
|
234
|
+
"availability_request_status",
|
|
235
|
+
"availability_request_status_at",
|
|
236
|
+
"availability_request_details",
|
|
158
237
|
)
|
|
159
238
|
list_display = (
|
|
160
239
|
"charger_id",
|
|
161
|
-
"
|
|
240
|
+
"connector_number",
|
|
162
241
|
"location_name",
|
|
163
242
|
"require_rfid_display",
|
|
164
243
|
"public_display",
|
|
165
244
|
"last_heartbeat",
|
|
166
|
-
"firmware_status",
|
|
167
|
-
"firmware_timestamp",
|
|
168
245
|
"session_kw",
|
|
169
246
|
"total_kw_display",
|
|
170
247
|
"page_link",
|
|
@@ -172,7 +249,18 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
172
249
|
"status_link",
|
|
173
250
|
)
|
|
174
251
|
search_fields = ("charger_id", "connector_id", "location__name")
|
|
175
|
-
|
|
252
|
+
filter_horizontal = ("owner_users", "owner_groups")
|
|
253
|
+
actions = [
|
|
254
|
+
"purge_data",
|
|
255
|
+
"fetch_cp_configuration",
|
|
256
|
+
"change_availability_operative",
|
|
257
|
+
"change_availability_inoperative",
|
|
258
|
+
"set_availability_state_operative",
|
|
259
|
+
"set_availability_state_inoperative",
|
|
260
|
+
"remote_stop_transaction",
|
|
261
|
+
"reset_chargers",
|
|
262
|
+
"delete_selected",
|
|
263
|
+
]
|
|
176
264
|
|
|
177
265
|
def get_view_on_site_url(self, obj=None):
|
|
178
266
|
return obj.get_absolute_url() if obj else None
|
|
@@ -215,6 +303,12 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
215
303
|
def get_log_identifier(self, obj):
|
|
216
304
|
return store.identity_key(obj.charger_id, obj.connector_id)
|
|
217
305
|
|
|
306
|
+
def connector_number(self, obj):
|
|
307
|
+
return obj.connector_id if obj.connector_id is not None else ""
|
|
308
|
+
|
|
309
|
+
connector_number.short_description = "#"
|
|
310
|
+
connector_number.admin_order_field = "connector_id"
|
|
311
|
+
|
|
218
312
|
def status_link(self, obj):
|
|
219
313
|
from django.utils.html import format_html
|
|
220
314
|
from django.urls import reverse
|
|
@@ -223,10 +317,36 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
223
317
|
"charger-status-connector",
|
|
224
318
|
args=[obj.charger_id, obj.connector_slug],
|
|
225
319
|
)
|
|
226
|
-
|
|
320
|
+
label = (obj.last_status or "status").strip() or "status"
|
|
321
|
+
status_key = label.lower()
|
|
322
|
+
error_code = (obj.last_error_code or "").strip().lower()
|
|
323
|
+
if (
|
|
324
|
+
self._has_active_session(obj)
|
|
325
|
+
and error_code in ERROR_OK_VALUES
|
|
326
|
+
and (status_key not in STATUS_BADGE_MAP or status_key == "available")
|
|
327
|
+
):
|
|
328
|
+
label = STATUS_BADGE_MAP["charging"][0]
|
|
329
|
+
return format_html('<a href="{}" target="_blank">{}</a>', url, label)
|
|
227
330
|
|
|
228
331
|
status_link.short_description = "Status"
|
|
229
332
|
|
|
333
|
+
def _has_active_session(self, charger: Charger) -> bool:
|
|
334
|
+
"""Return whether ``charger`` currently has an active session."""
|
|
335
|
+
|
|
336
|
+
if store.get_transaction(charger.charger_id, charger.connector_id):
|
|
337
|
+
return True
|
|
338
|
+
if charger.connector_id is not None:
|
|
339
|
+
return False
|
|
340
|
+
sibling_connectors = (
|
|
341
|
+
Charger.objects.filter(charger_id=charger.charger_id)
|
|
342
|
+
.exclude(pk=charger.pk)
|
|
343
|
+
.values_list("connector_id", flat=True)
|
|
344
|
+
)
|
|
345
|
+
for connector_id in sibling_connectors:
|
|
346
|
+
if store.get_transaction(charger.charger_id, connector_id):
|
|
347
|
+
return True
|
|
348
|
+
return False
|
|
349
|
+
|
|
230
350
|
def location_name(self, obj):
|
|
231
351
|
return obj.location.name if obj.location else ""
|
|
232
352
|
|
|
@@ -239,6 +359,257 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
239
359
|
|
|
240
360
|
purge_data.short_description = "Purge data"
|
|
241
361
|
|
|
362
|
+
@admin.action(description="Fetch CP configuration")
|
|
363
|
+
def fetch_cp_configuration(self, request, queryset):
|
|
364
|
+
fetched = 0
|
|
365
|
+
for charger in queryset:
|
|
366
|
+
connector_value = charger.connector_id
|
|
367
|
+
ws = store.get_connection(charger.charger_id, connector_value)
|
|
368
|
+
if ws is None:
|
|
369
|
+
self.message_user(
|
|
370
|
+
request,
|
|
371
|
+
f"{charger}: no active connection",
|
|
372
|
+
level=messages.ERROR,
|
|
373
|
+
)
|
|
374
|
+
continue
|
|
375
|
+
message_id = uuid.uuid4().hex
|
|
376
|
+
payload = {}
|
|
377
|
+
msg = json.dumps([2, message_id, "GetConfiguration", payload])
|
|
378
|
+
try:
|
|
379
|
+
async_to_sync(ws.send)(msg)
|
|
380
|
+
except Exception as exc: # pragma: no cover - network error
|
|
381
|
+
self.message_user(
|
|
382
|
+
request,
|
|
383
|
+
f"{charger}: failed to send GetConfiguration ({exc})",
|
|
384
|
+
level=messages.ERROR,
|
|
385
|
+
)
|
|
386
|
+
continue
|
|
387
|
+
log_key = store.identity_key(charger.charger_id, connector_value)
|
|
388
|
+
store.add_log(log_key, f"< {msg}", log_type="charger")
|
|
389
|
+
store.register_pending_call(
|
|
390
|
+
message_id,
|
|
391
|
+
{
|
|
392
|
+
"action": "GetConfiguration",
|
|
393
|
+
"charger_id": charger.charger_id,
|
|
394
|
+
"connector_id": connector_value,
|
|
395
|
+
"log_key": log_key,
|
|
396
|
+
"requested_at": timezone.now(),
|
|
397
|
+
},
|
|
398
|
+
)
|
|
399
|
+
store.schedule_call_timeout(
|
|
400
|
+
message_id,
|
|
401
|
+
timeout=5.0,
|
|
402
|
+
action="GetConfiguration",
|
|
403
|
+
log_key=log_key,
|
|
404
|
+
message=(
|
|
405
|
+
"GetConfiguration timed out: charger did not respond"
|
|
406
|
+
" (operation may not be supported)"
|
|
407
|
+
),
|
|
408
|
+
)
|
|
409
|
+
fetched += 1
|
|
410
|
+
if fetched:
|
|
411
|
+
self.message_user(
|
|
412
|
+
request,
|
|
413
|
+
f"Requested configuration from {fetched} charger(s)",
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
def _dispatch_change_availability(self, request, queryset, availability_type: str):
|
|
417
|
+
sent = 0
|
|
418
|
+
for charger in queryset:
|
|
419
|
+
connector_value = charger.connector_id
|
|
420
|
+
ws = store.get_connection(charger.charger_id, connector_value)
|
|
421
|
+
if ws is None:
|
|
422
|
+
self.message_user(
|
|
423
|
+
request,
|
|
424
|
+
f"{charger}: no active connection",
|
|
425
|
+
level=messages.ERROR,
|
|
426
|
+
)
|
|
427
|
+
continue
|
|
428
|
+
connector_id = connector_value if connector_value is not None else 0
|
|
429
|
+
message_id = uuid.uuid4().hex
|
|
430
|
+
payload = {"connectorId": connector_id, "type": availability_type}
|
|
431
|
+
msg = json.dumps([2, message_id, "ChangeAvailability", payload])
|
|
432
|
+
try:
|
|
433
|
+
async_to_sync(ws.send)(msg)
|
|
434
|
+
except Exception as exc: # pragma: no cover - network error
|
|
435
|
+
self.message_user(
|
|
436
|
+
request,
|
|
437
|
+
f"{charger}: failed to send ChangeAvailability ({exc})",
|
|
438
|
+
level=messages.ERROR,
|
|
439
|
+
)
|
|
440
|
+
continue
|
|
441
|
+
log_key = store.identity_key(charger.charger_id, connector_value)
|
|
442
|
+
store.add_log(log_key, f"< {msg}", log_type="charger")
|
|
443
|
+
timestamp = timezone.now()
|
|
444
|
+
store.register_pending_call(
|
|
445
|
+
message_id,
|
|
446
|
+
{
|
|
447
|
+
"action": "ChangeAvailability",
|
|
448
|
+
"charger_id": charger.charger_id,
|
|
449
|
+
"connector_id": connector_value,
|
|
450
|
+
"availability_type": availability_type,
|
|
451
|
+
"requested_at": timestamp,
|
|
452
|
+
},
|
|
453
|
+
)
|
|
454
|
+
updates = {
|
|
455
|
+
"availability_requested_state": availability_type,
|
|
456
|
+
"availability_requested_at": timestamp,
|
|
457
|
+
"availability_request_status": "",
|
|
458
|
+
"availability_request_status_at": None,
|
|
459
|
+
"availability_request_details": "",
|
|
460
|
+
}
|
|
461
|
+
Charger.objects.filter(pk=charger.pk).update(**updates)
|
|
462
|
+
for field, value in updates.items():
|
|
463
|
+
setattr(charger, field, value)
|
|
464
|
+
sent += 1
|
|
465
|
+
if sent:
|
|
466
|
+
self.message_user(
|
|
467
|
+
request,
|
|
468
|
+
f"Sent ChangeAvailability ({availability_type}) to {sent} charger(s)",
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
@admin.action(description="Set availability to Operative")
|
|
472
|
+
def change_availability_operative(self, request, queryset):
|
|
473
|
+
self._dispatch_change_availability(request, queryset, "Operative")
|
|
474
|
+
|
|
475
|
+
@admin.action(description="Set availability to Inoperative")
|
|
476
|
+
def change_availability_inoperative(self, request, queryset):
|
|
477
|
+
self._dispatch_change_availability(request, queryset, "Inoperative")
|
|
478
|
+
|
|
479
|
+
def _set_availability_state(
|
|
480
|
+
self, request, queryset, availability_state: str
|
|
481
|
+
) -> None:
|
|
482
|
+
timestamp = timezone.now()
|
|
483
|
+
updated = 0
|
|
484
|
+
for charger in queryset:
|
|
485
|
+
updates = {
|
|
486
|
+
"availability_state": availability_state,
|
|
487
|
+
"availability_state_updated_at": timestamp,
|
|
488
|
+
}
|
|
489
|
+
Charger.objects.filter(pk=charger.pk).update(**updates)
|
|
490
|
+
for field, value in updates.items():
|
|
491
|
+
setattr(charger, field, value)
|
|
492
|
+
updated += 1
|
|
493
|
+
if updated:
|
|
494
|
+
self.message_user(
|
|
495
|
+
request,
|
|
496
|
+
f"Updated availability to {availability_state} for {updated} charger(s)",
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
@admin.action(description="Mark availability as Operative")
|
|
500
|
+
def set_availability_state_operative(self, request, queryset):
|
|
501
|
+
self._set_availability_state(request, queryset, "Operative")
|
|
502
|
+
|
|
503
|
+
@admin.action(description="Mark availability as Inoperative")
|
|
504
|
+
def set_availability_state_inoperative(self, request, queryset):
|
|
505
|
+
self._set_availability_state(request, queryset, "Inoperative")
|
|
506
|
+
|
|
507
|
+
@admin.action(description="Remote stop active transaction")
|
|
508
|
+
def remote_stop_transaction(self, request, queryset):
|
|
509
|
+
stopped = 0
|
|
510
|
+
for charger in queryset:
|
|
511
|
+
connector_value = charger.connector_id
|
|
512
|
+
ws = store.get_connection(charger.charger_id, connector_value)
|
|
513
|
+
if ws is None:
|
|
514
|
+
self.message_user(
|
|
515
|
+
request,
|
|
516
|
+
f"{charger}: no active connection",
|
|
517
|
+
level=messages.ERROR,
|
|
518
|
+
)
|
|
519
|
+
continue
|
|
520
|
+
tx_obj = store.get_transaction(charger.charger_id, connector_value)
|
|
521
|
+
if tx_obj is None:
|
|
522
|
+
self.message_user(
|
|
523
|
+
request,
|
|
524
|
+
f"{charger}: no active transaction",
|
|
525
|
+
level=messages.ERROR,
|
|
526
|
+
)
|
|
527
|
+
continue
|
|
528
|
+
message_id = uuid.uuid4().hex
|
|
529
|
+
payload = {"transactionId": tx_obj.pk}
|
|
530
|
+
msg = json.dumps([
|
|
531
|
+
2,
|
|
532
|
+
message_id,
|
|
533
|
+
"RemoteStopTransaction",
|
|
534
|
+
payload,
|
|
535
|
+
])
|
|
536
|
+
try:
|
|
537
|
+
async_to_sync(ws.send)(msg)
|
|
538
|
+
except Exception as exc: # pragma: no cover - network error
|
|
539
|
+
self.message_user(
|
|
540
|
+
request,
|
|
541
|
+
f"{charger}: failed to send RemoteStopTransaction ({exc})",
|
|
542
|
+
level=messages.ERROR,
|
|
543
|
+
)
|
|
544
|
+
continue
|
|
545
|
+
log_key = store.identity_key(charger.charger_id, connector_value)
|
|
546
|
+
store.add_log(log_key, f"< {msg}", log_type="charger")
|
|
547
|
+
store.register_pending_call(
|
|
548
|
+
message_id,
|
|
549
|
+
{
|
|
550
|
+
"action": "RemoteStopTransaction",
|
|
551
|
+
"charger_id": charger.charger_id,
|
|
552
|
+
"connector_id": connector_value,
|
|
553
|
+
"transaction_id": tx_obj.pk,
|
|
554
|
+
"log_key": log_key,
|
|
555
|
+
"requested_at": timezone.now(),
|
|
556
|
+
},
|
|
557
|
+
)
|
|
558
|
+
stopped += 1
|
|
559
|
+
if stopped:
|
|
560
|
+
self.message_user(
|
|
561
|
+
request,
|
|
562
|
+
f"Sent RemoteStopTransaction to {stopped} charger(s)",
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
@admin.action(description="Reset charger (soft)")
|
|
566
|
+
def reset_chargers(self, request, queryset):
|
|
567
|
+
reset = 0
|
|
568
|
+
for charger in queryset:
|
|
569
|
+
connector_value = charger.connector_id
|
|
570
|
+
ws = store.get_connection(charger.charger_id, connector_value)
|
|
571
|
+
if ws is None:
|
|
572
|
+
self.message_user(
|
|
573
|
+
request,
|
|
574
|
+
f"{charger}: no active connection",
|
|
575
|
+
level=messages.ERROR,
|
|
576
|
+
)
|
|
577
|
+
continue
|
|
578
|
+
message_id = uuid.uuid4().hex
|
|
579
|
+
msg = json.dumps([
|
|
580
|
+
2,
|
|
581
|
+
message_id,
|
|
582
|
+
"Reset",
|
|
583
|
+
{"type": "Soft"},
|
|
584
|
+
])
|
|
585
|
+
try:
|
|
586
|
+
async_to_sync(ws.send)(msg)
|
|
587
|
+
except Exception as exc: # pragma: no cover - network error
|
|
588
|
+
self.message_user(
|
|
589
|
+
request,
|
|
590
|
+
f"{charger}: failed to send Reset ({exc})",
|
|
591
|
+
level=messages.ERROR,
|
|
592
|
+
)
|
|
593
|
+
continue
|
|
594
|
+
log_key = store.identity_key(charger.charger_id, connector_value)
|
|
595
|
+
store.add_log(log_key, f"< {msg}", log_type="charger")
|
|
596
|
+
store.register_pending_call(
|
|
597
|
+
message_id,
|
|
598
|
+
{
|
|
599
|
+
"action": "Reset",
|
|
600
|
+
"charger_id": charger.charger_id,
|
|
601
|
+
"connector_id": connector_value,
|
|
602
|
+
"log_key": log_key,
|
|
603
|
+
"requested_at": timezone.now(),
|
|
604
|
+
},
|
|
605
|
+
)
|
|
606
|
+
reset += 1
|
|
607
|
+
if reset:
|
|
608
|
+
self.message_user(
|
|
609
|
+
request,
|
|
610
|
+
f"Sent Reset to {reset} charger(s)",
|
|
611
|
+
)
|
|
612
|
+
|
|
242
613
|
def delete_queryset(self, request, queryset):
|
|
243
614
|
for obj in queryset:
|
|
244
615
|
obj.delete()
|
|
@@ -258,7 +629,7 @@ class ChargerAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
258
629
|
|
|
259
630
|
|
|
260
631
|
@admin.register(Simulator)
|
|
261
|
-
class SimulatorAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
632
|
+
class SimulatorAdmin(SaveBeforeChangeAction, LogViewAdminMixin, EntityModelAdmin):
|
|
262
633
|
list_display = (
|
|
263
634
|
"name",
|
|
264
635
|
"cp_path",
|
|
@@ -270,17 +641,39 @@ class SimulatorAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
270
641
|
"running",
|
|
271
642
|
"log_link",
|
|
272
643
|
)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
644
|
+
fieldsets = (
|
|
645
|
+
(
|
|
646
|
+
None,
|
|
647
|
+
{
|
|
648
|
+
"fields": (
|
|
649
|
+
"name",
|
|
650
|
+
"cp_path",
|
|
651
|
+
("host", "ws_port"),
|
|
652
|
+
"rfid",
|
|
653
|
+
("duration", "interval", "pre_charge_delay"),
|
|
654
|
+
"kw_max",
|
|
655
|
+
("repeat", "door_open"),
|
|
656
|
+
("username", "password"),
|
|
657
|
+
)
|
|
658
|
+
},
|
|
659
|
+
),
|
|
660
|
+
(
|
|
661
|
+
"Configuration",
|
|
662
|
+
{
|
|
663
|
+
"fields": (
|
|
664
|
+
"configuration_keys",
|
|
665
|
+
"configuration_unknown_keys",
|
|
666
|
+
),
|
|
667
|
+
"classes": ("collapse",),
|
|
668
|
+
"description": (
|
|
669
|
+
"Provide JSON lists for configurationKey entries and "
|
|
670
|
+
"unknownKey values returned by GetConfiguration."
|
|
671
|
+
),
|
|
672
|
+
},
|
|
673
|
+
),
|
|
282
674
|
)
|
|
283
675
|
actions = ("start_simulator", "stop_simulator", "send_open_door")
|
|
676
|
+
change_actions = ["start_simulator_action", "stop_simulator_action"]
|
|
284
677
|
|
|
285
678
|
log_type = "simulator"
|
|
286
679
|
|
|
@@ -382,11 +775,25 @@ class SimulatorAdmin(LogViewAdminMixin, EntityModelAdmin):
|
|
|
382
775
|
if sim:
|
|
383
776
|
await sim.stop()
|
|
384
777
|
|
|
385
|
-
|
|
778
|
+
objs = list(queryset)
|
|
779
|
+
try:
|
|
780
|
+
loop = asyncio.get_running_loop()
|
|
781
|
+
except RuntimeError:
|
|
782
|
+
asyncio.run(_stop(objs))
|
|
783
|
+
else:
|
|
784
|
+
loop.create_task(_stop(objs))
|
|
386
785
|
self.message_user(request, "Stopping simulators")
|
|
387
786
|
|
|
388
787
|
stop_simulator.short_description = "Stop selected simulators"
|
|
389
788
|
|
|
789
|
+
def start_simulator_action(self, request, obj):
|
|
790
|
+
queryset = type(obj).objects.filter(pk=obj.pk)
|
|
791
|
+
self.start_simulator(request, queryset)
|
|
792
|
+
|
|
793
|
+
def stop_simulator_action(self, request, obj):
|
|
794
|
+
queryset = type(obj).objects.filter(pk=obj.pk)
|
|
795
|
+
self.stop_simulator(request, queryset)
|
|
796
|
+
|
|
390
797
|
def log_link(self, obj):
|
|
391
798
|
from django.utils.html import format_html
|
|
392
799
|
from django.urls import reverse
|
|
@@ -431,7 +838,7 @@ class TransactionAdmin(EntityModelAdmin):
|
|
|
431
838
|
"stop_time",
|
|
432
839
|
"kw",
|
|
433
840
|
)
|
|
434
|
-
readonly_fields = ("kw",)
|
|
841
|
+
readonly_fields = ("kw", "received_start_time", "received_stop_time")
|
|
435
842
|
list_filter = ("charger", "account")
|
|
436
843
|
date_hierarchy = "start_time"
|
|
437
844
|
inlines = [MeterValueInline]
|