-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
If a CP is reset during a transaction, the system will end up with an orphaned transaction that is not closed. This middleware closes any previous orphaned transactions for that CP, using the last seen meter value for tx.meter_stop
- Loading branch information
Keith Grennan
committed
Jul 28, 2023
1 parent
ed52db4
commit 630a36c
Showing
7 changed files
with
145 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import logging | ||
|
||
from ocpp.models import Transaction | ||
from ocpp.services.ocpp.base import OCPPMiddleware, OCPPRequest, OCPPResponse | ||
from ocpp.types.action import Action | ||
from ocpp.types.stop_reason import StopReason | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class OrphanedTransactionMiddleware(OCPPMiddleware): | ||
""" | ||
When a new transaction is started, close out any unclosed previous transactions for the same CP | ||
""" | ||
|
||
def handle(self, req: OCPPRequest) -> OCPPResponse: | ||
message = req.message | ||
assert Action(message.action) == Action.StartTransaction | ||
for orphaned_tx in Transaction.objects.filter( | ||
charge_point=message.charge_point, stopped_at__isnull=True | ||
): | ||
last_meter_value = ( | ||
orphaned_tx.metervalue_set.filter( | ||
measurand="Energy.Active.Import.Register" | ||
) | ||
.order_by("timestamp") | ||
.last() | ||
) | ||
meter_stop = last_meter_value.value if last_meter_value else 0 | ||
orphaned_tx.stop(StopReason.Other, meter_stop) | ||
logger.info( | ||
"Stopped orphaned transaction %s with meter value %d", | ||
orphaned_tx, | ||
meter_stop, | ||
) | ||
|
||
return self.next.handle(req) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
be/ocpp/tests/services/ocpp/automation/test_orphaned_transaction.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from datetime import timedelta | ||
from unittest.mock import patch | ||
|
||
from django.test import TestCase | ||
|
||
from ocpp.models import Message, Transaction | ||
from ocpp.services.charge_point_message_handler import ChargePointMessageHandler | ||
from ocpp.tests.factory import ChargePointFactory, TransactionFactory, MeterValueFactory | ||
from ocpp.utils.date import utc_now | ||
|
||
|
||
@patch( | ||
"ocpp.services.charge_point_service.ChargePointService.send_message_to_charge_point" | ||
) | ||
class OrphanedTransactionTest(TestCase): | ||
def setUp(self) -> None: | ||
self.charge_point = ChargePointFactory() | ||
|
||
def test_auto_remote_start(self, send_message_to_charge_point): | ||
orphaned_tx = TransactionFactory( | ||
charge_point=self.charge_point, started_at=utc_now() | ||
) | ||
MeterValueFactory( | ||
timestamp=utc_now() - timedelta(minutes=1), | ||
transaction=orphaned_tx, | ||
measurand="Energy.Active.Import.Register", | ||
value=5, | ||
) | ||
MeterValueFactory( | ||
timestamp=utc_now(), | ||
transaction=orphaned_tx, | ||
measurand="Energy.Active.Import.Register", | ||
value=10, | ||
) | ||
message = Message.from_occp( | ||
self.charge_point, | ||
dict( | ||
message=[ | ||
2, | ||
"x", | ||
"StartTransaction", | ||
{ | ||
"idTag": "x", | ||
"timestamp": "2023-03-30T01:58:52.001Z", | ||
"connectorId": 1, | ||
"meterStart": 0, | ||
}, | ||
] | ||
), | ||
) | ||
ChargePointMessageHandler.handle_message_from_charge_point(message) | ||
self.charge_point.refresh_from_db() | ||
orphaned_tx.refresh_from_db() | ||
assert orphaned_tx.stopped_at | ||
assert orphaned_tx.meter_stop == 10 | ||
|
||
# make sure it doesn't affect the new transaction | ||
assert Transaction.objects.filter( | ||
charge_point=self.charge_point, stopped_at__isnull=True, meter_stop=0 | ||
).exists() |