Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .env.evcc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# General Settings
NETWORK_INTERFACE=eth1
NETWORK_INTERFACE=acccs_evcc
ENABLE_TLS_1_3=True
ENABLE_NMAP=False

Expand All @@ -15,4 +15,7 @@ EVCCID=1FMVAA45B63C47DD58Y6

# NMAP Settings
NMAP_ARGS="-sS -sU -6"
NMAP_PORTS="-"
NMAP_PORTS="-"

# Virtual Testing
VIRTUAL=True
7 changes: 5 additions & 2 deletions .env.secc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# General Settings
NETWORK_INTERFACE=eth2
NETWORK_INTERFACE=acccs_secc
ENABLE_TLS_1_3=True
ENABLE_NMAP=False

Expand All @@ -20,4 +20,7 @@ EVSEID=USFRDE8326

# NMAP Settings
NMAP_ARGS="-sS -sU -6"
NMAP_PORTS="-"
NMAP_PORTS="-"

# Virtual Testing
VIRTUAL=True
9 changes: 7 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
[submodule "external_libs/HomePlugPWN"]
path = external_libs/HomePlugPWN
url = git@github.com:JakeMG-INL/HomePlugPWN.git
branch = minimal-libs
url = https://github.com/JakeMG-INL/HomePlugPWN.git
branch = minimal-libs

[submodule "external_libs/EXPy"]
path = external_libs/EXPy
url = https://github.com/IdahoLabResearch/EXPy.git
branch = main
34 changes: 16 additions & 18 deletions app/evcc/comm_session_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,11 +428,11 @@ async def start_comm_session(self, host: IPv6Address, port: int, is_tls: bool):
)
logger.info("TCP client connected")
except Exception as exc:
logger.exception(
logger.error(
f"{exc.__class__.__name__} when trying to connect "
f"to host {host} and port {port}"
)
return
raise exc

if shared_settings[SettingKey.ENABLE_NMAP]:
self.nmap_scanner = threading.Thread(target=self.port_scan, args=(host,), name='nmap_scanner')
Expand All @@ -456,12 +456,12 @@ async def start_comm_session(self, host: IPv6Address, port: int, is_tls: bool):
comm_session.start(Timeouts.SUPPORTED_APP_PROTOCOL_REQ)
)
except MessageProcessingError as exc:
logger.exception(
logger.error(
f"{exc.__class__.__name__} occurred while trying to "
f"create create an SDPRequest"
f"create an SupportedAppProtocolReq message"
)
return
raise exc

def port_scan(self, host):

if not os.path.isdir("scan_results"):
Expand Down Expand Up @@ -502,12 +502,8 @@ async def process_incoming_udp_packet(self, message: UDPPacketNotification):
sdp_response = SDPResponse.from_payload(v2gtp_msg.payload)
except InvalidSDPResponseError as exc:
logger.error(exc)
try:
await self.restart_sdp(True)
return
except SDPFailedError as exc:
logger.exception(exc)
return # TODO check if this is correct here
await self.restart_sdp(True)
return

logger.info(f"SDPResponse received: {sdp_response}")

Expand Down Expand Up @@ -553,11 +549,7 @@ async def process_incoming_udp_packet(self, message: UDPPacketNotification):
f"Incoming datagram of {len(message)} bytes is no "
f"valid SDPResponse message"
)
try:
await self.restart_sdp(True)
except SDPFailedError as exc:
logger.exception(exc)
return # TODO check if this is correct here
await self.restart_sdp(True)
return

await self.start_comm_session(host, port, secc_signals_tls)
Expand All @@ -577,12 +569,17 @@ async def get_from_rcv_queue(self, queue: asyncio.Queue):

