Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
5
tools/EVerest-main/modules/EV/PyEvJosev/BUILD.bazel
Normal file
5
tools/EVerest-main/modules/EV/PyEvJosev/BUILD.bazel
Normal file
@@ -0,0 +1,5 @@
|
||||
load("//modules:module.bzl", "py_everest_module")
|
||||
|
||||
py_everest_module(
|
||||
name = "PyEvJosev",
|
||||
)
|
||||
70
tools/EVerest-main/modules/EV/PyEvJosev/manifest.yaml
Normal file
70
tools/EVerest-main/modules/EV/PyEvJosev/manifest.yaml
Normal file
@@ -0,0 +1,70 @@
|
||||
description: >-
|
||||
This module implements an DIN70121, ISO15118-2 and ISO15118-20 EV using the Josev project.
|
||||
config:
|
||||
device:
|
||||
description: >-
|
||||
Ethernet device used for HLC. Any local interface that has an ipv6 link-local and a MAC addr will work.
|
||||
type: string
|
||||
default: eth0
|
||||
supported_DIN70121:
|
||||
description: The EV supports the DIN SPEC
|
||||
type: boolean
|
||||
default: false
|
||||
supported_ISO15118_2:
|
||||
description: The EV supports ISO15118-2
|
||||
type: boolean
|
||||
default: false
|
||||
supported_ISO15118_20_AC:
|
||||
description: The EV supports ISO15118-20 AC
|
||||
type: boolean
|
||||
default: false
|
||||
supported_ISO15118_20_DC:
|
||||
description: The EV supports ISO15118-20 DC
|
||||
type: boolean
|
||||
default: false
|
||||
tls_active:
|
||||
description: If true, EVCC connects to SECC as TLS client
|
||||
type: boolean
|
||||
default: false
|
||||
enforce_tls:
|
||||
description: The EVCC will enforce a TLS connection
|
||||
type: boolean
|
||||
default: false
|
||||
is_cert_install_needed:
|
||||
description: >-
|
||||
If true, the contract certificate will be installed via the evse.
|
||||
And any existing contract certificate will also be overwritten.
|
||||
type: boolean
|
||||
default: false
|
||||
enable_tls_1_3:
|
||||
description: The EVCC will enable TLS version 1.3
|
||||
type: boolean
|
||||
default: false
|
||||
is_internet_service_needed:
|
||||
description: If true, the ev will ask for internet service
|
||||
type: boolean
|
||||
default: false
|
||||
request_all_service_details:
|
||||
description: If true, the ev will ask for details about all offered services
|
||||
type: boolean
|
||||
default: false
|
||||
select_all_vas_services:
|
||||
description: If true, the ev will select all offered services
|
||||
type: boolean
|
||||
default: false
|
||||
supported_d20_energy_services:
|
||||
description: >-
|
||||
The supported ISO15118-20 energy services (DC, DC_BPT, AC, AC_BPT) the EV supports,
|
||||
provided as a prioritized list. The first entry in the list has the highest priority
|
||||
and is the most likely to be selected. The services should be separated only with commas.
|
||||
type: string
|
||||
default: "DC,DC_BPT"
|
||||
provides:
|
||||
ev:
|
||||
interface: ISO15118_ev
|
||||
description: This module implements the ISO15118-2 implementation of an EV
|
||||
enable_external_mqtt: true
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Sebastian Lukas
|
||||
156
tools/EVerest-main/modules/EV/PyEvJosev/module.py
Normal file
156
tools/EVerest-main/modules/EV/PyEvJosev/module.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import threading
|
||||
import math
|
||||
|
||||
from everest.framework import Module, RuntimeSession, log
|
||||
|
||||
# fmt: off
|
||||
JOSEV_WORK_DIR = Path(__file__).parent / '../../3rd_party/josev'
|
||||
sys.path.append(JOSEV_WORK_DIR.as_posix())
|
||||
|
||||
from iso15118.evcc import EVCCHandler
|
||||
from iso15118.evcc.controller.simulator import SimEVController
|
||||
from iso15118.evcc.evcc_config import EVCCConfig
|
||||
from iso15118.evcc.everest import context as JOSEV_CONTEXT
|
||||
from iso15118.shared.exificient_exi_codec import ExificientEXICodec
|
||||
from iso15118.shared.settings import set_PKI_PATH, enable_tls_1_3
|
||||
|
||||
from utilities import (
|
||||
setup_everest_logging,
|
||||
determine_network_interface,
|
||||
patch_josev_config
|
||||
)
|
||||
|
||||
setup_everest_logging()
|
||||
|
||||
EVEREST_CERTS_SUB_DIR = 'certs'
|
||||
|
||||
async def evcc_handler_main_loop(module_config: dict, exi_codec: ExificientEXICodec):
|
||||
"""
|
||||
Entrypoint function that starts the ISO 15118 code running on
|
||||
the EVCC (EV Communication Controller)
|
||||
"""
|
||||
iface = determine_network_interface(module_config['device'])
|
||||
|
||||
evcc_config = EVCCConfig()
|
||||
patch_josev_config(evcc_config, module_config)
|
||||
|
||||
await EVCCHandler(
|
||||
evcc_config=evcc_config,
|
||||
iface=iface,
|
||||
exi_codec=exi_codec,
|
||||
ev_controller=SimEVController(evcc_config),
|
||||
).start()
|
||||
|
||||
class PyEVJosevModule():
|
||||
def __init__(self) -> None:
|
||||
self._es = JOSEV_CONTEXT.ev_state
|
||||
self._session = RuntimeSession()
|
||||
m = Module(self._session)
|
||||
|
||||
log.update_process_name(m.info.id)
|
||||
|
||||
self._setup = m.say_hello()
|
||||
|
||||
etc_certs_path = m.info.paths.etc / EVEREST_CERTS_SUB_DIR
|
||||
set_PKI_PATH(str(etc_certs_path.resolve()))
|
||||
|
||||
if self._setup.configs.module['enable_tls_1_3']:
|
||||
enable_tls_1_3()
|
||||
|
||||
self._es.internet_service_needed = self._setup.configs.module['is_internet_service_needed']
|
||||
self._es.all_service_details = self._setup.configs.module['request_all_service_details']
|
||||
self._es.all_vas_services = self._setup.configs.module['select_all_vas_services']
|
||||
|
||||
# setup publishing callback
|
||||
def publish_callback(variable_name: str, value: any):
|
||||
m.publish_variable('ev', variable_name, value)
|
||||
|
||||
# set publish callback for context
|
||||
JOSEV_CONTEXT.set_publish_callback(publish_callback)
|
||||
|
||||
# setup handlers
|
||||
for cmd in m.implementations['ev'].commands:
|
||||
m.implement_command(
|
||||
'ev', cmd, getattr(self, f'_handler_{cmd}'))
|
||||
|
||||
# init ready event
|
||||
self._ready_event = threading.Event()
|
||||
|
||||
self._mod = m
|
||||
self._mod.init_done(self._ready)
|
||||
|
||||
def start_evcc_handler(self):
|
||||
exi_codec = ExificientEXICodec()
|
||||
try:
|
||||
while True:
|
||||
self._ready_event.wait()
|
||||
try:
|
||||
asyncio.run(evcc_handler_main_loop(self._setup.configs.module, exi_codec))
|
||||
self._mod.publish_variable('ev', 'v2g_session_finished', None)
|
||||
except KeyboardInterrupt:
|
||||
log.debug("SECC program terminated manually")
|
||||
break
|
||||
finally:
|
||||
self._ready_event.clear()
|
||||
finally:
|
||||
exi_codec.shutdown()
|
||||
|
||||
def _ready(self):
|
||||
log.debug("ready!")
|
||||
|
||||
# implementation handlers
|
||||
|
||||
def _handler_start_charging(self, args) -> bool:
|
||||
|
||||
self._es.DepartureTime = args['DepartureTime']
|
||||
self._es.EAmount_kWh = args['EAmount']
|
||||
self._es.EnergyTransferMode = args['EnergyTransferMode']
|
||||
|
||||
if "payment_option" in args['SelectedPaymentOption']:
|
||||
self._es.PaymentOption = args['SelectedPaymentOption']['payment_option']
|
||||
if "enforce_payment_option" in args['SelectedPaymentOption']:
|
||||
self._es.enforce_payment_option = args['SelectedPaymentOption']['enforce_payment_option']
|
||||
|
||||
self._ready_event.set()
|
||||
|
||||
return True
|
||||
|
||||
def _handler_stop_charging(self, args):
|
||||
self._es.StopCharging = True
|
||||
|
||||
def _handler_pause_charging(self, args):
|
||||
self._es.Pause = True
|
||||
|
||||
def _handler_set_fault(self, args):
|
||||
pass
|
||||
|
||||
def _handler_set_dc_params(self, args):
|
||||
parameters = args['EvParameters']
|
||||
self._es.dc_max_current_limit = parameters['max_current_limit']
|
||||
self._es.dc_max_power_limit = parameters['max_power_limit']
|
||||
self._es.dc_max_voltage_limit = parameters['max_voltage_limit']
|
||||
self._es.dc_energy_capacity = parameters['energy_capacity']
|
||||
self._es.dc_target_current = parameters['target_current']
|
||||
self._es.dc_target_voltage = parameters['target_voltage']
|
||||
|
||||
def _handler_set_bpt_dc_params(self, args):
|
||||
parameters = args['EvBPTParameters']
|
||||
self._es.dc_discharge_max_current_limit = parameters["discharge_max_current_limit"]
|
||||
self._es.dc_discharge_max_power_limit = parameters['discharge_max_power_limit']
|
||||
self._es.dc_discharge_target_current = parameters['discharge_target_current']
|
||||
self._es.minimal_soc = parameters["discharge_minimal_soc"]
|
||||
|
||||
def _handler_enable_sae_j2847_v2g_v2h(self, args):
|
||||
self._es.SAEJ2847_V2H_V2G_Active = True
|
||||
|
||||
def _handler_update_soc(self, args):
|
||||
self._es.actual_soc = math.floor(args['SoC'])
|
||||
|
||||
py_ev_josev = PyEVJosevModule()
|
||||
py_ev_josev.start_evcc_handler()
|
||||
|
||||
107
tools/EVerest-main/modules/EV/PyEvJosev/utilities.py
Normal file
107
tools/EVerest-main/modules/EV/PyEvJosev/utilities.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
import logging
|
||||
import netifaces
|
||||
|
||||
from everest.framework import log
|
||||
|
||||
from iso15118.evcc.evcc_config import EVCCConfig
|
||||
from iso15118.shared.utils import load_requested_protocols, load_requested_energy_services
|
||||
|
||||
class EverestPyLoggingHandler(logging.Handler):
|
||||
|
||||
def __init__(self):
|
||||
logging.Handler.__init__(self)
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
|
||||
log_level: int = record.levelno
|
||||
if log_level == logging.CRITICAL:
|
||||
log.critical(msg)
|
||||
elif log_level == logging.ERROR:
|
||||
log.error(msg)
|
||||
elif log_level == logging.WARNING:
|
||||
log.warning(msg)
|
||||
# FIXME (aw): implicitely pipe everything with loglevel INFO into DEBUG
|
||||
else:
|
||||
log.debug(msg)
|
||||
|
||||
|
||||
def setup_everest_logging():
|
||||
# remove all logging handler so that we'll have only our custom one
|
||||
# FIXME (aw): this is probably bad practice because if everyone does that, only the last one might survive
|
||||
logging.getLogger().handlers.clear()
|
||||
|
||||
handler = EverestPyLoggingHandler()
|
||||
|
||||
# NOTE (aw): the default formatting should be fine
|
||||
# formatter = logging.Formatter("%(levelname)s - %(name)s (%(lineno)d): %(message)s")
|
||||
# handler.setFormatter(formatter)
|
||||
|
||||
logging.getLogger().addHandler(handler)
|
||||
|
||||
|
||||
def choose_first_ipv6_local() -> str:
|
||||
for iface in netifaces.interfaces():
|
||||
if netifaces.AF_INET6 in netifaces.ifaddresses(iface):
|
||||
for netif_inet6 in netifaces.ifaddresses(iface)[netifaces.AF_INET6]:
|
||||
if 'fe80' in netif_inet6['addr']:
|
||||
return iface
|
||||
|
||||
log.warning('No necessary IPv6 link-local address was found!')
|
||||
return 'eth0'
|
||||
|
||||
|
||||
def determine_network_interface(preferred_interface: str) -> str:
|
||||
if preferred_interface == "auto":
|
||||
return choose_first_ipv6_local()
|
||||
elif preferred_interface not in netifaces.interfaces():
|
||||
log.warning(
|
||||
f"The network interface {preferred_interface} was not found!")
|
||||
|
||||
return preferred_interface
|
||||
|
||||
|
||||
def patch_josev_config(josev_config: EVCCConfig, everest_config: dict) -> None:
|
||||
|
||||
josev_config.use_tls = everest_config['tls_active']
|
||||
|
||||
josev_config.enforce_tls = everest_config['enforce_tls']
|
||||
|
||||
josev_config.is_cert_install_needed = everest_config['is_cert_install_needed']
|
||||
|
||||
josev_config.sdp_retry_cycles = 1
|
||||
|
||||
protocols = [
|
||||
"DIN_SPEC_70121",
|
||||
"ISO_15118_2",
|
||||
"ISO_15118_20_AC",
|
||||
"ISO_15118_20_DC",
|
||||
]
|
||||
|
||||
if not everest_config['supported_DIN70121']:
|
||||
protocols.remove('DIN_SPEC_70121')
|
||||
|
||||
if not everest_config['supported_ISO15118_2']:
|
||||
protocols.remove('ISO_15118_2')
|
||||
|
||||
if not everest_config['supported_ISO15118_20_AC']:
|
||||
protocols.remove('ISO_15118_20_AC')
|
||||
|
||||
if not everest_config['supported_ISO15118_20_DC']:
|
||||
protocols.remove('ISO_15118_20_DC')
|
||||
|
||||
if not protocols:
|
||||
log.error("The supporting hlc protocols were not specified")
|
||||
|
||||
josev_config.supported_protocols = load_requested_protocols(protocols)
|
||||
|
||||
if everest_config['supported_d20_energy_services']:
|
||||
josev_config.supported_energy_services = load_requested_energy_services(
|
||||
everest_config['supported_d20_energy_services'].split(',')
|
||||
)
|
||||
else:
|
||||
josev_config.supported_energy_services = load_requested_energy_services(
|
||||
['DC']
|
||||
)
|
||||
Reference in New Issue
Block a user