check-my-irve/simulator/charge_point_sim.py

132 lines
5.4 KiB
Python

import asyncio
import websockets
import argparse
import random
from ocpp.routing import on
from ocpp.v16 import ChargePoint as cp
from ocpp.v16 import call
from ocpp.v16 import call_result
from ocpp.v16.enums import Action
class SimulatorChargePoint(cp):
def __init__(self, id, connection, vendor, model):
super().__init__(id, connection)
self.vendor = vendor
self.model = model
# --- Handlers pour les commandes CSMS -> Borne ---
@on(Action.GetConfiguration)
async def on_get_config(self, **kwargs):
return call_result.GetConfiguration(
configuration_key=[
{"key": "HeartbeatInterval", "value": "30", "readonly": False},
{"key": "WebSocketPingInterval", "value": "60", "readonly": False}
],
unknown_key=[]
)
@on(Action.RemoteStartTransaction)
async def on_remote_start(self, **kwargs):
return call_result.RemoteStartTransaction(status="Accepted")
@on(Action.RemoteStopTransaction)
async def on_remote_stop(self, **kwargs):
return call_result.RemoteStopTransaction(status="Accepted")
@on(Action.Reset)
async def on_reset(self, **kwargs):
return call_result.Reset(status="Accepted")
@on(Action.TriggerMessage)
async def on_trigger(self, **kwargs):
return call_result.TriggerMessage(status="Accepted")
@on(Action.ChangeConfiguration)
async def on_change_config(self, **kwargs):
return call_result.ChangeConfiguration(status="Accepted")
@on(Action.UnlockConnector)
async def on_unlock(self, **kwargs):
return call_result.UnlockConnector(status="Unlocked")
# --- Tâche de fond : Heartbeat ---
async def send_heartbeat(self):
while True:
await asyncio.sleep(30)
try:
await self.call(call.Heartbeat())
except websockets.exceptions.ConnectionClosed:
break
# --- Cycle de vie continu (Voitures qui arrivent et repartent) ---
async def run_lifecycle(self):
# 1. Boot initial
await self.call(call.BootNotification(charge_point_vendor=self.vendor, charge_point_model=self.model))
await self.call(call.StatusNotification(connector_id=1, error_code="NoError", status="Available"))
print(f"[{self.id}] Borne en ligne. En attente de véhicules...")
while True:
# Temps d'attente avant l'arrivée d'une voiture (5 à 15 secondes)
wait_time = random.randint(5, 15)
await asyncio.sleep(wait_time)
# 2. Arrivée d'un véhicule
await self.call(call.StatusNotification(connector_id=1, error_code="NoError", status="Preparing"))
auth = await self.call(call.Authorize(id_tag="CHARGEMAP_PASS"))
if auth.id_tag_info["status"] == "Accepted":
tx = await self.call(call.StartTransaction(
connector_id=1, id_tag="CHARGEMAP_PASS", meter_start=0,
timestamp="2026-08-11T14:00:00Z"
))
await self.call(call.StatusNotification(connector_id=1, error_code="NoError", status="Charging"))
# 3. Cycle de charge (3 à 6 envois de MeterValues)
consumption = 0
charge_cycles = random.randint(3, 6)
for _ in range(charge_cycles):
consumption += random.randint(500, 1500)
await self.call(call.MeterValues(
connector_id=1,
transaction_id=tx.transaction_id,
meter_value=[{"timestamp": "2026-08-11T14:00:00Z", "sampled_value": [{"value": str(consumption)}]}]
))
await asyncio.sleep(3) # Recharge de 3s
# 4. Départ du véhicule
await self.call(call.StopTransaction(
transaction_id=tx.transaction_id, meter_stop=consumption,
timestamp="2026-08-11T14:01:00Z"
))
await self.call(call.StatusNotification(connector_id=1, error_code="NoError", status="Finishing"))
await asyncio.sleep(2)
await self.call(call.StatusNotification(connector_id=1, error_code="NoError", status="Available"))
else:
# Badge refusé
await self.call(call.StatusNotification(connector_id=1, error_code="NoError", status="SuspendedEV"))
await asyncio.sleep(5)
await self.call(call.StatusNotification(connector_id=1, error_code="NoError", status="Available"))
async def main():
parser = argparse.ArgumentParser()
parser.add_argument("--url", default="ws://backend:8000/ocpp")
parser.add_argument("--cp-id", default="SIM-001")
parser.add_argument("--vendor", default="Tesla")
parser.add_argument("--model", default="Model3")
args = parser.parse_args()
full_url = f"{args.url}/{args.cp_id}"
print(f"Connexion à {full_url}...")
async with websockets.connect(full_url, subprotocols=["ocpp1.6"]) as ws:
cp = SimulatorChargePoint(args.cp_id, ws, args.vendor, args.model)
# On lance l'écoute CSMS, les Heartbeats et le cycle de vie en parallèle
await asyncio.gather(
cp.start(),
cp.send_heartbeat(),
cp.run_lifecycle()
)
if __name__ == "__main__":
asyncio.run(main())