try:
if isinstance(notification, UDPPacketNotification):
await self.process_incoming_udp_packet(notification)
try:
await self.process_incoming_udp_packet(notification)
except Exception as exc:
logger.exception(exc)
break
elif isinstance(notification, ReceiveTimeoutNotification):
try:
await self.restart_sdp(False)
except SDPFailedError as exc:
logger.exception(exc)
break
# TODO not sure what else to do here
elif isinstance(notification, StopNotification):
await cancel_task(self.comm_session[1])
Expand All @@ -596,6 +593,7 @@ async def get_from_rcv_queue(self, queue: asyncio.Queue):
await self.restart_sdp(True)
except SDPFailedError as exc:
logger.exception(exc)
break
# TODO not sure what else to do here
else:
logger.warning(
Expand Down
8 changes: 5 additions & 3 deletions app/evcc/controller/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,8 @@ async def is_bulk_charging_complete(self) -> bool:
raise NotImplementedError

@abstractmethod
async def get_remaining_time_to_full_soc(self) -> PVRemainingTimeToFullSOC:
async def get_remaining_time_to_full_soc(
self, protocol: Protocol) -> PVRemainingTimeToFullSOC:
"""
Gets the remaining time until full soc is reached.

Expand Down Expand Up @@ -611,7 +612,8 @@ async def get_target_voltage(self) -> RationalNumber:
"""
raise NotImplementedError

async def get_remaining_time_to_bulk_soc(self) -> PVRemainingTimeToBulkSOC:
async def get_remaining_time_to_bulk_soc(
self, protocol: Protocol) -> PVRemainingTimeToBulkSOC:
"""
Gets the remaining time until bulk soc is reached.

Expand Down Expand Up @@ -666,7 +668,7 @@ async def get_dc_ev_power_delivery_parameter(self) -> DCEVPowerDeliveryParameter
raise NotImplementedError

@abstractmethod
async def get_dc_charge_params(self) -> DCEVChargeParams:
async def get_dc_charge_params(self, protocol: Protocol) -> DCEVChargeParams:
"""
This would return an encapsulation of the following parameters:
DC Max Current Limit
Expand Down
81 changes: 53 additions & 28 deletions app/evcc/controller/pev.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
import json
import logging

from smbus import SMBus

from app.shared.EmulatorEnum import RunMode, PEVState

from app.evcc.transport.slac import SLACHandler
Expand Down Expand Up @@ -42,42 +40,60 @@ def __init__(self, args):
self.sourceMAC = get_nic_mac_address(self.iface)
self.sourceIP = str(get_link_local_addr(self.iface))
self.sourcePort = args.source_port[0] if args.source_port else get_tcp_port()
self.protocols = args.protocols.split(",") if args.protocols else ["ISO_15118_2", "DIN_SPEC_70121"]
self.authModes = args.authmodes.split(",") if args.authmodes else ["PNC", "EIM"]
self.energyMode = args.energymode if args.energymode else "DC"
self.useTLS = args.useTLS if args.useTLS else "True"
self.protocols = args.protocols.split(",") if args.protocols else None
self.authModes = args.authmodes.split(",") if args.authmodes else None
self.energyMode = args.energymode if args.energymode else None
self.useTLS = args.useTLS if args.useTLS else None
self.slacSoundTimeout = args.slacSoundTimeout if args.slacSoundTimeout else 1000

self.destinationMAC = None
self.destinationIP = None
self.destinationPort = None
self.slac = None

self.virtual = self.config.virtual

if not self.virtual:
from smbus import SMBus

# I2C bus for relays
self.bus = SMBus(1)
# I2C bus for relays
self.bus = SMBus(1)

# Constants for i2c controlled relays
self.I2C_ADDR = 0x20
self.CONTROL_REG = 0x9
self.PEV_CP1 = 0b10
self.PEV_CP2 = 0b100
self.PEV_PP = 0b10000
self.ALL_OFF = 0b0
# Constants for i2c controlled relays
self.I2C_ADDR = 0x20
self.CONTROL_REG = 0x9
self.PEV_CP1 = 0b10
self.PEV_CP2 = 0b100
self.PEV_PP = 0b10000
self.ALL_OFF = 0b0

async def start(self):
# Initialize the smbus for I2C commands
self.bus.write_byte_data(self.I2C_ADDR, 0x00, 0x00)
self.toggleProximity()
if not self.virtual:
# Initialize the smbus for I2C commands
self.bus.write_byte_data(self.I2C_ADDR, 0x00, 0x00)
self.toggleProximity()

evcc_config = {
"supportedProtocols": self.protocols,
"supportedAuthModes": self.authModes,
"supportedEnergyServices": [self.energyMode],
"useTls": self.useTLS,
}
with open(self.config.ev_config_file_path, "r") as f:
evcc_config_data = json.load(f)

if self.protocols:
evcc_config_data["supportedProtocols"] = self.protocols
if self.authModes:
evcc_config_data["supportedAuthModes"] = self.authModes
if self.energyMode:
evcc_config_data["supportedEnergyServices"] = [self.energyMode]
if self.useTLS:
if self.useTLS.lower() == "true":
evcc_config_data["useTls"] = True
elif self.useTLS.lower() == "false":
evcc_config_data["useTls"] = False
else:
logger.warning(f"Invalid value for useTLS: {self.useTLS}. "
f"Should be 'true' or 'false'. Defaulting to the .env value.")

self.config.ev_config_file_path = "app/shared/examples/evcc/evcc_settings.json"
with open(self.config.ev_config_file_path, "w") as f:
json.dump(evcc_config, f, indent=4)
json.dump(evcc_config_data, f, indent=4)

evcc_config = await load_from_file(self.config.ev_config_file_path)
self.slac = SLACHandler(self)
Expand Down Expand Up @@ -107,13 +123,22 @@ def openProximity(self):
def setState(self, state: PEVState):
if state == PEVState.A:
logger.info("Going to state A")
self.bus.write_byte_data(self.I2C_ADDR, self.CONTROL_REG, self.ALL_OFF)
if self.virtual:
return
else:
self.bus.write_byte_data(self.I2C_ADDR, self.CONTROL_REG, self.ALL_OFF)
elif state == PEVState.B:
logger.info("Going to state B")
self.bus.write_byte_data(self.I2C_ADDR, self.CONTROL_REG, self.PEV_PP | self.PEV_CP1)
if self.virtual:
return
else:
self.bus.write_byte_data(self.I2C_ADDR, self.CONTROL_REG, self.PEV_PP | self.PEV_CP1)
elif state == PEVState.C:
logger.info("Going to state C")
self.bus.write_byte_data(self.I2C_ADDR, self.CONTROL_REG, self.PEV_PP | self.PEV_CP1 | self.PEV_CP2)
if self.virtual:
return
else:
self.bus.write_byte_data(self.I2C_ADDR, self.CONTROL_REG, self.PEV_PP | self.PEV_CP1 | self.PEV_CP2)

def toggleProximity(self, t: int = 5):
self.openProximity()
Expand Down
57 changes: 51 additions & 6 deletions app/evcc/controller/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
PVPMax,
PVRemainingTimeToBulkSOC,
PVRemainingTimeToFullSOC,
PVEVMaxCurrentLimitDin,
PVEVMaxPowerLimitDin,
PVEVMaxVoltageLimitDin,
PVEVEnergyCapacityDin,
PVEVTargetCurrentDin,
PVEVTargetVoltageDin,
PVRemainingTimeToBulkSOCDin,
PVRemainingTimeToFullSOCDin,
)
from app.shared.messages.din_spec.datatypes import (
DCEVPowerDeliveryParameter as DCEVPowerDeliveryParameterDINSPEC,
Expand Down Expand Up @@ -593,7 +601,11 @@ async def is_precharged(
async def get_dc_ev_power_delivery_parameter_dinspec(
self,
) -> DCEVPowerDeliveryParameterDINSPEC:
pass
return DCEVPowerDeliveryParameterDINSPEC(
dc_ev_status=await self.get_dc_ev_status_dinspec(),
bulk_charging_complete=False,
charging_complete=await self.continue_charging(),
)

async def get_dc_ev_power_delivery_parameter(self) -> DCEVPowerDeliveryParameter:
return DCEVPowerDeliveryParameter(
Expand All @@ -611,11 +623,19 @@ async def is_charging_complete(self) -> bool:
else:
return False

async def get_remaining_time_to_full_soc(self) -> PVRemainingTimeToFullSOC:
return PVRemainingTimeToFullSOC(multiplier=0, value=100, unit="s")
async def get_remaining_time_to_full_soc(
self, protocol: Protocol) -> PVRemainingTimeToFullSOC:
if protocol == Protocol.DIN_SPEC_70121:
return PVRemainingTimeToFullSOCDin(multiplier=0, value=100, unit="s")
else:
return PVRemainingTimeToFullSOC(multiplier=0, value=100, unit="s")

async def get_remaining_time_to_bulk_soc(self) -> PVRemainingTimeToBulkSOC:
return PVRemainingTimeToBulkSOC(multiplier=0, value=80, unit="s")
async def get_remaining_time_to_bulk_soc(
self, protocol: Protocol) -> PVRemainingTimeToBulkSOC:
if protocol == Protocol.DIN_SPEC_70121:
return PVRemainingTimeToBulkSOCDin(multiplier=0, value=80, unit="s")
else:
return PVRemainingTimeToBulkSOC(multiplier=0, value=80, unit="s")

async def welding_detection_has_finished(self):
if self.welding_detection_cycles == 3:
Expand Down Expand Up @@ -674,8 +694,33 @@ async def get_ac_charge_loop_params_v20(
# | DC-SPECIFIC FUNCTIONS |
# ============================================================================

async def get_dc_charge_params(self) -> DCEVChargeParams:
async def get_dc_charge_params(self, protocol: Protocol) -> DCEVChargeParams:
"""Applies to both DIN SPEC and 15118-2"""
if protocol not in (Protocol.ISO_15118_2, Protocol.DIN_SPEC_70121):
logger.error(
f"Invalid protocol '{protocol}' for DC charge params, "
"expected ISO 15118-2 or DIN SPEC 70121"
)
raise InvalidProtocolError
elif protocol == Protocol.DIN_SPEC_70121:
self.dc_ev_charge_params.dc_max_current_limit = PVEVMaxCurrentLimitDin(
multiplier=-3, value=32000, unit=UnitSymbol.AMPERE
)
self.dc_ev_charge_params.dc_max_power_limit = PVEVMaxPowerLimitDin(
multiplier=1, value=8000, unit=UnitSymbol.WATT
)
self.dc_ev_charge_params.dc_max_voltage_limit = PVEVMaxVoltageLimitDin(
multiplier=1, value=50, unit=UnitSymbol.VOLTAGE
)
self.dc_ev_charge_params.dc_energy_capacity = PVEVEnergyCapacityDin(
multiplier=1, value=7000, unit=UnitSymbol.WATT_HOURS
)
self.dc_ev_charge_params.dc_target_current = PVEVTargetCurrentDin(
multiplier=0, value=1, unit=UnitSymbol.AMPERE
)
self.dc_ev_charge_params.dc_target_voltage = PVEVTargetVoltageDin(
multiplier=1, value=50, unit=UnitSymbol.VOLTAGE
)
return self.dc_ev_charge_params

async def get_dc_ev_status_dinspec(self) -> DCEVStatusDINSPEC:
Expand Down
4 changes: 4 additions & 0 deletions app/evcc/evcc_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Config:
console_log_level: Optional[str] = None
file_log_level: Optional[str] = None
ev_config_file_path: str = None
virtual: bool = False

def load_envs(self, env_path: Optional[str] = None) -> None:
"""
Expand Down Expand Up @@ -50,6 +51,9 @@ def load_envs(self, env_path: Optional[str] = None) -> None:
default="app/shared/examples/evcc/iso15118_2/evcc_config_eim_ac.json", # noqa
)
)

self.virtual = env.bool("VIRTUAL", default=False)

env.seal() # raise all errors at once, if any
load_shared_settings()
logger.info("EVCC environment settings:")
Expand Down
Loading