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:
1
tools/EVerest-main/applications/CMakeLists.txt
Normal file
1
tools/EVerest-main/applications/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
add_subdirectory(pionix_chargebridge)
|
||||
31
tools/EVerest-main/applications/README.md
Normal file
31
tools/EVerest-main/applications/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Applications related to EVerest
|
||||
|
||||
This directory will contain applications that are used during the EVerest build process,
|
||||
deployed to the target hardware and useful scripts for development.
|
||||
|
||||
## containers/
|
||||
|
||||
containers/ contains multiple useful Dockerfile based containers that can be used for development.
|
||||
|
||||
* `mosquittto` : An mqtt broker
|
||||
* `mqtt-explorer` : Tool to inspect messages on mqtt topics
|
||||
* `nodered` : NodeRED application to run i.e. sil flows
|
||||
* `steve` : OCPP backend
|
||||
|
||||
## dependency_manager
|
||||
|
||||
Contains the EDM (EVerest Dependency Manager) tool which is a cli to
|
||||
manage an EVerest workspace and external dependencies during building.
|
||||
|
||||
## everest_dev_tool
|
||||
|
||||
A CLI designed to provide helpful commands/aliases for developing.
|
||||
|
||||
## utils/
|
||||
|
||||
utils/ contains ev-cli and everest-testing as well as additional code integrated from everest-utils.
|
||||
This might get broken up further in the future.
|
||||
|
||||
## devrd
|
||||
|
||||
Script to manage devcontainer + containerized services in the development environment
|
||||
@@ -0,0 +1,15 @@
|
||||
FROM eclipse-mosquitto:2.0.22
|
||||
|
||||
COPY mosquitto.conf /mosquitto/config/mosquitto.conf
|
||||
|
||||
ARG TARGETARCH
|
||||
COPY entrypoint_wrapper.sh /entrypoint_wrapper.sh
|
||||
RUN if [ ${TARGETARCH} != "amd64" ]; then \
|
||||
mv /docker-entrypoint.sh /wrapped_entrypoint.sh; \
|
||||
cp /entrypoint_wrapper.sh /entrypoint.sh; \
|
||||
else \
|
||||
mv /docker-entrypoint.sh /entrypoint.sh; \
|
||||
fi; \
|
||||
rm /entrypoint_wrapper.sh
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
CMD ["/usr/sbin/mosquitto", "-c", "/mosquitto/config/mosquitto.conf"]
|
||||
37
tools/EVerest-main/applications/containers/mosquitto/entrypoint_wrapper.sh
Executable file
37
tools/EVerest-main/applications/containers/mosquitto/entrypoint_wrapper.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/ash
|
||||
|
||||
# ---------------------------------------------
|
||||
# Architecture Warning Wrapper Script
|
||||
#
|
||||
# This script is used as an entrypoint wrapper to emit a warning
|
||||
# when the container is not running on the officially supported
|
||||
# amd64 (x86_64) architecture.
|
||||
#
|
||||
# It checks for the presence of a wrapped entrypoint script
|
||||
# (/wrapped_entrypoint.sh) and executes it if found; otherwise,
|
||||
# it falls back to executing the provided command directly.
|
||||
#
|
||||
# The warning is shown both before and after the wrapped command
|
||||
# to ensure visibility.
|
||||
# ---------------------------------------------
|
||||
|
||||
function print_warning {
|
||||
echo -e "\033[0;31m"
|
||||
echo "-------------------------------------------------------------"
|
||||
echo "⚠️ WARNING: Unsupported Architecture Detected"
|
||||
echo
|
||||
echo "This Docker image is not running on the amd64 (x86_64) architecture."
|
||||
echo "It is recommended to use the amd64-based image for full compatibility."
|
||||
echo "Other architectures are not officially supported and may cause issues."
|
||||
echo
|
||||
echo "-------------------------------------------------------------"
|
||||
echo -e "\033[0m"
|
||||
}
|
||||
|
||||
print_warning
|
||||
|
||||
if [ -f /wrapped_entrypoint.sh ]; then
|
||||
exec /wrapped_entrypoint.sh "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
@@ -0,0 +1,876 @@
|
||||
# Config file for mosquitto
|
||||
#
|
||||
# See mosquitto.conf(5) for more information.
|
||||
#
|
||||
# Default values are shown, uncomment to change.
|
||||
#
|
||||
# Use the # character to indicate a comment, but only if it is the
|
||||
# very first character on the line.
|
||||
|
||||
# =================================================================
|
||||
# General configuration
|
||||
# =================================================================
|
||||
|
||||
# Use per listener security settings.
|
||||
#
|
||||
# It is recommended this option be set before any other options.
|
||||
#
|
||||
# If this option is set to true, then all authentication and access control
|
||||
# options are controlled on a per listener basis. The following options are
|
||||
# affected:
|
||||
#
|
||||
# password_file acl_file psk_file auth_plugin auth_opt_* allow_anonymous
|
||||
# auto_id_prefix allow_zero_length_clientid
|
||||
#
|
||||
# Note that if set to true, then a durable client (i.e. with clean session set
|
||||
# to false) that has disconnected will use the ACL settings defined for the
|
||||
# listener that it was most recently connected to.
|
||||
#
|
||||
# The default behaviour is for this to be set to false, which maintains the
|
||||
# setting behaviour from previous versions of mosquitto.
|
||||
#per_listener_settings false
|
||||
|
||||
|
||||
# This option controls whether a client is allowed to connect with a zero
|
||||
# length client id or not. This option only affects clients using MQTT v3.1.1
|
||||
# and later. If set to false, clients connecting with a zero length client id
|
||||
# are disconnected. If set to true, clients will be allocated a client id by
|
||||
# the broker. This means it is only useful for clients with clean session set
|
||||
# to true.
|
||||
#allow_zero_length_clientid true
|
||||
|
||||
# If allow_zero_length_clientid is true, this option allows you to set a prefix
|
||||
# to automatically generated client ids to aid visibility in logs.
|
||||
# Defaults to 'auto-'
|
||||
#auto_id_prefix auto-
|
||||
|
||||
# This option affects the scenario when a client subscribes to a topic that has
|
||||
# retained messages. It is possible that the client that published the retained
|
||||
# message to the topic had access at the time they published, but that access
|
||||
# has been subsequently removed. If check_retain_source is set to true, the
|
||||
# default, the source of a retained message will be checked for access rights
|
||||
# before it is republished. When set to false, no check will be made and the
|
||||
# retained message will always be published. This affects all listeners.
|
||||
#check_retain_source true
|
||||
|
||||
# QoS 1 and 2 messages will be allowed inflight per client until this limit
|
||||
# is exceeded. Defaults to 0. (No maximum)
|
||||
# See also max_inflight_messages
|
||||
#max_inflight_bytes 0
|
||||
|
||||
# The maximum number of QoS 1 and 2 messages currently inflight per
|
||||
# client.
|
||||
# This includes messages that are partway through handshakes and
|
||||
# those that are being retried. Defaults to 20. Set to 0 for no
|
||||
# maximum. Setting to 1 will guarantee in-order delivery of QoS 1
|
||||
# and 2 messages.
|
||||
#max_inflight_messages 20
|
||||
|
||||
# For MQTT v5 clients, it is possible to have the server send a "server
|
||||
# keepalive" value that will override the keepalive value set by the client.
|
||||
# This is intended to be used as a mechanism to say that the server will
|
||||
# disconnect the client earlier than it anticipated, and that the client should
|
||||
# use the new keepalive value. The max_keepalive option allows you to specify
|
||||
# that clients may only connect with keepalive less than or equal to this
|
||||
# value, otherwise they will be sent a server keepalive telling them to use
|
||||
# max_keepalive. This only applies to MQTT v5 clients. The maximum value
|
||||
# allowable is 65535. Do not set below 10.
|
||||
#max_keepalive 65535
|
||||
|
||||
# For MQTT v5 clients, it is possible to have the server send a "maximum packet
|
||||
# size" value that will instruct the client it will not accept MQTT packets
|
||||
# with size greater than max_packet_size bytes. This applies to the full MQTT
|
||||
# packet, not just the payload. Setting this option to a positive value will
|
||||
# set the maximum packet size to that number of bytes. If a client sends a
|
||||
# packet which is larger than this value, it will be disconnected. This applies
|
||||
# to all clients regardless of the protocol version they are using, but v3.1.1
|
||||
# and earlier clients will of course not have received the maximum packet size
|
||||
# information. Defaults to no limit. Setting below 20 bytes is forbidden
|
||||
# because it is likely to interfere with ordinary client operation, even with
|
||||
# very small payloads.
|
||||
#max_packet_size 0
|
||||
|
||||
# QoS 1 and 2 messages above those currently in-flight will be queued per
|
||||
# client until this limit is exceeded. Defaults to 0. (No maximum)
|
||||
# See also max_queued_messages.
|
||||
# If both max_queued_messages and max_queued_bytes are specified, packets will
|
||||
# be queued until the first limit is reached.
|
||||
#max_queued_bytes 0
|
||||
|
||||
# Set the maximum QoS supported. Clients publishing at a QoS higher than
|
||||
# specified here will be disconnected.
|
||||
#max_qos 2
|
||||
|
||||
# The maximum number of QoS 1 and 2 messages to hold in a queue per client
|
||||
# above those that are currently in-flight. Defaults to 1000. Set
|
||||
# to 0 for no maximum (not recommended).
|
||||
# See also queue_qos0_messages.
|
||||
# See also max_queued_bytes.
|
||||
#max_queued_messages 1000
|
||||
#
|
||||
# This option sets the maximum number of heap memory bytes that the broker will
|
||||
# allocate, and hence sets a hard limit on memory use by the broker. Memory
|
||||
# requests that exceed this value will be denied. The effect will vary
|
||||
# depending on what has been denied. If an incoming message is being processed,
|
||||
# then the message will be dropped and the publishing client will be
|
||||
# disconnected. If an outgoing message is being sent, then the individual
|
||||
# message will be dropped and the receiving client will be disconnected.
|
||||
# Defaults to no limit.
|
||||
#memory_limit 0
|
||||
|
||||
# This option sets the maximum publish payload size that the broker will allow.
|
||||
# Received messages that exceed this size will not be accepted by the broker.
|
||||
# The default value is 0, which means that all valid MQTT messages are
|
||||
# accepted. MQTT imposes a maximum payload size of 268435455 bytes.
|
||||
#message_size_limit 0
|
||||
|
||||
# This option allows persistent clients (those with clean session set to false)
|
||||
# to be removed if they do not reconnect within a certain time frame.
|
||||
#
|
||||
# This is a non-standard option in MQTT V3.1 but allowed in MQTT v3.1.1.
|
||||
#
|
||||
# Badly designed clients may set clean session to false whilst using a randomly
|
||||
# generated client id. This leads to persistent clients that will never
|
||||
# reconnect. This option allows these clients to be removed.
|
||||
#
|
||||
# The expiration period should be an integer followed by one of h d w m y for
|
||||
# hour, day, week, month and year respectively. For example
|
||||
#
|
||||
# persistent_client_expiration 2m
|
||||
# persistent_client_expiration 14d
|
||||
# persistent_client_expiration 1y
|
||||
#
|
||||
# The default if not set is to never expire persistent clients.
|
||||
#persistent_client_expiration
|
||||
|
||||
# Write process id to a file. Default is a blank string which means
|
||||
# a pid file shouldn't be written.
|
||||
# This should be set to /var/run/mosquitto/mosquitto.pid if mosquitto is
|
||||
# being run automatically on boot with an init script and
|
||||
# start-stop-daemon or similar.
|
||||
#pid_file
|
||||
|
||||
# Set to true to queue messages with QoS 0 when a persistent client is
|
||||
# disconnected. These messages are included in the limit imposed by
|
||||
# max_queued_messages and max_queued_bytes
|
||||
# Defaults to false.
|
||||
# This is a non-standard option for the MQTT v3.1 spec but is allowed in
|
||||
# v3.1.1.
|
||||
#queue_qos0_messages false
|
||||
|
||||
# Set to false to disable retained message support. If a client publishes a
|
||||
# message with the retain bit set, it will be disconnected if this is set to
|
||||
# false.
|
||||
#retain_available true
|
||||
|
||||
# Disable Nagle's algorithm on client sockets. This has the effect of reducing
|
||||
# latency of individual messages at the potential cost of increasing the number
|
||||
# of packets being sent.
|
||||
#set_tcp_nodelay false
|
||||
|
||||
# Time in seconds between updates of the $SYS tree.
|
||||
# Set to 0 to disable the publishing of the $SYS tree.
|
||||
#sys_interval 10
|
||||
|
||||
# The MQTT specification requires that the QoS of a message delivered to a
|
||||
# subscriber is never upgraded to match the QoS of the subscription. Enabling
|
||||
# this option changes this behaviour. If upgrade_outgoing_qos is set true,
|
||||
# messages sent to a subscriber will always match the QoS of its subscription.
|
||||
# This is a non-standard option explicitly disallowed by the spec.
|
||||
#upgrade_outgoing_qos false
|
||||
|
||||
# When run as root, drop privileges to this user and its primary
|
||||
# group.
|
||||
# Set to root to stay as root, but this is not recommended.
|
||||
# If set to "mosquitto", or left unset, and the "mosquitto" user does not exist
|
||||
# then it will drop privileges to the "nobody" user instead.
|
||||
# If run as a non-root user, this setting has no effect.
|
||||
# Note that on Windows this has no effect and so mosquitto should be started by
|
||||
# the user you wish it to run as.
|
||||
#user mosquitto
|
||||
|
||||
# =================================================================
|
||||
# Listeners
|
||||
# =================================================================
|
||||
|
||||
# Listen on a port/ip address combination. By using this variable
|
||||
# multiple times, mosquitto can listen on more than one port. If
|
||||
# this variable is used and neither bind_address nor port given,
|
||||
# then the default listener will not be started.
|
||||
# The port number to listen on must be given. Optionally, an ip
|
||||
# address or host name may be supplied as a second argument. In
|
||||
# this case, mosquitto will attempt to bind the listener to that
|
||||
# address and so restrict access to the associated network and
|
||||
# interface. By default, mosquitto will listen on all interfaces.
|
||||
# Note that for a websockets listener it is not possible to bind to a host
|
||||
# name.
|
||||
#
|
||||
# On systems that support Unix Domain Sockets, it is also possible
|
||||
# to create a # Unix socket rather than opening a TCP socket. In
|
||||
# this case, the port number should be set to 0 and a unix socket
|
||||
# path must be provided, e.g.
|
||||
# listener 0 /tmp/mosquitto.sock
|
||||
#
|
||||
# listener port-number [ip address/host name/unix socket path]
|
||||
listener 1883
|
||||
|
||||
# By default, a listener will attempt to listen on all supported IP protocol
|
||||
# versions. If you do not have an IPv4 or IPv6 interface you may wish to
|
||||
# disable support for either of those protocol versions. In particular, note
|
||||
# that due to the limitations of the websockets library, it will only ever
|
||||
# attempt to open IPv6 sockets if IPv6 support is compiled in, and so will fail
|
||||
# if IPv6 is not available.
|
||||
#
|
||||
# Set to `ipv4` to force the listener to only use IPv4, or set to `ipv6` to
|
||||
# force the listener to only use IPv6. If you want support for both IPv4 and
|
||||
# IPv6, then do not use the socket_domain option.
|
||||
#
|
||||
#socket_domain
|
||||
|
||||
# Bind the listener to a specific interface. This is similar to
|
||||
# the [ip address/host name] part of the listener definition, but is useful
|
||||
# when an interface has multiple addresses or the address may change. If used
|
||||
# with the [ip address/host name] part of the listener definition, then the
|
||||
# bind_interface option will take priority.
|
||||
# Not available on Windows.
|
||||
#
|
||||
# Example: bind_interface eth0
|
||||
#bind_interface
|
||||
|
||||
# When a listener is using the websockets protocol, it is possible to serve
|
||||
# http data as well. Set http_dir to a directory which contains the files you
|
||||
# wish to serve. If this option is not specified, then no normal http
|
||||
# connections will be possible.
|
||||
#http_dir
|
||||
|
||||
# The maximum number of client connections to allow. This is
|
||||
# a per listener setting.
|
||||
# Default is -1, which means unlimited connections.
|
||||
# Note that other process limits mean that unlimited connections
|
||||
# are not really possible. Typically the default maximum number of
|
||||
# connections possible is around 1024.
|
||||
#max_connections -1
|
||||
|
||||
# The listener can be restricted to operating within a topic hierarchy using
|
||||
# the mount_point option. This is achieved be prefixing the mount_point string
|
||||
# to all topics for any clients connected to this listener. This prefixing only
|
||||
# happens internally to the broker; the client will not see the prefix.
|
||||
#mount_point
|
||||
|
||||
# Choose the protocol to use when listening.
|
||||
# This can be either mqtt or websockets.
|
||||
# Certificate based TLS may be used with websockets, except that only the
|
||||
# cafile, certfile, keyfile, ciphers, and ciphers_tls13 options are supported.
|
||||
#protocol mqtt
|
||||
|
||||
# Set use_username_as_clientid to true to replace the clientid that a client
|
||||
# connected with with its username. This allows authentication to be tied to
|
||||
# the clientid, which means that it is possible to prevent one client
|
||||
# disconnecting another by using the same clientid.
|
||||
# If a client connects with no username it will be disconnected as not
|
||||
# authorised when this option is set to true.
|
||||
# Do not use in conjunction with clientid_prefixes.
|
||||
# See also use_identity_as_username.
|
||||
#use_username_as_clientid
|
||||
|
||||
# Change the websockets headers size. This is a global option, it is not
|
||||
# possible to set per listener. This option sets the size of the buffer used in
|
||||
# the libwebsockets library when reading HTTP headers. If you are passing large
|
||||
# header data such as cookies then you may need to increase this value. If left
|
||||
# unset, or set to 0, then the default of 1024 bytes will be used.
|
||||
#websockets_headers_size
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Certificate based SSL/TLS support
|
||||
# -----------------------------------------------------------------
|
||||
# The following options can be used to enable certificate based SSL/TLS support
|
||||
# for this listener. Note that the recommended port for MQTT over TLS is 8883,
|
||||
# but this must be set manually.
|
||||
#
|
||||
# See also the mosquitto-tls man page and the "Pre-shared-key based SSL/TLS
|
||||
# support" section. Only one of certificate or PSK encryption support can be
|
||||
# enabled for any listener.
|
||||
|
||||
# Both of certfile and keyfile must be defined to enable certificate based
|
||||
# TLS encryption.
|
||||
|
||||
# Path to the PEM encoded server certificate.
|
||||
#certfile
|
||||
|
||||
# Path to the PEM encoded keyfile.
|
||||
#keyfile
|
||||
|
||||
# If you wish to control which encryption ciphers are used, use the ciphers
|
||||
# option. The list of available ciphers can be optained using the "openssl
|
||||
# ciphers" command and should be provided in the same format as the output of
|
||||
# that command. This applies to TLS 1.2 and earlier versions only. Use
|
||||
# ciphers_tls1.3 for TLS v1.3.
|
||||
#ciphers
|
||||
|
||||
# Choose which TLS v1.3 ciphersuites are used for this listener.
|
||||
# Defaults to "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"
|
||||
#ciphers_tls1.3
|
||||
|
||||
# If you have require_certificate set to true, you can create a certificate
|
||||
# revocation list file to revoke access to particular client certificates. If
|
||||
# you have done this, use crlfile to point to the PEM encoded revocation file.
|
||||
#crlfile
|
||||
|
||||
# To allow the use of ephemeral DH key exchange, which provides forward
|
||||
# security, the listener must load DH parameters. This can be specified with
|
||||
# the dhparamfile option. The dhparamfile can be generated with the command
|
||||
# e.g. "openssl dhparam -out dhparam.pem 2048"
|
||||
#dhparamfile
|
||||
|
||||
# By default an TLS enabled listener will operate in a similar fashion to a
|
||||
# https enabled web server, in that the server has a certificate signed by a CA
|
||||
# and the client will verify that it is a trusted certificate. The overall aim
|
||||
# is encryption of the network traffic. By setting require_certificate to true,
|
||||
# the client must provide a valid certificate in order for the network
|
||||
# connection to proceed. This allows access to the broker to be controlled
|
||||
# outside of the mechanisms provided by MQTT.
|
||||
#require_certificate false
|
||||
|
||||
# cafile and capath define methods of accessing the PEM encoded
|
||||
# Certificate Authority certificates that will be considered trusted when
|
||||
# checking incoming client certificates.
|
||||
# cafile defines the path to a file containing the CA certificates.
|
||||
# capath defines a directory that will be searched for files
|
||||
# containing the CA certificates. For capath to work correctly, the
|
||||
# certificate files must have ".crt" as the file ending and you must run
|
||||
# "openssl rehash <path to capath>" each time you add/remove a certificate.
|
||||
#cafile
|
||||
#capath
|
||||
|
||||
|
||||
# If require_certificate is true, you may set use_identity_as_username to true
|
||||
# to use the CN value from the client certificate as a username. If this is
|
||||
# true, the password_file option will not be used for this listener.
|
||||
#use_identity_as_username false
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Pre-shared-key based SSL/TLS support
|
||||
# -----------------------------------------------------------------
|
||||
# The following options can be used to enable PSK based SSL/TLS support for
|
||||
# this listener. Note that the recommended port for MQTT over TLS is 8883, but
|
||||
# this must be set manually.
|
||||
#
|
||||
# See also the mosquitto-tls man page and the "Certificate based SSL/TLS
|
||||
# support" section. Only one of certificate or PSK encryption support can be
|
||||
# enabled for any listener.
|
||||
|
||||
# The psk_hint option enables pre-shared-key support for this listener and also
|
||||
# acts as an identifier for this listener. The hint is sent to clients and may
|
||||
# be used locally to aid authentication. The hint is a free form string that
|
||||
# doesn't have much meaning in itself, so feel free to be creative.
|
||||
# If this option is provided, see psk_file to define the pre-shared keys to be
|
||||
# used or create a security plugin to handle them.
|
||||
#psk_hint
|
||||
|
||||
# When using PSK, the encryption ciphers used will be chosen from the list of
|
||||
# available PSK ciphers. If you want to control which ciphers are available,
|
||||
# use the "ciphers" option. The list of available ciphers can be optained
|
||||
# using the "openssl ciphers" command and should be provided in the same format
|
||||
# as the output of that command.
|
||||
#ciphers
|
||||
|
||||
# Set use_identity_as_username to have the psk identity sent by the client used
|
||||
# as its username. Authentication will be carried out using the PSK rather than
|
||||
# the MQTT username/password and so password_file will not be used for this
|
||||
# listener.
|
||||
#use_identity_as_username false
|
||||
|
||||
listener 9001
|
||||
protocol websockets
|
||||
|
||||
# =================================================================
|
||||
# Persistence
|
||||
# =================================================================
|
||||
|
||||
# If persistence is enabled, save the in-memory database to disk
|
||||
# every autosave_interval seconds. If set to 0, the persistence
|
||||
# database will only be written when mosquitto exits. See also
|
||||
# autosave_on_changes.
|
||||
# Note that writing of the persistence database can be forced by
|
||||
# sending mosquitto a SIGUSR1 signal.
|
||||
#autosave_interval 1800
|
||||
|
||||
# If true, mosquitto will count the number of subscription changes, retained
|
||||
# messages received and queued messages and if the total exceeds
|
||||
# autosave_interval then the in-memory database will be saved to disk.
|
||||
# If false, mosquitto will save the in-memory database to disk by treating
|
||||
# autosave_interval as a time in seconds.
|
||||
#autosave_on_changes false
|
||||
|
||||
# Save persistent message data to disk (true/false).
|
||||
# This saves information about all messages, including
|
||||
# subscriptions, currently in-flight messages and retained
|
||||
# messages.
|
||||
# retained_persistence is a synonym for this option.
|
||||
#persistence false
|
||||
|
||||
# The filename to use for the persistent database, not including
|
||||
# the path.
|
||||
#persistence_file mosquitto.db
|
||||
|
||||
# Location for persistent database.
|
||||
# Default is an empty string (current directory).
|
||||
# Set to e.g. /var/lib/mosquitto if running as a proper service on Linux or
|
||||
# similar.
|
||||
#persistence_location
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Logging
|
||||
# =================================================================
|
||||
|
||||
# Places to log to. Use multiple log_dest lines for multiple
|
||||
# logging destinations.
|
||||
# Possible destinations are: stdout stderr syslog topic file dlt
|
||||
#
|
||||
# stdout and stderr log to the console on the named output.
|
||||
#
|
||||
# syslog uses the userspace syslog facility which usually ends up
|
||||
# in /var/log/messages or similar.
|
||||
#
|
||||
# topic logs to the broker topic '$SYS/broker/log/<severity>',
|
||||
# where severity is one of D, E, W, N, I, M which are debug, error,
|
||||
# warning, notice, information and message. Message type severity is used by
|
||||
# the subscribe/unsubscribe log_types and publishes log messages to
|
||||
# $SYS/broker/log/M/susbcribe or $SYS/broker/log/M/unsubscribe.
|
||||
#
|
||||
# The file destination requires an additional parameter which is the file to be
|
||||
# logged to, e.g. "log_dest file /var/log/mosquitto.log". The file will be
|
||||
# closed and reopened when the broker receives a HUP signal. Only a single file
|
||||
# destination may be configured.
|
||||
#
|
||||
# The dlt destination is for the automotive `Diagnostic Log and Trace` tool.
|
||||
# This requires that Mosquitto has been compiled with DLT support.
|
||||
#
|
||||
# Note that if the broker is running as a Windows service it will default to
|
||||
# "log_dest none" and neither stdout nor stderr logging is available.
|
||||
# Use "log_dest none" if you wish to disable logging.
|
||||
#log_dest stderr
|
||||
|
||||
# Types of messages to log. Use multiple log_type lines for logging
|
||||
# multiple types of messages.
|
||||
# Possible types are: debug, error, warning, notice, information,
|
||||
# none, subscribe, unsubscribe, websockets, all.
|
||||
# Note that debug type messages are for decoding the incoming/outgoing
|
||||
# network packets. They are not logged in "topics".
|
||||
#log_type error
|
||||
#log_type warning
|
||||
#log_type notice
|
||||
#log_type information
|
||||
|
||||
|
||||
# If set to true, client connection and disconnection messages will be included
|
||||
# in the log.
|
||||
#connection_messages true
|
||||
|
||||
# If using syslog logging (not on Windows), messages will be logged to the
|
||||
# "daemon" facility by default. Use the log_facility option to choose which of
|
||||
# local0 to local7 to log to instead. The option value should be an integer
|
||||
# value, e.g. "log_facility 5" to use local5.
|
||||
#log_facility
|
||||
|
||||
# If set to true, add a timestamp value to each log message.
|
||||
#log_timestamp true
|
||||
|
||||
# Set the format of the log timestamp. If left unset, this is the number of
|
||||
# seconds since the Unix epoch.
|
||||
# This is a free text string which will be passed to the strftime function. To
|
||||
# get an ISO 8601 datetime, for example:
|
||||
# log_timestamp_format %Y-%m-%dT%H:%M:%S
|
||||
#log_timestamp_format
|
||||
|
||||
# Change the websockets logging level. This is a global option, it is not
|
||||
# possible to set per listener. This is an integer that is interpreted by
|
||||
# libwebsockets as a bit mask for its lws_log_levels enum. See the
|
||||
# libwebsockets documentation for more details. "log_type websockets" must also
|
||||
# be enabled.
|
||||
#websockets_log_level 0
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Security
|
||||
# =================================================================
|
||||
|
||||
# If set, only clients that have a matching prefix on their
|
||||
# clientid will be allowed to connect to the broker. By default,
|
||||
# all clients may connect.
|
||||
# For example, setting "secure-" here would mean a client "secure-
|
||||
# client" could connect but another with clientid "mqtt" couldn't.
|
||||
#clientid_prefixes
|
||||
|
||||
# Boolean value that determines whether clients that connect
|
||||
# without providing a username are allowed to connect. If set to
|
||||
# false then a password file should be created (see the
|
||||
# password_file option) to control authenticated client access.
|
||||
#
|
||||
# Defaults to false, unless there are no listeners defined in the configuration
|
||||
# file, in which case it is set to true, but connections are only allowed from
|
||||
# the local machine.
|
||||
allow_anonymous true
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Default authentication and topic access control
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
# Control access to the broker using a password file. This file can be
|
||||
# generated using the mosquitto_passwd utility. If TLS support is not compiled
|
||||
# into mosquitto (it is recommended that TLS support should be included) then
|
||||
# plain text passwords are used, in which case the file should be a text file
|
||||
# with lines in the format:
|
||||
# username:password
|
||||
# The password (and colon) may be omitted if desired, although this
|
||||
# offers very little in the way of security.
|
||||
#
|
||||
# See the TLS client require_certificate and use_identity_as_username options
|
||||
# for alternative authentication options. If an auth_plugin is used as well as
|
||||
# password_file, the auth_plugin check will be made first.
|
||||
#password_file
|
||||
|
||||
# Access may also be controlled using a pre-shared-key file. This requires
|
||||
# TLS-PSK support and a listener configured to use it. The file should be text
|
||||
# lines in the format:
|
||||
# identity:key
|
||||
# The key should be in hexadecimal format without a leading "0x".
|
||||
# If an auth_plugin is used as well, the auth_plugin check will be made first.
|
||||
#psk_file
|
||||
|
||||
# Control access to topics on the broker using an access control list
|
||||
# file. If this parameter is defined then only the topics listed will
|
||||
# have access.
|
||||
# If the first character of a line of the ACL file is a # it is treated as a
|
||||
# comment.
|
||||
# Topic access is added with lines of the format:
|
||||
#
|
||||
# topic [read|write|readwrite|deny] <topic>
|
||||
#
|
||||
# The access type is controlled using "read", "write", "readwrite" or "deny".
|
||||
# This parameter is optional (unless <topic> contains a space character) - if
|
||||
# not given then the access is read/write. <topic> can contain the + or #
|
||||
# wildcards as in subscriptions.
|
||||
#
|
||||
# The "deny" option can used to explicity deny access to a topic that would
|
||||
# otherwise be granted by a broader read/write/readwrite statement. Any "deny"
|
||||
# topics are handled before topics that grant read/write access.
|
||||
#
|
||||
# The first set of topics are applied to anonymous clients, assuming
|
||||
# allow_anonymous is true. User specific topic ACLs are added after a
|
||||
# user line as follows:
|
||||
#
|
||||
# user <username>
|
||||
#
|
||||
# The username referred to here is the same as in password_file. It is
|
||||
# not the clientid.
|
||||
#
|
||||
#
|
||||
# If is also possible to define ACLs based on pattern substitution within the
|
||||
# topic. The patterns available for substition are:
|
||||
#
|
||||
# %c to match the client id of the client
|
||||
# %u to match the username of the client
|
||||
#
|
||||
# The substitution pattern must be the only text for that level of hierarchy.
|
||||
#
|
||||
# The form is the same as for the topic keyword, but using pattern as the
|
||||
# keyword.
|
||||
# Pattern ACLs apply to all users even if the "user" keyword has previously
|
||||
# been given.
|
||||
#
|
||||
# If using bridges with usernames and ACLs, connection messages can be allowed
|
||||
# with the following pattern:
|
||||
# pattern write $SYS/broker/connection/%c/state
|
||||
#
|
||||
# pattern [read|write|readwrite] <topic>
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# pattern write sensor/%u/data
|
||||
#
|
||||
# If an auth_plugin is used as well as acl_file, the auth_plugin check will be
|
||||
# made first.
|
||||
#acl_file
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# External authentication and topic access plugin options
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
# External authentication and access control can be supported with the
|
||||
# auth_plugin option. This is a path to a loadable plugin. See also the
|
||||
# auth_opt_* options described below.
|
||||
#
|
||||
# The auth_plugin option can be specified multiple times to load multiple
|
||||
# plugins. The plugins will be processed in the order that they are specified
|
||||
# here. If the auth_plugin option is specified alongside either of
|
||||
# password_file or acl_file then the plugin checks will be made first.
|
||||
#
|
||||
#auth_plugin
|
||||
|
||||
# If the auth_plugin option above is used, define options to pass to the
|
||||
# plugin here as described by the plugin instructions. All options named
|
||||
# using the format auth_opt_* will be passed to the plugin, for example:
|
||||
#
|
||||
# auth_opt_db_host
|
||||
# auth_opt_db_port
|
||||
# auth_opt_db_username
|
||||
# auth_opt_db_password
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Bridges
|
||||
# =================================================================
|
||||
|
||||
# A bridge is a way of connecting multiple MQTT brokers together.
|
||||
# Create a new bridge using the "connection" option as described below. Set
|
||||
# options for the bridges using the remaining parameters. You must specify the
|
||||
# address and at least one topic to subscribe to.
|
||||
#
|
||||
# Each connection must have a unique name.
|
||||
#
|
||||
# The address line may have multiple host address and ports specified. See
|
||||
# below in the round_robin description for more details on bridge behaviour if
|
||||
# multiple addresses are used. Note that if you use an IPv6 address, then you
|
||||
# are required to specify a port.
|
||||
#
|
||||
# The direction that the topic will be shared can be chosen by
|
||||
# specifying out, in or both, where the default value is out.
|
||||
# The QoS level of the bridged communication can be specified with the next
|
||||
# topic option. The default QoS level is 0, to change the QoS the topic
|
||||
# direction must also be given.
|
||||
#
|
||||
# The local and remote prefix options allow a topic to be remapped when it is
|
||||
# bridged to/from the remote broker. This provides the ability to place a topic
|
||||
# tree in an appropriate location.
|
||||
#
|
||||
# For more details see the mosquitto.conf man page.
|
||||
#
|
||||
# Multiple topics can be specified per connection, but be careful
|
||||
# not to create any loops.
|
||||
#
|
||||
# If you are using bridges with cleansession set to false (the default), then
|
||||
# you may get unexpected behaviour from incoming topics if you change what
|
||||
# topics you are subscribing to. This is because the remote broker keeps the
|
||||
# subscription for the old topic. If you have this problem, connect your bridge
|
||||
# with cleansession set to true, then reconnect with cleansession set to false
|
||||
# as normal.
|
||||
#connection <name>
|
||||
#address <host>[:<port>] [<host>[:<port>]]
|
||||
#topic <topic> [[[out | in | both] qos-level] local-prefix remote-prefix]
|
||||
|
||||
# If you need to have the bridge connect over a particular network interface,
|
||||
# use bridge_bind_address to tell the bridge which local IP address the socket
|
||||
# should bind to, e.g. `bridge_bind_address 192.168.1.10`
|
||||
#bridge_bind_address
|
||||
|
||||
# If a bridge has topics that have "out" direction, the default behaviour is to
|
||||
# send an unsubscribe request to the remote broker on that topic. This means
|
||||
# that changing a topic direction from "in" to "out" will not keep receiving
|
||||
# incoming messages. Sending these unsubscribe requests is not always
|
||||
# desirable, setting bridge_attempt_unsubscribe to false will disable sending
|
||||
# the unsubscribe request.
|
||||
#bridge_attempt_unsubscribe true
|
||||
|
||||
# Set the version of the MQTT protocol to use with for this bridge. Can be one
|
||||
# of mqttv50, mqttv311 or mqttv31. Defaults to mqttv311.
|
||||
#bridge_protocol_version mqttv311
|
||||
|
||||
# Set the clean session variable for this bridge.
|
||||
# When set to true, when the bridge disconnects for any reason, all
|
||||
# messages and subscriptions will be cleaned up on the remote
|
||||
# broker. Note that with cleansession set to true, there may be a
|
||||
# significant amount of retained messages sent when the bridge
|
||||
# reconnects after losing its connection.
|
||||
# When set to false, the subscriptions and messages are kept on the
|
||||
# remote broker, and delivered when the bridge reconnects.
|
||||
#cleansession false
|
||||
|
||||
# Set the amount of time a bridge using the lazy start type must be idle before
|
||||
# it will be stopped. Defaults to 60 seconds.
|
||||
#idle_timeout 60
|
||||
|
||||
# Set the keepalive interval for this bridge connection, in
|
||||
# seconds.
|
||||
#keepalive_interval 60
|
||||
|
||||
# Set the clientid to use on the local broker. If not defined, this defaults to
|
||||
# 'local.<clientid>'. If you are bridging a broker to itself, it is important
|
||||
# that local_clientid and clientid do not match.
|
||||
#local_clientid
|
||||
|
||||
# If set to true, publish notification messages to the local and remote brokers
|
||||
# giving information about the state of the bridge connection. Retained
|
||||
# messages are published to the topic $SYS/broker/connection/<clientid>/state
|
||||
# unless the notification_topic option is used.
|
||||
# If the message is 1 then the connection is active, or 0 if the connection has
|
||||
# failed.
|
||||
# This uses the last will and testament feature.
|
||||
#notifications true
|
||||
|
||||
# Choose the topic on which notification messages for this bridge are
|
||||
# published. If not set, messages are published on the topic
|
||||
# $SYS/broker/connection/<clientid>/state
|
||||
#notification_topic
|
||||
|
||||
# Set the client id to use on the remote end of this bridge connection. If not
|
||||
# defined, this defaults to 'name.hostname' where name is the connection name
|
||||
# and hostname is the hostname of this computer.
|
||||
# This replaces the old "clientid" option to avoid confusion. "clientid"
|
||||
# remains valid for the time being.
|
||||
#remote_clientid
|
||||
|
||||
# Set the password to use when connecting to a broker that requires
|
||||
# authentication. This option is only used if remote_username is also set.
|
||||
# This replaces the old "password" option to avoid confusion. "password"
|
||||
# remains valid for the time being.
|
||||
#remote_password
|
||||
|
||||
# Set the username to use when connecting to a broker that requires
|
||||
# authentication.
|
||||
# This replaces the old "username" option to avoid confusion. "username"
|
||||
# remains valid for the time being.
|
||||
#remote_username
|
||||
|
||||
# Set the amount of time a bridge using the automatic start type will wait
|
||||
# until attempting to reconnect.
|
||||
# This option can be configured to use a constant delay time in seconds, or to
|
||||
# use a backoff mechanism based on "Decorrelated Jitter", which adds a degree
|
||||
# of randomness to when the restart occurs.
|
||||
#
|
||||
# Set a constant timeout of 20 seconds:
|
||||
# restart_timeout 20
|
||||
#
|
||||
# Set backoff with a base (start value) of 10 seconds and a cap (upper limit) of
|
||||
# 60 seconds:
|
||||
# restart_timeout 10 30
|
||||
#
|
||||
# Defaults to jitter with a base of 5 and cap of 30
|
||||
#restart_timeout 5 30
|
||||
|
||||
# If the bridge has more than one address given in the address/addresses
|
||||
# configuration, the round_robin option defines the behaviour of the bridge on
|
||||
# a failure of the bridge connection. If round_robin is false, the default
|
||||
# value, then the first address is treated as the main bridge connection. If
|
||||
# the connection fails, the other secondary addresses will be attempted in
|
||||
# turn. Whilst connected to a secondary bridge, the bridge will periodically
|
||||
# attempt to reconnect to the main bridge until successful.
|
||||
# If round_robin is true, then all addresses are treated as equals. If a
|
||||
# connection fails, the next address will be tried and if successful will
|
||||
# remain connected until it fails
|
||||
#round_robin false
|
||||
|
||||
# Set the start type of the bridge. This controls how the bridge starts and
|
||||
# can be one of three types: automatic, lazy and once. Note that RSMB provides
|
||||
# a fourth start type "manual" which isn't currently supported by mosquitto.
|
||||
#
|
||||
# "automatic" is the default start type and means that the bridge connection
|
||||
# will be started automatically when the broker starts and also restarted
|
||||
# after a short delay (30 seconds) if the connection fails.
|
||||
#
|
||||
# Bridges using the "lazy" start type will be started automatically when the
|
||||
# number of queued messages exceeds the number set with the "threshold"
|
||||
# parameter. It will be stopped automatically after the time set by the
|
||||
# "idle_timeout" parameter. Use this start type if you wish the connection to
|
||||
# only be active when it is needed.
|
||||
#
|
||||
# A bridge using the "once" start type will be started automatically when the
|
||||
# broker starts but will not be restarted if the connection fails.
|
||||
#start_type automatic
|
||||
|
||||
# Set the number of messages that need to be queued for a bridge with lazy
|
||||
# start type to be restarted. Defaults to 10 messages.
|
||||
# Must be less than max_queued_messages.
|
||||
#threshold 10
|
||||
|
||||
# If try_private is set to true, the bridge will attempt to indicate to the
|
||||
# remote broker that it is a bridge not an ordinary client. If successful, this
|
||||
# means that loop detection will be more effective and that retained messages
|
||||
# will be propagated correctly. Not all brokers support this feature so it may
|
||||
# be necessary to set try_private to false if your bridge does not connect
|
||||
# properly.
|
||||
#try_private true
|
||||
|
||||
# Some MQTT brokers do not allow retained messages. MQTT v5 gives a mechanism
|
||||
# for brokers to tell clients that they do not support retained messages, but
|
||||
# this is not possible for MQTT v3.1.1 or v3.1. If you need to bridge to a
|
||||
# v3.1.1 or v3.1 broker that does not support retained messages, set the
|
||||
# bridge_outgoing_retain option to false. This will remove the retain bit on
|
||||
# all outgoing messages to that bridge, regardless of any other setting.
|
||||
#bridge_outgoing_retain true
|
||||
|
||||
# If you wish to restrict the size of messages sent to a remote bridge, use the
|
||||
# bridge_max_packet_size option. This sets the maximum number of bytes for
|
||||
# the total message, including headers and payload.
|
||||
# Note that MQTT v5 brokers may provide their own maximum-packet-size property.
|
||||
# In this case, the smaller of the two limits will be used.
|
||||
# Set to 0 for "unlimited".
|
||||
#bridge_max_packet_size 0
|
||||
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Certificate based SSL/TLS support
|
||||
# -----------------------------------------------------------------
|
||||
# Either bridge_cafile or bridge_capath must be defined to enable TLS support
|
||||
# for this bridge.
|
||||
# bridge_cafile defines the path to a file containing the
|
||||
# Certificate Authority certificates that have signed the remote broker
|
||||
# certificate.
|
||||
# bridge_capath defines a directory that will be searched for files containing
|
||||
# the CA certificates. For bridge_capath to work correctly, the certificate
|
||||
# files must have ".crt" as the file ending and you must run "openssl rehash
|
||||
# <path to capath>" each time you add/remove a certificate.
|
||||
#bridge_cafile
|
||||
#bridge_capath
|
||||
|
||||
|
||||
# If the remote broker has more than one protocol available on its port, e.g.
|
||||
# MQTT and WebSockets, then use bridge_alpn to configure which protocol is
|
||||
# requested. Note that WebSockets support for bridges is not yet available.
|
||||
#bridge_alpn
|
||||
|
||||
# When using certificate based encryption, bridge_insecure disables
|
||||
# verification of the server hostname in the server certificate. This can be
|
||||
# useful when testing initial server configurations, but makes it possible for
|
||||
# a malicious third party to impersonate your server through DNS spoofing, for
|
||||
# example. Use this option in testing only. If you need to resort to using this
|
||||
# option in a production environment, your setup is at fault and there is no
|
||||
# point using encryption.
|
||||
#bridge_insecure false
|
||||
|
||||
# Path to the PEM encoded client certificate, if required by the remote broker.
|
||||
#bridge_certfile
|
||||
|
||||
# Path to the PEM encoded client private key, if required by the remote broker.
|
||||
#bridge_keyfile
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# PSK based SSL/TLS support
|
||||
# -----------------------------------------------------------------
|
||||
# Pre-shared-key encryption provides an alternative to certificate based
|
||||
# encryption. A bridge can be configured to use PSK with the bridge_identity
|
||||
# and bridge_psk options. These are the client PSK identity, and pre-shared-key
|
||||
# in hexadecimal format with no "0x". Only one of certificate and PSK based
|
||||
# encryption can be used on one
|
||||
# bridge at once.
|
||||
#bridge_identity
|
||||
#bridge_psk
|
||||
|
||||
|
||||
# =================================================================
|
||||
# External config files
|
||||
# =================================================================
|
||||
|
||||
# External configuration files may be included by using the
|
||||
# include_dir option. This defines a directory that will be searched
|
||||
# for config files. All files that end in '.conf' will be loaded as
|
||||
# a configuration file. It is best to have this as the last option
|
||||
# in the main file. This option will only be processed from the main
|
||||
# configuration file. The directory specified must not contain the
|
||||
# main configuration file.
|
||||
# Files within include_dir will be loaded sorted in case-sensitive
|
||||
# alphabetical order, with capital letters ordered first. If this option is
|
||||
# given multiple times, all of the files from the first instance will be
|
||||
# processed before the next instance. See the man page for examples.
|
||||
#include_dir
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM smeagolworms4/mqtt-explorer:browser-1.0.3
|
||||
|
||||
COPY ./settings.json /mqtt-explorer/config/settings.json
|
||||
|
||||
ARG TARGETARCH
|
||||
COPY entrypoint_wrapper.sh /entrypoint_wrapper.sh
|
||||
RUN if [ ${TARGETARCH} != "amd64" ]; then \
|
||||
mv /entrypoint.sh /wrapped_entrypoint.sh; \
|
||||
cp /entrypoint_wrapper.sh /entrypoint.sh; \
|
||||
fi; \
|
||||
rm /entrypoint_wrapper.sh
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
CMD node node-server/server/dist/node-server/server/src/index.js \
|
||||
--http-port=$HTTP_PORT \
|
||||
--config-path=$CONFIG_PATH \
|
||||
--http-user=$HTTP_USER \
|
||||
--http-password=$HTTP_PASSWORD\
|
||||
--ssl-key-path=$SSL_KEY_PATH\
|
||||
--ssl-cert-path=$SSL_CERT_PATH
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
|
||||
# ---------------------------------------------
|
||||
# Architecture Warning Wrapper Script
|
||||
#
|
||||
# This script is used as an entrypoint wrapper to emit a warning
|
||||
# when the container is not running on the officially supported
|
||||
# amd64 (x86_64) architecture.
|
||||
#
|
||||
# It checks for the presence of a wrapped entrypoint script
|
||||
# (/wrapped_entrypoint.sh) and executes it if found; otherwise,
|
||||
# it falls back to executing the provided command directly.
|
||||
#
|
||||
# The warning is shown both before and after the wrapped command
|
||||
# to ensure visibility.
|
||||
# ---------------------------------------------
|
||||
|
||||
function print_warning {
|
||||
echo -e "\033[0;31m"
|
||||
echo "-------------------------------------------------------------"
|
||||
echo "⚠️ WARNING: Unsupported Architecture Detected"
|
||||
echo
|
||||
echo "This Docker image is not running on the amd64 (x86_64) architecture."
|
||||
echo "It is recommended to use the amd64-based image for full compatibility."
|
||||
echo "Other architectures are not officially supported and may cause issues."
|
||||
echo
|
||||
echo "-------------------------------------------------------------"
|
||||
echo -e "\033[0m"
|
||||
}
|
||||
|
||||
print_warning
|
||||
|
||||
if [ -f /wrapped_entrypoint.sh ]; then
|
||||
exec /wrapped_entrypoint.sh "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"ConnectionManager_connections": {
|
||||
"mqtt-server": {
|
||||
"configVersion": 1,
|
||||
"certValidation": true,
|
||||
"clientId": "mqtt-explorer-e1085971",
|
||||
"id": "mqtt-server",
|
||||
"name": "MQTT Server",
|
||||
"encryption": false,
|
||||
"subscriptions": [
|
||||
{
|
||||
"topic": "#",
|
||||
"qos": 0
|
||||
},
|
||||
{
|
||||
"topic": "$SYS/#",
|
||||
"qos": 0
|
||||
}
|
||||
],
|
||||
"type": "mqtt",
|
||||
"host": "mqtt-server",
|
||||
"port": 1883,
|
||||
"protocol": "mqtt"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM nodered/node-red:4.1.2
|
||||
RUN npm install node-red-dashboard@3.6.6
|
||||
RUN npm install node-red-contrib-ui-actions@0.1.8
|
||||
RUN npm install node-red-node-ui-table@0.4.5
|
||||
RUN npm install node-red-contrib-ui-level@0.1.46
|
||||
|
||||
COPY nodered-settings.js /data/settings.js
|
||||
|
||||
USER root
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
ARG TARGETARCH
|
||||
COPY entrypoint_wrapper.sh /entrypoint_wrapper.sh
|
||||
RUN if [ ${TARGETARCH} != "amd64" ]; then \
|
||||
mv /entrypoint.sh /wrapped_entrypoint.sh; \
|
||||
cp /entrypoint_wrapper.sh /entrypoint.sh; \
|
||||
fi; \
|
||||
rm /entrypoint_wrapper.sh
|
||||
USER node-red
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
10
tools/EVerest-main/applications/containers/nodered/entrypoint.sh
Executable file
10
tools/EVerest-main/applications/containers/nodered/entrypoint.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec npm \
|
||||
--no-update-notifier \
|
||||
--no-fund \
|
||||
start \
|
||||
--cache /data/.npm \
|
||||
-- \
|
||||
--userDir /data \
|
||||
"$@"
|
||||
37
tools/EVerest-main/applications/containers/nodered/entrypoint_wrapper.sh
Executable file
37
tools/EVerest-main/applications/containers/nodered/entrypoint_wrapper.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
|
||||
# ---------------------------------------------
|
||||
# Architecture Warning Wrapper Script
|
||||
#
|
||||
# This script is used as an entrypoint wrapper to emit a warning
|
||||
# when the container is not running on the officially supported
|
||||
# amd64 (x86_64) architecture.
|
||||
#
|
||||
# It checks for the presence of a wrapped entrypoint script
|
||||
# (/wrapped_entrypoint.sh) and executes it if found; otherwise,
|
||||
# it falls back to executing the provided command directly.
|
||||
#
|
||||
# The warning is shown both before and after the wrapped command
|
||||
# to ensure visibility.
|
||||
# ---------------------------------------------
|
||||
|
||||
function print_warning {
|
||||
echo -e "\033[0;31m"
|
||||
echo "-------------------------------------------------------------"
|
||||
echo "⚠️ WARNING: Unsupported Architecture Detected"
|
||||
echo
|
||||
echo "This Docker image is not running on the amd64 (x86_64) architecture."
|
||||
echo "It is recommended to use the amd64-based image for full compatibility."
|
||||
echo "Other architectures are not officially supported and may cause issues."
|
||||
echo
|
||||
echo "-------------------------------------------------------------"
|
||||
echo -e "\033[0m"
|
||||
}
|
||||
|
||||
print_warning
|
||||
|
||||
if [ -f /wrapped_entrypoint.sh ]; then
|
||||
exec /wrapped_entrypoint.sh "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
// Flow file location
|
||||
flowFile: 'flows.json',
|
||||
|
||||
// Enable projects
|
||||
enableProjects: process.env.NODE_RED_ENABLE_PROJECTS === 'true',
|
||||
|
||||
// HTTP settings
|
||||
httpNodeRoot: '/',
|
||||
httpAdminRoot: '/',
|
||||
|
||||
// Logging
|
||||
logging: {
|
||||
console: {
|
||||
level: "info",
|
||||
metrics: false,
|
||||
audit: false
|
||||
}
|
||||
}
|
||||
};
|
||||
30
tools/EVerest-main/applications/containers/steve/Dockerfile
Normal file
30
tools/EVerest-main/applications/containers/steve/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM maven:3.6.1-jdk-11
|
||||
|
||||
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
|
||||
|
||||
WORKDIR /steve
|
||||
|
||||
ENV DOCKERIZE_VERSION v0.6.1
|
||||
RUN wget --no-verbose https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
|
||||
&& tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
|
||||
&& rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz
|
||||
|
||||
RUN wget -qO- https://github.com/steve-community/steve/archive/steve-3.6.0.tar.gz | tar xz --strip-components=1
|
||||
COPY main.properties src/main/resources/config/docker
|
||||
COPY init.sh .
|
||||
COPY keystore.jks .
|
||||
|
||||
ARG TARGETARCH
|
||||
COPY entrypoint_wrapper.sh /entrypoint_wrapper.sh
|
||||
RUN if [ ${TARGETARCH} != "amd64" ]; then \
|
||||
ln -s /usr/local/bin/mvn-entrypoint.sh /wrapped_entrypoint.sh; \
|
||||
cp /entrypoint_wrapper.sh /entrypoint.sh; \
|
||||
else \
|
||||
ln -s /usr/local/bin/mvn-entrypoint.sh /entrypoint.sh; \
|
||||
fi; \
|
||||
rm /entrypoint_wrapper.sh
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
CMD /steve/init.sh
|
||||
|
||||
|
||||
|
||||
37
tools/EVerest-main/applications/containers/steve/entrypoint_wrapper.sh
Executable file
37
tools/EVerest-main/applications/containers/steve/entrypoint_wrapper.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ---------------------------------------------
|
||||
# Architecture Warning Wrapper Script
|
||||
#
|
||||
# This script is used as an entrypoint wrapper to emit a warning
|
||||
# when the container is not running on the officially supported
|
||||
# amd64 (x86_64) architecture.
|
||||
#
|
||||
# It checks for the presence of a wrapped entrypoint script
|
||||
# (/wrapped_entrypoint.sh) and executes it if found; otherwise,
|
||||
# it falls back to executing the provided command directly.
|
||||
#
|
||||
# The warning is shown both before and after the wrapped command
|
||||
# to ensure visibility.
|
||||
# ---------------------------------------------
|
||||
|
||||
function print_warning {
|
||||
echo -e "\033[0;31m"
|
||||
echo "-------------------------------------------------------------"
|
||||
echo "⚠️ WARNING: Unsupported Architecture Detected"
|
||||
echo
|
||||
echo "This Docker image is not running on the amd64 (x86_64) architecture."
|
||||
echo "It is recommended to use the amd64-based image for full compatibility."
|
||||
echo "Other architectures are not officially supported and may cause issues."
|
||||
echo
|
||||
echo "-------------------------------------------------------------"
|
||||
echo -e "\033[0m"
|
||||
}
|
||||
|
||||
print_warning
|
||||
|
||||
if [ -f /wrapped_entrypoint.sh ]; then
|
||||
exec /wrapped_entrypoint.sh "$@"
|
||||
else
|
||||
exec "$@"
|
||||
fi
|
||||
10
tools/EVerest-main/applications/containers/steve/init.sh
Executable file
10
tools/EVerest-main/applications/containers/steve/init.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
set -e # exit on any error
|
||||
dockerize -wait tcp://ocpp-db:3306 -timeout 60s
|
||||
|
||||
if [ ! -f ".buildsuccess" ]; then
|
||||
mvn clean package -Pdocker -Djdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2"
|
||||
touch .buildsuccess
|
||||
fi
|
||||
|
||||
java -jar target/steve.jar
|
||||
BIN
tools/EVerest-main/applications/containers/steve/keystore.jks
Normal file
BIN
tools/EVerest-main/applications/containers/steve/keystore.jks
Normal file
Binary file not shown.
@@ -0,0 +1,57 @@
|
||||
# Just to be backwards compatible with previous versions, this is set to "steve",
|
||||
# since there might be already configured chargepoints expecting the older path.
|
||||
# Otherwise, might as well be changed to something else or be left empty.
|
||||
#
|
||||
context.path = steve
|
||||
|
||||
# Database configuration
|
||||
#
|
||||
db.ip = ocpp-db
|
||||
db.port = 3306
|
||||
db.schema = ocpp-db
|
||||
db.user = ocpp
|
||||
db.password = ocpp
|
||||
|
||||
# Credentials for Web interface access
|
||||
#
|
||||
auth.user = admin
|
||||
auth.password = 1234
|
||||
|
||||
# Jetty configuration
|
||||
#
|
||||
server.host = 0.0.0.0
|
||||
server.gzip.enabled = false
|
||||
|
||||
# Jetty HTTP configuration
|
||||
#
|
||||
http.enabled = true
|
||||
http.port = 8180
|
||||
|
||||
# Jetty HTTPS configuration
|
||||
#
|
||||
https.enabled = true
|
||||
https.port = 8443
|
||||
keystore.path = /steve/keystore.jks
|
||||
keystore.password = 123456
|
||||
|
||||
# When the WebSocket/Json charge point opens more than one WebSocket connection,
|
||||
# we need a mechanism/strategy to select one of them for outgoing requests.
|
||||
# For allowed values see de.rwth.idsg.steve.ocpp.ws.custom.WsSessionSelectStrategyEnum.
|
||||
#
|
||||
ws.session.select.strategy = ALWAYS_LAST
|
||||
|
||||
# if BootNotification messages arrive (SOAP) or WebSocket connection attempts are made (JSON) from unknown charging
|
||||
# stations, we reject these charging stations, because stations with these chargeBoxIds were NOT inserted into database
|
||||
# beforehand. by setting this property to true, this behaviour can be modified to automatically insert unknown
|
||||
# stations into database and accept their requests.
|
||||
#
|
||||
# CAUTION: setting this property to true is very dangerous, because we will accept EVERY BootNotification or WebSocket
|
||||
# connection attempt from ANY sender as long as the sender knows the URL and sends a valid message.
|
||||
#
|
||||
auto.register.unknown.stations = false
|
||||
|
||||
### DO NOT MODIFY ###
|
||||
steve.version = ${project.version}
|
||||
git.describe = ${git.commit.id.describe}
|
||||
db.sql.logging = false
|
||||
profile = prod
|
||||
3
tools/EVerest-main/applications/dependency_manager/.gitignore
vendored
Normal file
3
tools/EVerest-main/applications/dependency_manager/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build
|
||||
__pycache__
|
||||
*.egg-info
|
||||
@@ -0,0 +1,24 @@
|
||||
ev_setup_cmake_variables_python_wheel()
|
||||
|
||||
ev_add_pip_package(
|
||||
NAME edm
|
||||
SOURCE_DIRECTORY .
|
||||
)
|
||||
|
||||
ev_is_python_venv_active(
|
||||
RESULT_VAR IS_PYTHON_VENV_ACTIVE
|
||||
)
|
||||
|
||||
if(NOT ${IS_PYTHON_VENV_ACTIVE})
|
||||
message(WARNING "Python venv is not active. Please ensure that edm is available in your environment.")
|
||||
else()
|
||||
get_target_property(SOURCE_DIRECTORY ev_pip_package_edm SOURCE_DIRECTORY)
|
||||
message(STATUS "Installing edm from: ${SOURCE_DIRECTORY}")
|
||||
ev_pip_install_local(
|
||||
PACKAGE_NAME "edm"
|
||||
PACKAGE_SOURCE_DIRECTORY "${SOURCE_DIRECTORY}"
|
||||
)
|
||||
unset(EVEREST_DEPENDENCY_MANAGER CACHE)
|
||||
find_program(EVEREST_DEPENDENCY_MANAGER edm HINTS ${EV_ACTIVATE_PYTHON_VENV_PATH_TO_VENV}/bin REQUIRED)
|
||||
message(STATUS "Using edm from: ${EDM}")
|
||||
endif()
|
||||
201
tools/EVerest-main/applications/dependency_manager/LICENSE
Normal file
201
tools/EVerest-main/applications/dependency_manager/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
232
tools/EVerest-main/applications/dependency_manager/README.md
Normal file
232
tools/EVerest-main/applications/dependency_manager/README.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# Dependency Manager for EVerest
|
||||
|
||||
- [Dependency Manager for EVerest](#dependency-manager-for-everest)
|
||||
- [Install and Quick Start](#install-and-quick-start)
|
||||
- [Installing edm](#installing-edm)
|
||||
- [Enabling CPM_SOURCE_CACHE](#enabling-cpm_source_cache)
|
||||
- [Python packages needed to run edm](#python-packages-needed-to-run-edm)
|
||||
- [Setting up CMake integration](#setting-up-cmake-integration)
|
||||
- [Setting up a workspace](#setting-up-a-workspace)
|
||||
- [Updating a workspace](#updating-a-workspace)
|
||||
- [Using the EDM CMake module and dependencies.yaml](#using-the-edm-cmake-module-and-dependenciesyaml)
|
||||
- [Modifying dependencies](#modifying-dependencies)
|
||||
- [Create a workspace config from an existing directory tree](#create-a-workspace-config-from-an-existing-directory-tree)
|
||||
- [Git information at a glance](#git-information-at-a-glance)
|
||||
|
||||
## Install and Quick Start
|
||||
To install the **edm** dependency manager for EVerest you have to perform the following steps.
|
||||
|
||||
Please make sure you are running a sufficiently recent version of **Python3 (>=3.6)** and that you are able to install Python packages from source. Usually you just have to ensure that you have **pip**, **setuptools** and **wheel** available. Refer to [the Python *Installing Packages* documentation](https://packaging.python.org/tutorials/installing-packages/#requirements-for-installing-packages) for indepth guidance if any problems arise.
|
||||
|
||||
```bash
|
||||
python3 -m pip install --upgrade pip setuptools wheel
|
||||
```
|
||||
|
||||
### Installing edm
|
||||
Now you can clone *this repository* and install **edm**:
|
||||
|
||||
(make sure you have set up your [ssh key](https://www.atlassian.com/git/tutorials/git-ssh) in github first!)
|
||||
|
||||
```bash
|
||||
git clone git@github.com:EVerest/everest-dev-environment.git
|
||||
cd everest-dev-environment/dependency_manager
|
||||
python3 -m pip install .
|
||||
edm --config ../everest-complete.yaml --workspace ~/checkout/everest-workspace
|
||||
```
|
||||
|
||||
The last command registers the [**EDM** CMake module](#setting-up-cmake-integration) and creates a workspace in the *~/checkout/everest-workspace* directory from [a config that is shipped with this repository](../everest-complete.yaml).
|
||||
The workspace will have the following structure containing all current dependencies for EVerest:
|
||||
```bash
|
||||
everest-workspace/
|
||||
├── EVerest
|
||||
├── everest-deploy-devkit
|
||||
├── everest-dev-environment
|
||||
├── everest-framework
|
||||
├── everest-utils
|
||||
├── liblog
|
||||
├── libmodbus
|
||||
├── libocpp
|
||||
├── libsunspec
|
||||
├── libtimer
|
||||
├── open-plc-utils
|
||||
├── RISE-V2G
|
||||
└── workspace-config.yaml
|
||||
```
|
||||
The *workspace-config.yaml* contains a copy of the config that was used to create this workspace.
|
||||
|
||||
### Enabling CPM_SOURCE_CACHE
|
||||
The **edm** dependency manager uses [CPM](https://github.com/cpm-cmake/CPM.cmake) for its CMake integration.
|
||||
This means you *can* and **should** set the *CPM_SOURCE_CACHE* environment variable. This makes sure that dependencies that you do not manage in the workspace are not re-downloaded multiple times. For detailed information and other useful environment variables please refer to the [CPM Documentation](https://github.com/cpm-cmake/CPM.cmake/blob/master/README.md#CPM_SOURCE_CACHE).
|
||||
```bash
|
||||
export CPM_SOURCE_CACHE=$HOME/.cache/CPM
|
||||
```
|
||||
|
||||
### Python packages needed to run edm
|
||||
The following Python3 packages are needed to run the **edm** dependency manager.
|
||||
If you installed **edm** using the guide above they were already installed automatically.
|
||||
|
||||
- Python >= 3.6
|
||||
- Jinja2 >= 3.0
|
||||
- PyYAML >= 5.4
|
||||
|
||||
## Setting up and updating a workspace
|
||||
For letting **edm** do the work of setting up an initial EVerest workspace,
|
||||
do this:
|
||||
|
||||
```bash
|
||||
edm init --workspace ~/checkout/everest-workspace
|
||||
```
|
||||
If you are currently in the *everest-workspace* directory the following command has the same effect:
|
||||
|
||||
```bash
|
||||
edm init
|
||||
```
|
||||
|
||||
For using a dedicated release version, you can do this:
|
||||
|
||||
```bash
|
||||
edm init 2023.7.0
|
||||
```
|
||||
|
||||
In this example, version 2023.7.0 is pulled from the server. This will only work if
|
||||
you previous code is not in a "dirty" state.
|
||||
|
||||
## Using the EDM CMake module and dependencies.yaml
|
||||
To use **edm** from CMake you have to add the following line to the top-level *CMakeLists.txt* file in the respective source repository:
|
||||
```cmake
|
||||
find_package(EDM REQUIRED)
|
||||
```
|
||||
The **EDM** CMake module will be discovered automatically if you [registered the CMake module in the way it described in the *Setting up CMake integration* section of this readme](#setting-up-cmake-integration).
|
||||
|
||||
To define dependencies you can now add a **dependencies.yaml** file to your source repository. It should look like this:
|
||||
```yaml
|
||||
---
|
||||
liblog:
|
||||
git: git@github.com:EVerest/liblog.git
|
||||
git_tag: main
|
||||
options: ["BUILD_EXAMPLES OFF"]
|
||||
libtimer:
|
||||
git: git@github.com:EVerest/libtimer.git
|
||||
git_tag: main
|
||||
options: ["BUILD_EXAMPLES OFF"]
|
||||
|
||||
```
|
||||
|
||||
If you want to conditionally include some dependencies, eg. for testing, you can do this in the following way:
|
||||
```yaml
|
||||
catch2:
|
||||
git: https://github.com/catchorg/Catch2.git
|
||||
git_tag: v3.4.0
|
||||
cmake_condition: "BUILD_TESTING"
|
||||
|
||||
```
|
||||
Here *cmake_condition* can be any string that CMake can use in an if() block. Please be aware that any variables you use here must be defined before a call to *evc_setup_edm()* is made in your CMakeLists.txt
|
||||
|
||||
## Selective library consumption
|
||||
|
||||
If your project only needs specific everest-core libraries (e.g. `liblog`, `everest-util`, `everest-io`, `libocpp`) without building the full module framework, use the `EVEREST_LIBS_ONLY` and `EVEREST_INCLUDE_LIBS` CMake options.
|
||||
|
||||
In your project's `dependencies.yaml`:
|
||||
```yaml
|
||||
everest-core:
|
||||
git: https://github.com/EVerest/everest-core.git
|
||||
git_tag: 2026.02.0
|
||||
options:
|
||||
- "EVEREST_LIBS_ONLY ON"
|
||||
- "EVEREST_INCLUDE_LIBS log;util;io"
|
||||
```
|
||||
|
||||
Or directly via CMake:
|
||||
```bash
|
||||
cmake -S . -B build \
|
||||
-DEVEREST_LIBS_ONLY=ON \
|
||||
-DEVEREST_INCLUDE_LIBS="log;util;io"
|
||||
```
|
||||
|
||||
| Option | Default | Description |
|
||||
|---|---|---|
|
||||
| `EVEREST_LIBS_ONLY` | OFF | Skip modules, applications, config — only build libraries |
|
||||
| `EVEREST_INCLUDE_LIBS` | (empty) | Semicolon-separated allowlist; transitive deps auto-resolved |
|
||||
| `EVEREST_EXCLUDE_LIBS` | (empty) | Semicolon-separated blocklist of libraries to skip |
|
||||
|
||||
Transitive dependencies are resolved automatically. For example, `EVEREST_INCLUDE_LIBS="ocpp"` will also build `log`, `timer`, `evse_security`, `sqlite`, and `cbv2g`.
|
||||
|
||||
See the [EDM documentation](../../docs/source/explanation/dev-tools/edm.rst) for the full list of available libraries and their dependency chains.
|
||||
|
||||
## Modifying dependencies
|
||||
|
||||
To change dependency git URLs you can set the *EVEREST_MODIFY_DEPENDENCIES_URLS* environment variable to a string containing prefixes and replacements delimited by whitespace characters.
|
||||
For example:
|
||||
```bash
|
||||
EVEREST_MODIFY_DEPENDENCIES_URLS="prefix=https://github.com/EVerest/ replace=git@github.com:EVerest/"
|
||||
```
|
||||
This would change all dependency git URLs that start with *https://github.com/EVerest/* to *git@github.com:EVerest/*.
|
||||
|
||||
Multiple prefix and replace pairs can be chained together delimited by whitespace characters.
|
||||
For example:
|
||||
```bash
|
||||
EVEREST_MODIFY_DEPENDENCIES_URLS="prefix=https://github.com/EVerest/ replace=git@github.com:EVerest/ prefix=https://github.com/EVerest/everest-framework.git replace=https://github.com/EVerest/everest-framework.git"
|
||||
```
|
||||
This would change all dependency git URLs that start with *https://github.com/EVerest/* to *git@github.com:EVerest/* as well as keeping the dependency https URL of *https://github.com/EVerest/everest-framework.git* as *https://github.com/EVerest/everest-framework.git*.
|
||||
|
||||
Additionally you can set the *EVEREST_MODIFY_DEPENDENCIES* environment variable to a file containing modifications to the projects dependencies.yaml files when running cmake:
|
||||
|
||||
```bash
|
||||
EVEREST_MODIFY_DEPENDENCIES=../dependencies_modified.yaml cmake -S . -B build
|
||||
```
|
||||
|
||||
The *dependencies_modified.yaml* file can contain something along these lines:
|
||||
|
||||
```yaml
|
||||
nlohmann_json:
|
||||
git: null # this makes edm look for nlohmann_json via find_package
|
||||
libfmt:
|
||||
rename: fmt # if find_package needs a different dependency name you can rename it
|
||||
git: null
|
||||
catch2:
|
||||
git_tag: v1.2.3 # if you want to select a different git tag for a build this is also possible
|
||||
```
|
||||
|
||||
## Create a workspace config from an existing directory tree
|
||||
Suppose you already have a directory tree that you want to save into a config file.
|
||||
You can do this with the following command:
|
||||
```bash
|
||||
edm --create-config custom-config.yaml
|
||||
```
|
||||
|
||||
This is a short form of
|
||||
```bash
|
||||
edm --create-config custom-config.yaml --include-remotes git@github.com:EVerest/*
|
||||
```
|
||||
and only includes repositories from the *EVerest* namespace. You can add as many remotes to this list as you want.
|
||||
|
||||
For example if you only want to include certain repositories you can use the following command.
|
||||
```bash
|
||||
edm --create-config custom-config.yaml --include-remotes git@github.com:EVerest/everest* git@github.com:EVerest/liblog.git
|
||||
```
|
||||
|
||||
If you want to include all repositories, including external dependencies, in the config you can use the following command.
|
||||
```bash
|
||||
edm --create-config custom-config.yaml --external-in-config
|
||||
```
|
||||
|
||||
## Git information at a glance
|
||||
You can get a list of all git repositories in the current directory and their state using the following command.
|
||||
```bash
|
||||
edm --git-info --git-fetch
|
||||
```
|
||||
If you want to know the state of all repositories in a workspace you can use the following command.
|
||||
```bash
|
||||
edm --workspace ~/checkout/everest-workspace --git-info --git-fetch
|
||||
```
|
||||
|
||||
This creates output that is similar to the following example.
|
||||
```bash
|
||||
[edm]: Git info for "~/checkout/everest-workspace":
|
||||
[edm]: Using git-fetch to update remote information. This might take a few seconds.
|
||||
[edm]: "everest-dev-environment" @ branch: main [remote: origin/main] [behind 6] [clean]
|
||||
[edm]: "everest-framework" @ branch: main [remote: origin/main] [dirty]
|
||||
[edm]: "everest-deploy-devkit" @ branch: main [remote: origin/main] [clean]
|
||||
[edm]: "libtimer" @ branch: main [remote: origin/main] [dirty]
|
||||
[edm]: 2/4 repositories are dirty.
|
||||
```
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
## Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
##
|
||||
echo "generating bash-completion file"
|
||||
SRC_DIR="$(dirname "${BASH_SOURCE[0]}")/src"
|
||||
echo "Using module found in ${SRC_DIR}"
|
||||
cd "${SRC_DIR}"
|
||||
BASH_COMPLETION_FILE_DIR="$(pwd)"
|
||||
BASH_COMPLETION_FILE="${BASH_COMPLETION_FILE_DIR}/edm_tool/edm-completion.bash"
|
||||
shtab --shell=bash -u edm_tool.get_parser --prog edm > "${BASH_COMPLETION_FILE}"
|
||||
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
@@ -0,0 +1,3 @@
|
||||
Jinja2>=2.11
|
||||
PyYAML>=5.3
|
||||
requests>=2
|
||||
38
tools/EVerest-main/applications/dependency_manager/setup.cfg
Normal file
38
tools/EVerest-main/applications/dependency_manager/setup.cfg
Normal file
@@ -0,0 +1,38 @@
|
||||
[metadata]
|
||||
name = edm_tool
|
||||
version = attr: edm_tool.__version__
|
||||
description= A simple dependency manager
|
||||
long_description = file: README.md
|
||||
long_description_content_type= text/markdown
|
||||
url= https://github.com/EVerest/everest-dev-environment
|
||||
author = Kai-Uwe Hermann
|
||||
author_email = kai-uwe.hermann@pionix.de
|
||||
classifiers =
|
||||
Development Status :: 3 - Alpha
|
||||
Intended Audience :: Developers
|
||||
Topic :: Software Development :: Build Tools
|
||||
License :: OSI Approved :: Apache Software License
|
||||
|
||||
[options]
|
||||
packages = edm_tool
|
||||
package_dir =
|
||||
= src
|
||||
python_requires = >=3.6
|
||||
install_requires =
|
||||
Jinja2>=2.11
|
||||
PyYAML>=5.3
|
||||
requests>=2
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
edm = edm_tool:main
|
||||
|
||||
[options.package_data]
|
||||
edm_tool =
|
||||
templates/cpm.jinja
|
||||
cmake/CPM.cmake
|
||||
cmake/EDMConfig.cmake
|
||||
edm-completion.bash
|
||||
|
||||
[pycodestyle]
|
||||
max-line-length = 120
|
||||
@@ -0,0 +1,9 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2020 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
"""Everest Dependency Manager."""
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
# see setup.cfg
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
#
|
||||
"""Everest Dependency Manager."""
|
||||
from edm_tool import edm
|
||||
|
||||
__version__ = "0.8.0"
|
||||
|
||||
|
||||
def get_parser():
|
||||
"""Return the command line parser."""
|
||||
return edm.get_parser(__version__)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entrypoint of edm."""
|
||||
edm.main(get_parser())
|
||||
88
tools/EVerest-main/applications/dependency_manager/src/edm_tool/bazel.py
Executable file
88
tools/EVerest-main/applications/dependency_manager/src/edm_tool/bazel.py
Executable file
@@ -0,0 +1,88 @@
|
||||
"Bazel related functions for edm_tool."
|
||||
import yaml
|
||||
from typing import List, Optional, Dict
|
||||
|
||||
|
||||
def _format_optional_string(value: Optional[str]):
|
||||
"""Formats a string value as a string literal (with quotes) or `None` if the value is None."""
|
||||
if value is None:
|
||||
return "None"
|
||||
return f'"{value}"'
|
||||
|
||||
|
||||
def _is_commit(revision: str):
|
||||
# Revision is a commit if it is a hexadecimal 40-character string
|
||||
return len(revision) == 40 and all(c in "0123456789abcdef" for c in revision.lower())
|
||||
|
||||
def _get_depname_for_label(label: str) -> str:
|
||||
build, depname, bazel = label.split(":")[1].split(".")
|
||||
if build != "BUILD" or bazel != "bazel":
|
||||
raise ValueError(f"Invalid build file name: {label}")
|
||||
return depname
|
||||
|
||||
def _parse_build_file_labels(labels: Optional[List[str]]) -> Dict[str, str]:
|
||||
# For easier matching of build files with dependencies
|
||||
# we convert the list of build files:
|
||||
# ```
|
||||
# [
|
||||
# "@workspace//path/to/build:BUILD.<depname>.bazel",
|
||||
# ...
|
||||
# ]
|
||||
# ```
|
||||
# into a dictionary:
|
||||
# ```
|
||||
# {
|
||||
# "<depname>": "@workspace//path/to/build:BUILD.<depname>.bazel",
|
||||
# ...
|
||||
# }
|
||||
# ```
|
||||
# and check that all build files have proper names.
|
||||
if labels is None:
|
||||
return {}
|
||||
|
||||
return dict((_get_depname_for_label(label), label) for label in labels)
|
||||
|
||||
|
||||
def generate_deps(args):
|
||||
"Parse the dependencies.yaml and print content of *.bzl file to stdout."
|
||||
with open(args.dependencies_yaml, 'r', encoding='utf-8') as f:
|
||||
deps = yaml.safe_load(f)
|
||||
|
||||
build_files = _parse_build_file_labels(args.build_file)
|
||||
|
||||
for name in build_files:
|
||||
if name not in deps:
|
||||
raise ValueError(f"Build file {name} does not have a corresponding dependency in {args.dependencies_yaml}")
|
||||
|
||||
print("""
|
||||
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
|
||||
def edm_deps():""")
|
||||
|
||||
for name, desc in deps.items():
|
||||
repo = desc["git"]
|
||||
# The parameter is called `git_tag` but it can be a tag or a commit
|
||||
revision = desc["git_tag"]
|
||||
tag = None
|
||||
commit = None
|
||||
|
||||
if _is_commit(revision):
|
||||
commit = revision
|
||||
else:
|
||||
tag = revision
|
||||
|
||||
build_file = build_files.get(name)
|
||||
|
||||
print(
|
||||
f"""
|
||||
maybe(
|
||||
git_repository,
|
||||
name = "{name}",
|
||||
remote = "{repo}",
|
||||
tag = {_format_optional_string(tag)},
|
||||
commit = {_format_optional_string(commit)},
|
||||
build_file = {_format_optional_string(build_file)},
|
||||
)
|
||||
"""
|
||||
)
|
||||
@@ -0,0 +1,144 @@
|
||||
# AUTOMATCALLY GENERATED by `shtab`
|
||||
|
||||
|
||||
|
||||
_shtab_edm_tool_option_strings=('-h' '--help' '--version' '--workspace' '--working_dir' '--out' '--include_deps' '--config' '--create-vscode-workspace' '--update' '--cmake' '--verbose' '--nocolor' '--install-bash-completion' '--create-config' '--external-in-config' '--include-remotes' '--create-snapshot' '--git-info' '--git-fetch' '--git-pull')
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
_shtab_edm_tool_pos_0_nargs=A...
|
||||
_shtab_edm_tool__h_nargs=0
|
||||
_shtab_edm_tool___help_nargs=0
|
||||
_shtab_edm_tool___version_nargs=0
|
||||
_shtab_edm_tool___include_deps_nargs=0
|
||||
_shtab_edm_tool___create_vscode_workspace_nargs=0
|
||||
_shtab_edm_tool___update_nargs=0
|
||||
_shtab_edm_tool___cmake_nargs=0
|
||||
_shtab_edm_tool___verbose_nargs=0
|
||||
_shtab_edm_tool___nocolor_nargs=0
|
||||
_shtab_edm_tool___install_bash_completion_nargs=0
|
||||
_shtab_edm_tool___external_in_config_nargs=0
|
||||
_shtab_edm_tool___include_remotes_nargs=*
|
||||
_shtab_edm_tool___create_snapshot_nargs=?
|
||||
_shtab_edm_tool___git_info_nargs=0
|
||||
_shtab_edm_tool___git_fetch_nargs=0
|
||||
_shtab_edm_tool___git_pull_nargs=*
|
||||
|
||||
|
||||
# $1=COMP_WORDS[1]
|
||||
_shtab_compgen_files() {
|
||||
compgen -f -- $1 # files
|
||||
}
|
||||
|
||||
# $1=COMP_WORDS[1]
|
||||
_shtab_compgen_dirs() {
|
||||
compgen -d -- $1 # recurse into subdirs
|
||||
}
|
||||
|
||||
# $1=COMP_WORDS[1]
|
||||
_shtab_replace_nonword() {
|
||||
echo "${1//[^[:word:]]/_}"
|
||||
}
|
||||
|
||||
# set default values (called for the initial parser & any subparsers)
|
||||
_set_parser_defaults() {
|
||||
local subparsers_var="${prefix}_subparsers[@]"
|
||||
sub_parsers=${!subparsers_var}
|
||||
|
||||
local current_option_strings_var="${prefix}_option_strings[@]"
|
||||
current_option_strings=${!current_option_strings_var}
|
||||
|
||||
completed_positional_actions=0
|
||||
|
||||
_set_new_action "pos_${completed_positional_actions}" true
|
||||
}
|
||||
|
||||
# $1=action identifier
|
||||
# $2=positional action (bool)
|
||||
# set all identifiers for an action's parameters
|
||||
_set_new_action() {
|
||||
current_action="${prefix}_$(_shtab_replace_nonword $1)"
|
||||
|
||||
local current_action_compgen_var=${current_action}_COMPGEN
|
||||
current_action_compgen="${!current_action_compgen_var}"
|
||||
|
||||
local current_action_choices_var="${current_action}_choices"
|
||||
current_action_choices="${!current_action_choices_var}"
|
||||
|
||||
local current_action_nargs_var="${current_action}_nargs"
|
||||
if [ -n "${!current_action_nargs_var}" ]; then
|
||||
current_action_nargs="${!current_action_nargs_var}"
|
||||
else
|
||||
current_action_nargs=1
|
||||
fi
|
||||
|
||||
current_action_args_start_index=$(( $word_index + 1 ))
|
||||
|
||||
current_action_is_positional=$2
|
||||
}
|
||||
|
||||
# Notes:
|
||||
# `COMPREPLY`: what will be rendered after completion is triggered
|
||||
# `completing_word`: currently typed word to generate completions for
|
||||
# `${!var}`: evaluates the content of `var` and expand its content as a variable
|
||||
# hello="world"
|
||||
# x="hello"
|
||||
# ${!x} -> ${hello} -> "world"
|
||||
_shtab_edm_tool() {
|
||||
local completing_word="${COMP_WORDS[COMP_CWORD]}"
|
||||
COMPREPLY=()
|
||||
|
||||
prefix=_shtab_edm_tool
|
||||
word_index=0
|
||||
_set_parser_defaults
|
||||
word_index=1
|
||||
|
||||
# determine what arguments are appropriate for the current state
|
||||
# of the arg parser
|
||||
while [ $word_index -ne $COMP_CWORD ]; do
|
||||
local this_word="${COMP_WORDS[$word_index]}"
|
||||
|
||||
if [[ -n $sub_parsers && " ${sub_parsers[@]} " =~ " ${this_word} " ]]; then
|
||||
# valid subcommand: add it to the prefix & reset the current action
|
||||
prefix="${prefix}_$(_shtab_replace_nonword $this_word)"
|
||||
_set_parser_defaults
|
||||
fi
|
||||
|
||||
if [[ " ${current_option_strings[@]} " =~ " ${this_word} " ]]; then
|
||||
# a new action should be acquired (due to recognised option string or
|
||||
# no more input expected from current action);
|
||||
# the next positional action can fill in here
|
||||
_set_new_action $this_word false
|
||||
fi
|
||||
|
||||
if [[ "$current_action_nargs" != "*" ]] && \
|
||||
[[ "$current_action_nargs" != "+" ]] && \
|
||||
[[ "$current_action_nargs" != *"..." ]] && \
|
||||
(( $word_index + 1 - $current_action_args_start_index >= \
|
||||
$current_action_nargs )); then
|
||||
$current_action_is_positional && let "completed_positional_actions += 1"
|
||||
_set_new_action "pos_${completed_positional_actions}" true
|
||||
fi
|
||||
|
||||
let "word_index+=1"
|
||||
done
|
||||
|
||||
# Generate the completions
|
||||
|
||||
if [[ "${completing_word}" == -* ]]; then
|
||||
# optional argument started: use option strings
|
||||
COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") )
|
||||
else
|
||||
# use choices & compgen
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=( $(compgen -W "${current_action_choices}" -- "${completing_word}") \
|
||||
$([ -n "${current_action_compgen}" ] \
|
||||
&& "${current_action_compgen}" "${completing_word}") )
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -o filenames -F _shtab_edm_tool edm
|
||||
1738
tools/EVerest-main/applications/dependency_manager/src/edm_tool/edm.py
Executable file
1738
tools/EVerest-main/applications/dependency_manager/src/edm_tool/edm.py
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
set(ENV{EVEREST_EDM_WORKSPACE} {{ workspace["workspace"] }})
|
||||
set(CPM_USE_NAMED_CACHE_DIRECTORIES ON)
|
||||
{% for dep in checkout %}
|
||||
set(CPM_{{ dep["name"] }}_SOURCE "{{ dep["path"] }}")
|
||||
{% endfor %}
|
||||
{% for name, value in dependencies.items() %}
|
||||
if("{{name}}" IN_LIST EVEREST_EXCLUDE_DEPENDENCIES)
|
||||
message(STATUS "Excluding dependency {{name}}")
|
||||
{% if "cmake_condition" in value and value["cmake_condition"]|length > 0 %}
|
||||
elseif({{ value["cmake_condition"] }})
|
||||
{% else %}
|
||||
else()
|
||||
{% endif %}
|
||||
{% if value and "git" in value %}
|
||||
CPMAddPackage(
|
||||
NAME {{ name }}
|
||||
GIT_REPOSITORY {{ value["git"] }}
|
||||
{% if "git_tag" in value %}
|
||||
GIT_TAG {{ value["git_tag"] }}
|
||||
{% endif %}
|
||||
{% if "options" in value and value["options"]|length > 0 %}
|
||||
OPTIONS
|
||||
{{value["options"]|quote|join(" ")}}
|
||||
{% endif %}
|
||||
{% if "prevent_install" in value and value["prevent_install"] %}
|
||||
EXCLUDE_FROM_ALL YES
|
||||
{% endif %}
|
||||
)
|
||||
{% else %}
|
||||
find_package(
|
||||
{{ name }}
|
||||
{% if value and "components" in value and value["components"]|length > 0 %}
|
||||
COMPONENTS
|
||||
{{value["components"]|quote|join(" ")}}
|
||||
{% endif %}
|
||||
{% if not value or "optional" not in value or not value["optional"] %}
|
||||
REQUIRED
|
||||
{% endif %}
|
||||
)
|
||||
{% endif %}
|
||||
{% if value and "alias" in value %}
|
||||
if({{name}}_ADDED)
|
||||
add_library({{value["alias"]["name"]}} ALIAS {{value["alias"]["target"]}})
|
||||
endif()
|
||||
{% endif %}
|
||||
{% if "cmake_condition" in value and value["cmake_condition"]|length > 0 %}
|
||||
else()
|
||||
message(STATUS "Excluding dependency {{name}} based on cmake_condition")
|
||||
{% endif %}
|
||||
endif()
|
||||
|
||||
{% endfor %}
|
||||
|
||||
execute_process(
|
||||
COMMAND "${EVEREST_DEPENDENCY_MANAGER}" release --everest-dir ${PROJECT_SOURCE_DIR} --build-dir ${CMAKE_BINARY_DIR} --out ${CMAKE_BINARY_DIR}/release.json
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${CMAKE_BINARY_DIR}/release.json"
|
||||
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/everest"
|
||||
)
|
||||
690
tools/EVerest-main/applications/devrd/devrd
Executable file
690
tools/EVerest-main/applications/devrd/devrd
Executable file
@@ -0,0 +1,690 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
# Default values
|
||||
EVEREST_TOOL_BRANCH="main"
|
||||
# Script directory - where devrd is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# .devcontainer directory is always relative to the script location
|
||||
DEVCONTAINER_DIR="${SCRIPT_DIR}/../../.devcontainer"
|
||||
# .env file is always in the .devcontainer directory (relative to script)
|
||||
ENV_FILE="${DEVCONTAINER_DIR}/.env"
|
||||
|
||||
# Function to load HOST_WORKSPACE_FOLDER from .env file
|
||||
# Usage: load_workspace_from_env [fallback]
|
||||
# If fallback is provided and workspace not found in .env, returns fallback
|
||||
# If no fallback provided, returns empty string (for use with ${var:-default} syntax)
|
||||
load_workspace_from_env() {
|
||||
local fallback="$1"
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
local workspace=$(grep "^HOST_WORKSPACE_FOLDER=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
if [ -n "$workspace" ]; then
|
||||
echo "$workspace"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
# If fallback provided and workspace not found, return fallback
|
||||
if [ -n "$fallback" ]; then
|
||||
echo "$fallback"
|
||||
fi
|
||||
}
|
||||
|
||||
# HOST_WORKSPACE_FOLDER is the folder that is mapped to /workspace in the container
|
||||
# Priority: 1) Command line/env var, 2) .env file, 3) Current directory
|
||||
HOST_WORKSPACE_FOLDER="${HOST_WORKSPACE_FOLDER:-$(load_workspace_from_env)}"
|
||||
HOST_WORKSPACE_FOLDER="${HOST_WORKSPACE_FOLDER:-$(pwd)}"
|
||||
|
||||
# Docker Compose project name (defaults to workspace folder name with _devcontainer suffix, can be overridden)
|
||||
# This matches VSC's naming convention: {workspace-folder-name}_devcontainer-{service-name}-1
|
||||
# If needed (and not running in VSCode), can be changed by setting the DOCKER_COMPOSE_PROJECT_NAME environment variable.
|
||||
DOCKER_COMPOSE_PROJECT_NAME="${DOCKER_COMPOSE_PROJECT_NAME:-$(basename "$HOST_WORKSPACE_FOLDER" | tr \"A-Z\" \"a-z\")_devcontainer}"
|
||||
|
||||
|
||||
# Function to detect if running inside container
|
||||
is_inside_container() {
|
||||
# Check for /.dockerenv (standard Docker indicator)
|
||||
[ -f /.dockerenv ] && return 0
|
||||
# Check if /workspace exists and is mounted (devcontainer specific)
|
||||
[ -d /workspace ] && [ -f /workspace/.devcontainer/devrd ] && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to show error when command is run from inside container
|
||||
show_inside_container_error() {
|
||||
local cmd_name="${1:-this command}"
|
||||
echo "✖ Error: This command cannot be run from inside the container"
|
||||
echo ""
|
||||
echo "You are currently inside the development container."
|
||||
echo "Please run this command from the host system instead:"
|
||||
echo ""
|
||||
echo " 1. Exit the container (type 'exit' or press Ctrl+D)"
|
||||
echo " 2. Run the command from your host terminal:"
|
||||
echo " ./devrd $cmd_name"
|
||||
echo ""
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to run docker compose with static project name
|
||||
# Compose files are always relative to the script's .devcontainer directory
|
||||
docker_compose() {
|
||||
docker compose -p "$DOCKER_COMPOSE_PROJECT_NAME" \
|
||||
-f "${DEVCONTAINER_DIR}/docker-compose.yml" \
|
||||
-f "${DEVCONTAINER_DIR}/general-devcontainer/docker-compose.devcontainer.yml" "$@"
|
||||
}
|
||||
|
||||
# Function to validate folder path
|
||||
validate_folder() {
|
||||
local folder="$1"
|
||||
|
||||
# Convert relative path to absolute
|
||||
case "$folder" in
|
||||
/*) ;; # Already absolute
|
||||
*) folder="$(cd "$folder" && pwd)" ;; # Convert relative to absolute
|
||||
esac
|
||||
|
||||
# Check if folder exists
|
||||
if [ ! -d "$folder" ]; then
|
||||
echo "Error: Folder '$folder' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if folder is readable
|
||||
if [ ! -r "$folder" ]; then
|
||||
echo "Error: Folder '$folder' is not accessible (permission denied)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$folder"
|
||||
}
|
||||
|
||||
|
||||
# Function to generate .env file
|
||||
generate_env() {
|
||||
if is_inside_container; then
|
||||
show_inside_container_error "env"
|
||||
fi
|
||||
# Process command line options
|
||||
if [ -n "$ENV_OPTIONS" ]; then
|
||||
set -- $ENV_OPTIONS
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-v|--version)
|
||||
EVEREST_TOOL_BRANCH="$2"
|
||||
shift 2
|
||||
;;
|
||||
-w|--workspace)
|
||||
HOST_WORKSPACE_FOLDER="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Set workspace folder
|
||||
if [ -n "$HOST_WORKSPACE_FOLDER" ]; then
|
||||
HOST_WORKSPACE_FOLDER=$(validate_folder "$HOST_WORKSPACE_FOLDER")
|
||||
else
|
||||
HOST_WORKSPACE_FOLDER="$(pwd)"
|
||||
fi
|
||||
|
||||
# Get commit hash
|
||||
COMMIT_HASH=$(git ls-remote https://github.com/EVerest/everest-dev-environment.git ${EVEREST_TOOL_BRANCH} | cut -f1 2>/dev/null || echo "")
|
||||
|
||||
# Check if we need to update existing file
|
||||
local needs_update=false
|
||||
if [ -f "$ENV_FILE" ] && [ -s "$ENV_FILE" ]; then
|
||||
# File exists, check if we have options that require updates
|
||||
if [ -n "$ENV_OPTIONS" ]; then
|
||||
needs_update=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "$ENV_FILE" ] || [ ! -s "$ENV_FILE" ] || [ "$needs_update" = true ]; then
|
||||
cat > "$ENV_FILE" << EOF
|
||||
# Auto-generated by devrd script
|
||||
ORGANIZATION_ARG=EVerest
|
||||
REPOSITORY_HOST=github.com
|
||||
REPOSITORY_USER=git
|
||||
COMMIT_HASH=$COMMIT_HASH
|
||||
EVEREST_TOOL_BRANCH=$EVEREST_TOOL_BRANCH
|
||||
UID=$(id -u)
|
||||
GID=$(id -g)
|
||||
HOST_WORKSPACE_FOLDER=$HOST_WORKSPACE_FOLDER
|
||||
EOF
|
||||
if [ "$needs_update" = true ]; then
|
||||
echo "Updated .env file"
|
||||
else
|
||||
echo "Generated .env file"
|
||||
fi
|
||||
else
|
||||
echo "Found existing .env file"
|
||||
cat "$ENV_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to build the container
|
||||
build_container() {
|
||||
if is_inside_container; then
|
||||
show_inside_container_error "build"
|
||||
fi
|
||||
echo "Building development container..."
|
||||
docker_compose --profile all build
|
||||
}
|
||||
|
||||
# Function to get actual port mapping from docker compose
|
||||
get_port_mapping() {
|
||||
local service_name=$1
|
||||
local internal_port=$2
|
||||
|
||||
# Get the actual port mapping from docker compose
|
||||
local port_mapping=$(docker_compose port $service_name $internal_port 2>/dev/null)
|
||||
|
||||
if [ -n "$port_mapping" ]; then
|
||||
# Extract just the host port (remove the host part)
|
||||
echo "$port_mapping" | sed 's/.*://'
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Function to display container links and tips
|
||||
display_container_status() {
|
||||
echo ""
|
||||
echo "Container Services Summary:"
|
||||
echo "=============================="
|
||||
|
||||
# Get actual port mappings from docker compose
|
||||
local mqtt_explorer_port=$(get_port_mapping mqtt-explorer 4000)
|
||||
local steve_http_port=$(get_port_mapping steve 8180)
|
||||
|
||||
# Display links with actual ports
|
||||
if [ -n "$mqtt_explorer_port" ]; then
|
||||
echo "MQTT Explorer: http://localhost:$mqtt_explorer_port"
|
||||
else
|
||||
echo "MQTT Explorer: currently not running"
|
||||
fi
|
||||
|
||||
if [ -n "$steve_http_port" ]; then
|
||||
echo "Steve (HTTP): http://localhost:$steve_http_port"
|
||||
else
|
||||
echo "Steve (HTTP): currently not running"
|
||||
fi
|
||||
|
||||
# Check if Node-RED is running
|
||||
if docker_compose ps | grep -q "nodered"; then
|
||||
echo "Node-RED UI: http://localhost:1880/ui"
|
||||
else
|
||||
echo "Node-RED UI: currently not running"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Tips:"
|
||||
echo " • MQTT Explorer: Browse and debug MQTT topics"
|
||||
echo " • Steve: OCPP backend management interface"
|
||||
echo " • Node-RED: Web-based UI for SIL simulations"
|
||||
echo " • Use './devrd prompt' to access the container shell"
|
||||
echo " • Use './devrd nodered-flows' to see available flows"
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
# Function to start containers using profiles
|
||||
start_compose_profile() {
|
||||
if is_inside_container; then
|
||||
show_inside_container_error "start"
|
||||
fi
|
||||
local profile_or_service="$1"
|
||||
|
||||
if [ -n "$profile_or_service" ]; then
|
||||
echo "Starting containers for profile/service: $profile_or_service..."
|
||||
docker_compose --profile "$profile_or_service" up -d
|
||||
else
|
||||
echo "Starting the development container and all services..."
|
||||
docker_compose --profile all up -d
|
||||
fi
|
||||
|
||||
# Display workspace mapping
|
||||
echo "Workspace mapping: $HOST_WORKSPACE_FOLDER → /workspace"
|
||||
echo ""
|
||||
|
||||
# Display container links
|
||||
display_container_status
|
||||
}
|
||||
|
||||
# Function to stop containers using profiles or container name pattern
|
||||
stop_compose_profile() {
|
||||
if is_inside_container; then
|
||||
show_inside_container_error "stop"
|
||||
fi
|
||||
local profile_or_pattern="$1"
|
||||
|
||||
if [ -n "$profile_or_pattern" ]; then
|
||||
# Check if it's a valid profile name
|
||||
case "$profile_or_pattern" in
|
||||
mqtt|ocpp|sil|all)
|
||||
echo "Stopping containers for profile: $profile_or_pattern..."
|
||||
docker_compose --profile "$profile_or_pattern" stop
|
||||
;;
|
||||
*)
|
||||
# Treat as container name pattern
|
||||
echo "Stopping containers matching pattern: $profile_or_pattern..."
|
||||
local containers=$(docker ps --format "{{.Names}}" | grep -E "($profile_or_pattern)" || true)
|
||||
if [ -z "$containers" ]; then
|
||||
echo "No running containers found matching pattern: $profile_or_pattern"
|
||||
return 1
|
||||
fi
|
||||
echo "$containers" | while read container; do
|
||||
echo "Stopping container: $container"
|
||||
docker stop "$container" 2>/dev/null || echo "Failed to stop container: $container"
|
||||
done
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "Stopping the development container and all services..."
|
||||
docker_compose --profile all stop
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to purge everything
|
||||
purge_everything() {
|
||||
if is_inside_container; then
|
||||
show_inside_container_error "purge"
|
||||
fi
|
||||
local purge_pattern="${1:-$(basename "$HOST_WORKSPACE_FOLDER")}"
|
||||
local current_project="$(basename "$HOST_WORKSPACE_FOLDER")"
|
||||
echo "Purging all devcontainer resources for pattern: $purge_pattern..."
|
||||
|
||||
# Only use docker_compose down if purging the current project
|
||||
if [ "$purge_pattern" = "$current_project" ]; then
|
||||
echo "Stopping and removing containers for current project..."
|
||||
docker_compose down -v --remove-orphans
|
||||
else
|
||||
echo "Purging resources for different project pattern: $purge_pattern"
|
||||
echo "Skipping docker-compose cleanup (not current project)"
|
||||
fi
|
||||
|
||||
# Remove all images related to the project
|
||||
echo "Removing devcontainer images..."
|
||||
docker images --format "table {{.Repository}}:{{.Tag}}" | grep -E "($purge_pattern)" | awk '{print $1}' | xargs -r docker rmi -f
|
||||
|
||||
# Remove all volumes related to the project (with force if needed)
|
||||
echo "Removing devcontainer volumes..."
|
||||
docker volume ls --format "{{.Name}}" | grep -E "($purge_pattern)" | while read volume; do
|
||||
echo "Removing volume: $volume"
|
||||
docker volume rm -f "$volume" 2>/dev/null || echo "Volume $volume could not be removed (may be in use)"
|
||||
done
|
||||
|
||||
# Ask user if they want to purge CPM cache volume
|
||||
echo ""
|
||||
echo "CPM source cache volume (everest-cpm-source-cache) is shared across all workspaces."
|
||||
read -p "Do you want to purge the CPM cache volume as well? [y/N]: " purge_cache
|
||||
purge_cache="${purge_cache:-N}"
|
||||
if [[ "$purge_cache" =~ ^[Yy]$ ]]; then
|
||||
echo "Removing CPM cache volume..."
|
||||
if docker volume rm everest-cpm-source-cache 2>/dev/null; then
|
||||
echo "✔ CPM cache volume removed"
|
||||
else
|
||||
echo "⚠ CPM cache volume could not be removed (may be in use or not exist)"
|
||||
fi
|
||||
else
|
||||
echo "Keeping CPM cache volume (will be reused for faster builds)"
|
||||
fi
|
||||
|
||||
# Remove any dangling images and containers
|
||||
echo ""
|
||||
echo "Cleaning up dangling resources..."
|
||||
docker system prune -f
|
||||
|
||||
echo ""
|
||||
echo "✔ Purge complete! All devcontainer resources have been removed."
|
||||
}
|
||||
|
||||
# Function to check if SSH agent is running
|
||||
check_ssh_agent() {
|
||||
if [ -z "$SSH_AUTH_SOCK" ] || ! ssh-add -l >/dev/null 2>&1; then
|
||||
echo "Error: SSH agent is not running or no keys are loaded."
|
||||
echo "Please start the SSH agent and add your keys:"
|
||||
echo " eval \$(ssh-agent)"
|
||||
echo " ssh-add ~/.ssh/id_rsa # or your private key"
|
||||
echo "Or if you're using a different key:"
|
||||
echo " ssh-add ~/.ssh/your_private_key"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to execute a command in the container
|
||||
exec_devcontainer() {
|
||||
if is_inside_container; then
|
||||
echo "✖ You're already inside the container."
|
||||
echo ""
|
||||
echo "To run a command, just execute it directly:"
|
||||
if [ $# -gt 0 ]; then
|
||||
echo " $@"
|
||||
else
|
||||
echo " <your-command>"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
echo "Checking if development container is running..."
|
||||
|
||||
# Check if the devcontainer service is running
|
||||
if ! docker_compose ps devcontainer | grep -q "Up"; then
|
||||
echo "Error: Development container is not running."
|
||||
echo "Please start the container first with: ./devrd start"
|
||||
echo "Or build and start with: ./devrd build && ./devrd start"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Executing command in development container..."
|
||||
run_in_devcontainer "$@"
|
||||
}
|
||||
|
||||
# Function to get a shell prompt in the container
|
||||
prompt_devcontainer() {
|
||||
if is_inside_container; then
|
||||
echo "✖ You're already inside the container shell."
|
||||
exit 1
|
||||
fi
|
||||
echo "Starting shell in development container..."
|
||||
exec_devcontainer /bin/bash
|
||||
}
|
||||
|
||||
# Helper function to check if Node-RED is running and get the URL
|
||||
# Sets nodered_url variable and returns 0 if running, 1 if not
|
||||
check_nodered_running() {
|
||||
if is_inside_container; then
|
||||
nodered_url="http://nodered:1880"
|
||||
curl -s "$nodered_url/flows" >/dev/null 2>&1 && return 0
|
||||
else
|
||||
nodered_url="http://localhost:1880"
|
||||
docker_compose ps | grep -q "nodered" && return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Helper function to execute a command in the container
|
||||
# Usage: run_in_devcontainer [--no-tty] <command> [args...]
|
||||
# Executes directly if inside container, via docker_compose exec if on host
|
||||
# No error checking - assumes container is running when called from host
|
||||
# Use --no-tty for non-interactive commands that need output capture
|
||||
run_in_devcontainer() {
|
||||
local no_tty=false
|
||||
if [ "$1" = "--no-tty" ]; then
|
||||
no_tty=true
|
||||
shift
|
||||
fi
|
||||
|
||||
if is_inside_container; then
|
||||
"$@"
|
||||
else
|
||||
if [ "$no_tty" = true ]; then
|
||||
docker_compose exec -T devcontainer "$@"
|
||||
else
|
||||
docker_compose exec devcontainer "$@"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to list available flows
|
||||
list_nodered_flows() {
|
||||
echo ""
|
||||
echo "Available Node-RED Flows:"
|
||||
echo "============================="
|
||||
|
||||
# Check if Node-RED is running
|
||||
if ! check_nodered_running; then
|
||||
echo "✖ Node-RED container is not running"
|
||||
echo "Please start with './devrd start' first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Find all flow files in the workspace
|
||||
local flows
|
||||
if is_inside_container; then
|
||||
flows=$(find /workspace -name "*-flow.json" -type f 2>/dev/null | sort)
|
||||
else
|
||||
flows=$(docker_compose exec -T devcontainer find /workspace -name "*-flow.json" -type f 2>/dev/null | sort)
|
||||
fi
|
||||
|
||||
if [ -z "$flows" ]; then
|
||||
echo "No flow files found in workspace"
|
||||
echo ""
|
||||
echo "Expected pattern: *-flow.json"
|
||||
echo "Search location: /workspace"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Found $(echo "$flows" | wc -l) flow file(s):"
|
||||
echo ""
|
||||
for flow in $flows; do
|
||||
# Remove /workspace/ prefix to get relative path from workspace root
|
||||
local relative_path=$(echo "$flow" | sed 's|^/workspace/||')
|
||||
echo " Path: $relative_path"
|
||||
done
|
||||
echo ""
|
||||
echo "Usage: ./devrd flow <flow-file-path>"
|
||||
echo "Example: ./devrd flow EVerest/config/nodered/config-sil-dc-flow.json"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to switch flow using REST API
|
||||
switch_nodered_flow() {
|
||||
local flow_path="$1"
|
||||
|
||||
if [ -z "$flow_path" ]; then
|
||||
echo "Error: Please specify a flow file path"
|
||||
echo ""
|
||||
echo "Available flows:"
|
||||
list_nodered_flows
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if Node-RED is running
|
||||
if ! check_nodered_running; then
|
||||
echo "✖ Node-RED container is not running"
|
||||
echo "Please start with './devrd start' first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Construct full path in container
|
||||
local full_path="/workspace/$flow_path"
|
||||
|
||||
# Check if file exists and is readable, then copy to temp file
|
||||
if ! run_in_devcontainer --no-tty test -r "$full_path"; then
|
||||
echo "✖ Flow file not found or not readable: $flow_path"
|
||||
echo ""
|
||||
echo "Available flows:"
|
||||
list_nodered_flows
|
||||
return 1
|
||||
fi
|
||||
# Copy flow to temporary file
|
||||
run_in_devcontainer --no-tty cat "$full_path" > /tmp/flows.json
|
||||
|
||||
echo "Switching Node-RED to flow: $(basename "$flow_path")"
|
||||
echo "Source: $flow_path"
|
||||
|
||||
# Process environment variables in the flow JSON
|
||||
# Replace "broker": "localhost" with "broker": "mqtt-server"
|
||||
sed -i 's/"broker": "localhost"/"broker": "mqtt-server"/g' /tmp/flows.json
|
||||
|
||||
# Deploy flow via Node-RED REST API
|
||||
echo "Deploying flow via Node-RED API..."
|
||||
local response=$(curl -s -w "%{http_code}" -X POST "$nodered_url/flows" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/flows.json)
|
||||
|
||||
local http_code="${response: -3}"
|
||||
|
||||
if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then
|
||||
echo "✔ Node-RED flow deployed successfully via API!"
|
||||
if is_inside_container; then
|
||||
echo "Access at: http://nodered:1880/ui (from container) or http://localhost:1880/ui (from host)"
|
||||
else
|
||||
echo "Access at: http://localhost:1880/ui"
|
||||
fi
|
||||
else
|
||||
echo "✖ Failed to deploy flow via API (HTTP $http_code)"
|
||||
echo "Response: ${response%???}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Clean up temporary file
|
||||
rm -f /tmp/flows.json
|
||||
}
|
||||
|
||||
|
||||
# Function to display help
|
||||
show_help() {
|
||||
echo "Usage: $0 [COMMAND] [OPTIONS]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " env Generate .env file with repository information (default)"
|
||||
echo " build Build the development container"
|
||||
echo " start [profile] Start containers (profiles: mqtt, ocpp, sil, all)"
|
||||
echo " stop [profile|pattern] Stop containers by profile (mqtt, ocpp, sil, all) or container name pattern"
|
||||
echo " purge [pattern] Remove all devcontainer resources (containers, images, volumes)"
|
||||
echo " Optional pattern to match (default: current folder name)"
|
||||
echo " exec <command> Execute a command in the development container (requires the container to be running)"
|
||||
echo " prompt Get a shell prompt in the development container (requires the container to be running)"
|
||||
echo " flows List available flows"
|
||||
echo " flow <path> Switch to specific flow file"
|
||||
echo ""
|
||||
echo "Options (for env command only):"
|
||||
echo " -v, --version VERSION Everest tool branch (default: $EVEREST_TOOL_BRANCH, preserves existing if not specified)"
|
||||
echo " -w, --workspace DIR Workspace directory to map to /workspace in container (default: current directory)"
|
||||
echo " --help Display this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 env # Generate .env file with repository information"
|
||||
echo " $0 build # Build container"
|
||||
echo " $0 start # Start all containers"
|
||||
echo " $0 start sil # Start SIL simulation tools (Node-RED, MQTT Explorer)"
|
||||
echo " $0 start ocpp # Start OCPP services (Steve, OCPP DB, MQTT)"
|
||||
echo " $0 start mqtt # Start only MQTT server"
|
||||
echo " $0 stop sil # Stop SIL simulation tools"
|
||||
echo " $0 stop ev-ws # Stop all containers matching pattern 'ev-ws'"
|
||||
echo " $0 purge # Remove all devcontainer resources for current folder"
|
||||
echo " $0 purge my-project # Remove all devcontainer resources matching 'my-project'"
|
||||
echo " $0 exec ls -la # Execute command in container"
|
||||
echo " $0 prompt # Get shell prompt in container"
|
||||
echo " $0 flows # List available flows"
|
||||
echo " $0 flow <path> # Switch to specific Node-RED flow file"
|
||||
|
||||
echo " $0 -w ~/Documents # Map Documents folder to /workspace"
|
||||
echo " $0 --workspace /opt/tools # Map tools folder to /workspace"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
COMMAND="env"
|
||||
ENV_OPTIONS=""
|
||||
|
||||
# First pass: collect all options
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
-v|--version|-w|--workspace)
|
||||
# Store env-specific options for later use
|
||||
ENV_OPTIONS="$ENV_OPTIONS $1 $2"
|
||||
shift 2
|
||||
;;
|
||||
--help)
|
||||
show_help
|
||||
;;
|
||||
exec)
|
||||
COMMAND="$1"
|
||||
shift
|
||||
# For exec, pass all remaining arguments to the exec function
|
||||
break
|
||||
;;
|
||||
env|build|prompt|flows)
|
||||
COMMAND="$1"
|
||||
shift
|
||||
# Don't break here, continue to collect more options
|
||||
;;
|
||||
flow)
|
||||
COMMAND="$1"
|
||||
shift
|
||||
# For flow, pass any remaining arguments as flow path
|
||||
break
|
||||
;;
|
||||
purge)
|
||||
COMMAND="$1"
|
||||
shift
|
||||
# For purge, pass any remaining arguments as pattern
|
||||
break
|
||||
;;
|
||||
start|stop)
|
||||
COMMAND="$1"
|
||||
shift
|
||||
# For start/stop, pass any remaining arguments as container name
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Execute the command
|
||||
case $COMMAND in
|
||||
env)
|
||||
# Check SSH agent for Git operations
|
||||
check_ssh_agent
|
||||
generate_env
|
||||
;;
|
||||
build)
|
||||
# Only generate env if .env file doesn't exist or is empty
|
||||
if [ ! -f "$ENV_FILE" ] || [ ! -s "$ENV_FILE" ]; then
|
||||
# Check SSH agent for Git operations
|
||||
check_ssh_agent
|
||||
generate_env
|
||||
fi
|
||||
build_container
|
||||
;;
|
||||
start)
|
||||
# Only generate env if .env file doesn't exist or is empty
|
||||
if [ ! -f "$ENV_FILE" ] || [ ! -s "$ENV_FILE" ]; then
|
||||
# Check SSH agent for Git operations
|
||||
check_ssh_agent
|
||||
generate_env
|
||||
fi
|
||||
start_compose_profile "$@"
|
||||
;;
|
||||
stop)
|
||||
stop_compose_profile "$@"
|
||||
;;
|
||||
purge)
|
||||
purge_everything "$@"
|
||||
;;
|
||||
exec)
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Error: exec command requires arguments"
|
||||
show_help
|
||||
fi
|
||||
exec_devcontainer "$@"
|
||||
;;
|
||||
prompt)
|
||||
prompt_devcontainer
|
||||
;;
|
||||
flows)
|
||||
list_nodered_flows
|
||||
;;
|
||||
flow)
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Error: flow command requires a flow file path"
|
||||
show_help
|
||||
fi
|
||||
switch_nodered_flow "$1"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $COMMAND"
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
97
tools/EVerest-main/applications/devrd/devrd-completion.bash
Executable file
97
tools/EVerest-main/applications/devrd/devrd-completion.bash
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Bash completion for devrd script
|
||||
# Source this file or add to your .bashrc to enable completion
|
||||
|
||||
_devrd_completion() {
|
||||
local cur prev opts cmds
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
# Available commands
|
||||
cmds="install env build start stop prompt purge exec flows flow"
|
||||
|
||||
# Available options
|
||||
opts="-v --version -w --workspace --help"
|
||||
|
||||
# Function to get available Node-RED flows dynamically
|
||||
_get_nodered_flows() {
|
||||
# Get the current project name (same logic as devrd script)
|
||||
local project_name="${DOCKER_COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")_devcontainer}"
|
||||
|
||||
# Check if we're in the right directory and container is running
|
||||
if [ -f "devrd" ] && docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml ps devcontainer | grep -q "Up"; then
|
||||
# Get flows from the container and return full paths (relative to workspace)
|
||||
docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml exec -T devcontainer find /workspace -name "*-flow.json" -type f 2>/dev/null | sed 's|/workspace/||' | sort
|
||||
else
|
||||
# Fallback to common flow file paths
|
||||
echo "EVerest/config/nodered/config-sil-dc-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-dc-bpt-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-energy-management-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-two-evse-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-flow.json"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get available container names
|
||||
_get_container_names() {
|
||||
echo "mqtt ocpp sil"
|
||||
}
|
||||
|
||||
# If the previous word is an option that takes an argument, complete based on the option
|
||||
case "$prev" in
|
||||
|
||||
-v|--version)
|
||||
# Complete with common version patterns
|
||||
COMPREPLY=( $(compgen -W "main master develop release/1.0 release/1.1" -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
-w|--workspace)
|
||||
# Complete directories
|
||||
COMPREPLY=( $(compgen -d -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
flow)
|
||||
# Complete with available flow file paths dynamically
|
||||
local flows
|
||||
flows=$(_get_nodered_flows)
|
||||
COMPREPLY=( $(compgen -W "$flows" -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
start|stop)
|
||||
# Complete with available container names
|
||||
local containers
|
||||
containers=$(_get_container_names)
|
||||
COMPREPLY=( $(compgen -W "$containers" -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
exec)
|
||||
# For exec command, complete with common commands
|
||||
COMPREPLY=( $(compgen -W "ls pwd cd cmake ninja make" -- "$cur") )
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# If we're completing the first word (command), show commands
|
||||
if [ $COMP_CWORD -eq 1 ]; then
|
||||
COMPREPLY=( $(compgen -W "$cmds" -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# If we're completing an option, show options
|
||||
if [[ "$cur" == -* ]]; then
|
||||
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
||||
return 0
|
||||
fi
|
||||
|
||||
# For other cases, complete with files/directories
|
||||
COMPREPLY=( $(compgen -f -- "$cur") )
|
||||
return 0
|
||||
}
|
||||
|
||||
# Register the completion function
|
||||
complete -F _devrd_completion devrd
|
||||
complete -F _devrd_completion ./devrd
|
||||
complete -F _devrd_completion ../devrd
|
||||
complete -F _devrd_completion ./applications/devrd/devrd
|
||||
90
tools/EVerest-main/applications/devrd/devrd-completion.zsh
Executable file
90
tools/EVerest-main/applications/devrd/devrd-completion.zsh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/bin/zsh
|
||||
|
||||
# Zsh completion for devrd script
|
||||
# Source this file or add to your .zshrc to enable completion
|
||||
|
||||
_devrd_completion() {
|
||||
local context state line
|
||||
typeset -A opt_args
|
||||
|
||||
# Available commands
|
||||
local commands=(
|
||||
'env:Generate .env file with repository information'
|
||||
'build:Build the development container'
|
||||
'start:Start containers (profiles: mqtt, ocpp, sil)'
|
||||
'stop:Stop containers (profiles: mqtt, ocpp, sil)'
|
||||
'purge:Remove all devcontainer resources (containers, images, volumes)'
|
||||
'exec:Execute a command in the container'
|
||||
'prompt:Get a shell prompt in the container'
|
||||
'flows:List available flows'
|
||||
'flow:Switch to specific flow file'
|
||||
)
|
||||
|
||||
# Available options
|
||||
local options=(
|
||||
'-v[Everest tool branch]:version:'
|
||||
'--version[Everest tool branch]:version:'
|
||||
'-w[Workspace directory]:directory:_files -/'
|
||||
'--workspace[Workspace directory]:directory:_files -/'
|
||||
'--help[Display help message]'
|
||||
)
|
||||
|
||||
# Function to get available Node-RED flows dynamically
|
||||
_get_nodered_flows() {
|
||||
# Get the current project name (same logic as devrd script)
|
||||
local project_name="${DOCKER_COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")_devcontainer}"
|
||||
|
||||
# Check if we're in the right directory and container is running
|
||||
if [ -f "devrd" ] && docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml ps devcontainer | grep -q "Up"; then
|
||||
# Get flows from the container and return full paths (relative to workspace)
|
||||
docker compose -p "$project_name" -f .devcontainer/docker-compose.yml -f .devcontainer/general-devcontainer/docker-compose.devcontainer.yml exec -T devcontainer find /workspace -name "*-flow.json" -type f 2>/dev/null | sed 's|/workspace/||' | sort
|
||||
else
|
||||
# Fallback to common flow file paths
|
||||
echo "EVerest/config/nodered/config-sil-dc-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-dc-bpt-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-energy-management-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-two-evse-flow.json"
|
||||
echo "EVerest/config/nodered/config-sil-flow.json"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to get available container names
|
||||
_get_container_names() {
|
||||
echo "mqtt ocpp sil"
|
||||
}
|
||||
|
||||
# Main completion logic
|
||||
_arguments -C \
|
||||
"$options[@]" \
|
||||
"1: :{_describe 'commands' commands}" \
|
||||
"*::arg:->args"
|
||||
|
||||
case $state in
|
||||
args)
|
||||
case $line[1] in
|
||||
flow)
|
||||
_values 'flow files' $(_get_nodered_flows)
|
||||
;;
|
||||
start|stop)
|
||||
_values 'profiles' $(_get_container_names)
|
||||
;;
|
||||
exec)
|
||||
_values 'commands' 'ls' 'pwd' 'cd' 'cmake' 'ninja' 'make'
|
||||
;;
|
||||
purge)
|
||||
_files
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Register the completion function
|
||||
if command -v compdef >/dev/null 2>&1; then
|
||||
compdef _devrd_completion devrd
|
||||
compdef _devrd_completion ./devrd
|
||||
compdef _devrd_completion ../devrd
|
||||
compdef _devrd_completion ./applications/devrd/devrd
|
||||
else
|
||||
echo "Warning: zsh completion system not loaded. Add 'autoload -U compinit && compinit' to your .zshrc"
|
||||
fi
|
||||
3
tools/EVerest-main/applications/everest_dev_tool/.gitignore
vendored
Normal file
3
tools/EVerest-main/applications/everest_dev_tool/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build
|
||||
__pycache__
|
||||
*.egg-info
|
||||
201
tools/EVerest-main/applications/everest_dev_tool/LICENSE
Normal file
201
tools/EVerest-main/applications/everest_dev_tool/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "everest_dev_tool"
|
||||
version = "0.1.0"
|
||||
description = "This tool provides helpful commands to setup/control your dev environment"
|
||||
license = { text="Apache-2.0" }
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
everest = "everest_dev_tool:main"
|
||||
@@ -0,0 +1,9 @@
|
||||
__version__ = "0.1.0"
|
||||
|
||||
from . import parser
|
||||
|
||||
def get_parser():
|
||||
return parser.get_parser(__version__)
|
||||
|
||||
def main():
|
||||
parser.main(get_parser())
|
||||
@@ -0,0 +1,30 @@
|
||||
import argparse
|
||||
import subprocess
|
||||
|
||||
def clone_handler(args: argparse.Namespace):
|
||||
log = args.logger
|
||||
|
||||
log.info(
|
||||
f"Cloning repository:\n"
|
||||
f" Method: {args.method}\n"
|
||||
f" Host: {args.host}\n"
|
||||
f" SSH User (if ssh): {args.ssh_user}\n"
|
||||
f" Organization: {args.organization}\n"
|
||||
f" Repository Name: {args.repository_name}\n"
|
||||
f" Branch: {args.branch}\n"
|
||||
)
|
||||
repository_url = ""
|
||||
if args.method == 'https':
|
||||
repository_url = f"https://{args.host}/"
|
||||
else:
|
||||
repository_url = f"{args.ssh_user}@{args.host}:"
|
||||
repository_url = repository_url + f"{ args.organization }/{ args.repository_name }.git"
|
||||
|
||||
cmd_args = ["git", "clone", "-b", args.branch, repository_url]
|
||||
|
||||
log.debug(f"Command to execute: {' '.join(cmd_args)}")
|
||||
|
||||
if args.dry:
|
||||
log.info(f"Dry run: Would execute: {' '.join(cmd_args)}")
|
||||
else:
|
||||
subprocess.run(cmd_args, check=True)
|
||||
@@ -0,0 +1,83 @@
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
|
||||
from . import git_handlers
|
||||
|
||||
log = logging.getLogger("EVerest's Development Tool")
|
||||
|
||||
def get_parser(version: str) -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter,
|
||||
description="EVerest's Development Tool",)
|
||||
parser.add_argument('--version', action='version', version=f'%(prog)s { version }')
|
||||
parser.add_argument('-v', '--verbose', action='store_true', help="Verbose output")
|
||||
parser.set_defaults(action_handler=lambda _: parser.print_help())
|
||||
|
||||
subparsers = parser.add_subparsers(help="available commands")
|
||||
|
||||
# Git related commands
|
||||
clone_parser = subparsers.add_parser("clone", help="Clone a repository", add_help=True)
|
||||
clone_parser.add_argument('-v', '--verbose', action='store_true', help="Verbose output")
|
||||
default_git_host = os.environ.get("EVEREST_DEV_TOOL_DEFAULT_GIT_HOST", "github.com")
|
||||
clone_parser.add_argument(
|
||||
'--host',
|
||||
default=default_git_host,
|
||||
help=(
|
||||
"Git host to use, default is 'github.com' "
|
||||
"(can be overridden by the environment variable "
|
||||
"EVEREST_DEV_TOOL_DEFAULT_GIT_HOST)"
|
||||
),
|
||||
)
|
||||
default_git_method = os.environ.get("EVEREST_DEV_TOOL_DEFAULT_GIT_METHOD", "ssh")
|
||||
clone_parser.add_argument(
|
||||
'--method',
|
||||
default=default_git_method,
|
||||
choices=['https', 'ssh'],
|
||||
help=(
|
||||
"Git method to use, default is 'ssh' "
|
||||
"(can be overridden by the environment variable "
|
||||
"EVEREST_DEV_TOOL_DEFAULT_GIT_METHOD)"
|
||||
)
|
||||
)
|
||||
default_git_ssh_user = os.environ.get("EVEREST_DEV_TOOL_DEFAULT_GIT_SSH_USER", "git")
|
||||
clone_parser.add_argument(
|
||||
'--ssh-user',
|
||||
default=default_git_ssh_user,
|
||||
help=(
|
||||
"SSH user to use, default is 'git' "
|
||||
"(can be overridden by the environment variable "
|
||||
"EVEREST_DEV_TOOL_DEFAULT_GIT_SSH_USER)"
|
||||
)
|
||||
)
|
||||
default_git_organization = os.environ.get("EVEREST_DEV_TOOL_DEFAULT_GIT_ORGANIZATION", "EVerest")
|
||||
clone_parser.add_argument(
|
||||
'--organization', '--org',
|
||||
default=default_git_organization,
|
||||
help=(
|
||||
"Github Organization name, default is 'EVerest'"
|
||||
" (can be overridden by the environment variable "
|
||||
"EVEREST_DEV_TOOL_DEFAULT_GIT_ORGANIZATION)"
|
||||
)
|
||||
)
|
||||
clone_parser.add_argument('--branch', '-b', default="main", help="Branch to checkout, default is 'main'")
|
||||
clone_parser.add_argument('--dry', action='store_true', help="Dry run, do not execute the clone command")
|
||||
clone_parser.add_argument("repository_name", help="Name of the repository to clone")
|
||||
clone_parser.set_defaults(action_handler=git_handlers.clone_handler)
|
||||
|
||||
return parser
|
||||
|
||||
def setup_logging(verbose: bool):
|
||||
if verbose:
|
||||
log.setLevel(logging.DEBUG)
|
||||
else:
|
||||
log.setLevel(logging.INFO)
|
||||
console_handler = logging.StreamHandler()
|
||||
log.addHandler(console_handler)
|
||||
|
||||
def main(parser: argparse.ArgumentParser):
|
||||
args = parser.parse_args()
|
||||
args.logger = log
|
||||
|
||||
setup_logging(args.verbose)
|
||||
|
||||
args.action_handler(args)
|
||||
@@ -0,0 +1,73 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
if(DEFINED EVEREST_IO_WITH_MQTT AND NOT EVEREST_IO_WITH_MQTT)
|
||||
message(FATAL_ERROR "pionix_chargebridge requires MQTT support in everest::io. "
|
||||
"Set EVEREST_IO_WITH_MQTT=ON or disable EVEREST_BUILD_APPLICATIONS.")
|
||||
endif()
|
||||
|
||||
find_package(ryml QUIET)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
|
||||
add_executable(pionix_chargebridge
|
||||
src/everest_api/api_connector.cpp
|
||||
src/everest_api/evse_bsp_api.cpp
|
||||
src/everest_api/ovm_api.cpp
|
||||
src/everest_api/ev_bsp_api.cpp
|
||||
|
||||
src/firmware_update/sync_fw_updater.cpp
|
||||
|
||||
src/utilities/filesystem.cpp
|
||||
src/utilities/logging.cpp
|
||||
src/utilities/parse_config.cpp
|
||||
src/utilities/print_config.cpp
|
||||
src/utilities/string.cpp
|
||||
src/utilities/symlink.cpp
|
||||
src/utilities/sync_udp_client.cpp
|
||||
src/utilities/type_converters.cpp
|
||||
|
||||
src/can_bridge.cpp
|
||||
src/charge_bridge.cpp
|
||||
src/bsp_bridge.cpp
|
||||
src/gpio_bridge.cpp
|
||||
src/heartbeat_service.cpp
|
||||
src/plc_bridge.cpp
|
||||
src/serial_bridge.cpp
|
||||
src/discovery.cpp
|
||||
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(pionix_chargebridge
|
||||
everest::io
|
||||
everest::everest_api_types
|
||||
nlohmann_json::nlohmann_json
|
||||
ryml::ryml
|
||||
)
|
||||
|
||||
target_include_directories(pionix_chargebridge
|
||||
PRIVATE include
|
||||
PRIVATE shared
|
||||
)
|
||||
|
||||
set(cb_firmware_binary config/firmware/charge-bridge-fw_complete.cbfw)
|
||||
|
||||
add_custom_command(
|
||||
TARGET pionix_chargebridge
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${cb_firmware_binary}"
|
||||
"$<TARGET_FILE_DIR:pionix_chargebridge>/"
|
||||
COMMENT "Copying Pionix ChargeBridge firmware binary..."
|
||||
)
|
||||
|
||||
|
||||
install (TARGETS pionix_chargebridge)
|
||||
install (FILES "${CMAKE_CURRENT_SOURCE_DIR}/${cb_firmware_binary}" DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/chargebridge/firmware)
|
||||
install (FILES "${CMAKE_CURRENT_SOURCE_DIR}/config/config-CB-EVAL.yaml" DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/chargebridge RENAME "config-CB-EVAL.yaml-example")
|
||||
install (FILES "${CMAKE_CURRENT_SOURCE_DIR}/config/config-CB-SAT-AC.yaml" DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/chargebridge RENAME "config-CB-SAT-AC.yaml-example")
|
||||
install (FILES "${CMAKE_CURRENT_SOURCE_DIR}/config/config-CB-EVAL-EV.yaml" DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/chargebridge RENAME "config-CB-EVAL-EV.yaml-example")
|
||||
install (FILES "${CMAKE_CURRENT_SOURCE_DIR}/config/config-CB-EVAL-SIM.yaml" DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/chargebridge RENAME "config-CB-EVAL-SIM.yaml-example")
|
||||
@@ -0,0 +1,154 @@
|
||||
charge_bridge:
|
||||
name: cb_eval_ev
|
||||
ip: ANY_EV
|
||||
#ip: chargebridge-44b7d0c99629.local
|
||||
fw_file: ./firmware/charge-bridge-fw_complete.cbfw
|
||||
fw_update_on_start: true
|
||||
mdns_name: ""
|
||||
|
||||
heartbeat:
|
||||
interval_s: 1
|
||||
connection_to_s: 10
|
||||
|
||||
safety:
|
||||
pp_mode: "disabled"
|
||||
cp_avg_ms: 10
|
||||
inverted_emergency_input: 0
|
||||
relay_1:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 200
|
||||
feedback_inverted: true
|
||||
# PWM not supported yet
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_2:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 200
|
||||
feedback_inverted: true
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_3:
|
||||
relay_mode: "UserRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: false
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
|
||||
can_0:
|
||||
enable: true
|
||||
local: "cb_ev_can"
|
||||
baudrate: 125000
|
||||
|
||||
serial_1:
|
||||
enable: true
|
||||
local: "/dev/cb_ev_uart"
|
||||
baudrate: 115200
|
||||
stopbits: OneStopBit
|
||||
parity: None
|
||||
|
||||
serial_2:
|
||||
enable: true
|
||||
local: "/dev/cb_ev_rs485"
|
||||
baudrate: 19200
|
||||
stopbits: OneStopBit
|
||||
parity: Even
|
||||
|
||||
plc:
|
||||
enable: true
|
||||
tap: "cb_ev_plc"
|
||||
ip: 172.25.6.1
|
||||
netmask: 255.255.255.0
|
||||
mtu: 1518
|
||||
powersaving_mode: 1
|
||||
|
||||
ev_bsp:
|
||||
enable: true
|
||||
module_id: "ev_bsp_1"
|
||||
mqtt_remote: 127.0.0.1
|
||||
mqtt_port: 1883
|
||||
mqtt_bind: 127.0.0.1
|
||||
ovm_enabled: false
|
||||
ovm_module_id: "ovm_1"
|
||||
|
||||
evse_bsp:
|
||||
enable: false
|
||||
module_id: "bsp_1"
|
||||
mqtt_remote: 127.0.0.1
|
||||
mqtt_port: 1883
|
||||
mqtt_bind: 127.0.0.1
|
||||
capabilities:
|
||||
max_current_A_import: 16
|
||||
min_current_A_import: 6
|
||||
max_phase_count_import: 3
|
||||
min_phase_count_import: 3
|
||||
max_current_A_export: 16
|
||||
min_current_A_export: 6
|
||||
max_phase_count_export: 3
|
||||
min_phase_count_export: 3
|
||||
supports_changing_phases_during_charging: false
|
||||
connector_type: "IEC62196Type2Cable"
|
||||
max_plug_temperature_C: 250
|
||||
ovm_enabled: true
|
||||
ovm_module_id: "ovm_1"
|
||||
|
||||
gpio:
|
||||
enable: true
|
||||
interval_s: 1
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_ping_interval_ms: 5000
|
||||
gpio_0:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_1:
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
config: 32767
|
||||
gpio_2:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_3:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_4:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 1000
|
||||
gpio_5:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_6:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_7:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_8:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_9:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
@@ -0,0 +1,153 @@
|
||||
charge_bridge:
|
||||
name: cb_eval
|
||||
ip: ANY_EVSE
|
||||
#ip: chargebridge-44b7d0c99629.local
|
||||
fw_file: ./firmware/charge-bridge-fw_complete.cbfw
|
||||
fw_update_on_start: true
|
||||
mdns_name: ""
|
||||
|
||||
heartbeat:
|
||||
interval_s: 1
|
||||
connection_to_s: 10
|
||||
|
||||
safety:
|
||||
pp_mode: "disabled"
|
||||
cp_avg_ms: 10
|
||||
inverted_emergency_input: 0
|
||||
relay_1:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 200
|
||||
feedback_inverted: true
|
||||
# PWM not supported yet
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_2:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 200
|
||||
feedback_inverted: true
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_3:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: true
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
|
||||
can_0:
|
||||
enable: true
|
||||
local: "cb_can"
|
||||
baudrate: 125000
|
||||
|
||||
serial_1:
|
||||
enable: true
|
||||
local: "/dev/cb_uart"
|
||||
baudrate: 115200
|
||||
stopbits: OneStopBit
|
||||
parity: None
|
||||
|
||||
serial_2:
|
||||
enable: true
|
||||
local: "/dev/cb_rs485"
|
||||
baudrate: 19200
|
||||
stopbits: OneStopBit
|
||||
parity: Even
|
||||
|
||||
plc:
|
||||
enable: true
|
||||
tap: "cb_plc"
|
||||
ip: 172.25.6.1
|
||||
netmask: 255.255.255.0
|
||||
mtu: 1518
|
||||
powersaving_mode: 1
|
||||
|
||||
ev_bsp:
|
||||
enable: false
|
||||
module_id: "ev_bsp_1"
|
||||
mqtt_remote: 127.0.0.1
|
||||
mqtt_port: 1883
|
||||
mqtt_bind: 127.0.0.1
|
||||
ovm_enabled: false
|
||||
ovm_module_id: "ovm_1"
|
||||
|
||||
evse_bsp:
|
||||
enable: true
|
||||
module_id: "cb_bsp"
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_ping_interval_ms: 5000
|
||||
ovm_enabled: true
|
||||
ovm_module_id: "cb_ovm"
|
||||
capabilities:
|
||||
max_current_A_import: 16
|
||||
min_current_A_import: 0
|
||||
max_phase_count_import: 3
|
||||
min_phase_count_import: 3
|
||||
max_current_A_export: 16
|
||||
min_current_A_export: 0
|
||||
max_phase_count_export: 3
|
||||
min_phase_count_export: 3
|
||||
supports_changing_phases_during_charging: false
|
||||
connector_type: "IEC62196Type2Cable"
|
||||
|
||||
gpio:
|
||||
enable: true
|
||||
interval_s: 1
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_ping_interval_ms: 5000
|
||||
gpio_0:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_1:
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
config: 32767
|
||||
gpio_2:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_3:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_4:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 1000
|
||||
gpio_5:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_6:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_7:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_8:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_9:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
@@ -0,0 +1,147 @@
|
||||
charge_bridge:
|
||||
name: cb_eval
|
||||
ip: ANY_EVSE
|
||||
#ip: chargebridge-44b7d0c9bcc0.local^
|
||||
#ip: chargebridge-44b7d0c99629.local
|
||||
fw_file: ./firmware/charge-bridge-fw_complete.cbfw
|
||||
fw_update_on_start: true
|
||||
mdns_name: ""
|
||||
|
||||
heartbeat:
|
||||
interval_s: 1
|
||||
connection_to_s: 10
|
||||
|
||||
safety:
|
||||
pp_mode: "disabled"
|
||||
cp_avg_ms: 10
|
||||
inverted_emergency_input: 0
|
||||
relay_1:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: true
|
||||
feedback_delay_ms: 200
|
||||
feedback_inverted: true
|
||||
# PWM not supported yet
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_2:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: true
|
||||
feedback_delay_ms: 200
|
||||
feedback_inverted: true
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_3:
|
||||
relay_mode: "UserRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: false
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
|
||||
can_0:
|
||||
enable: true
|
||||
local: "cb_can"
|
||||
baudrate: 125000
|
||||
|
||||
serial_1:
|
||||
enable: true
|
||||
local: "/dev/cb_uart"
|
||||
baudrate: 115200
|
||||
stopbits: OneStopBit
|
||||
parity: None
|
||||
|
||||
serial_2:
|
||||
enable: true
|
||||
local: "/dev/cb_rs485"
|
||||
baudrate: 19200
|
||||
stopbits: OneStopBit
|
||||
parity: Even
|
||||
|
||||
plc:
|
||||
enable: true
|
||||
tap: "cb_plc"
|
||||
ip: 172.25.6.2
|
||||
netmask: 255.255.255.0
|
||||
mtu: 1518
|
||||
powersaving_mode: 1
|
||||
|
||||
evse_bsp:
|
||||
enable: true
|
||||
module_id: "cb_bsp"
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_bind: 127.0.0.1
|
||||
mqtt_ping_interval_ms: 5000
|
||||
ovm_enabled: true
|
||||
ovm_module_id: "cb_ovm"
|
||||
capabilities:
|
||||
max_current_A_import: 16
|
||||
min_current_A_import: 0
|
||||
max_phase_count_import: 3
|
||||
min_phase_count_import: 3
|
||||
max_current_A_export: 16
|
||||
min_current_A_export: 0
|
||||
max_phase_count_export: 3
|
||||
min_phase_count_export: 3
|
||||
supports_changing_phases_during_charging: false
|
||||
connector_type: "IEC62196Type2Cable"
|
||||
|
||||
gpio:
|
||||
enable: true
|
||||
interval_s: 1
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_bind: 127.0.0.1
|
||||
mqtt_ping_interval_ms: 5000
|
||||
gpio_0:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_1:
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
config: 32767
|
||||
gpio_2:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_3:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_4:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 1000
|
||||
gpio_5:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_6:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_7:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_8:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_9:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
@@ -0,0 +1,169 @@
|
||||
charge_bridge:
|
||||
name: cb_sat_ac
|
||||
ip: ANY_EVSE
|
||||
fw_file: ./firmware/charge-bridge-fw_complete.cbfw
|
||||
fw_update_on_start: true
|
||||
mdns_name: ""
|
||||
|
||||
heartbeat:
|
||||
interval_s: 1
|
||||
connection_to_s: 10
|
||||
|
||||
safety:
|
||||
pp_mode: "disabled"
|
||||
cp_avg_ms: 10
|
||||
inverted_emergency_input: 0
|
||||
relay_1:
|
||||
relay_mode: "PowerRelay"
|
||||
# Auxilary contact is connected from the OMRON relay
|
||||
feedback_enabled: true
|
||||
# The Omron relay switches in less than 100ms, use 200ms here
|
||||
feedback_delay_ms: 200
|
||||
# Only for PCB version 1.1, set to false for PCB version 1.2 and up
|
||||
feedback_inverted: true
|
||||
# PWM not supported yet
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_2:
|
||||
# Not connected on PCB
|
||||
relay_mode: "UserRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: false
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_3:
|
||||
# Not connected on PCB
|
||||
relay_mode: "UserRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: false
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
|
||||
can_0:
|
||||
enable: true
|
||||
local: "cb_can"
|
||||
baudrate: 250000
|
||||
|
||||
serial_1:
|
||||
enable: true
|
||||
local: "/dev/cb_uart"
|
||||
baudrate: 115200
|
||||
stopbits: OneStopBit
|
||||
parity: None
|
||||
|
||||
serial_2:
|
||||
enable: true
|
||||
local: "/dev/cb_rs485"
|
||||
baudrate: 9600
|
||||
stopbits: OneStopBit
|
||||
parity: None
|
||||
|
||||
plc:
|
||||
enable: true
|
||||
tap: "cb_plc"
|
||||
ip: 172.25.6.1
|
||||
netmask: 255.255.255.0
|
||||
mtu: 1518
|
||||
powersaving_mode: 1
|
||||
|
||||
evse_bsp:
|
||||
enable: true
|
||||
module_id: "cb_bsp"
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_bind: 127.0.0.1
|
||||
mqtt_ping_interval_ms: 1000
|
||||
capabilities:
|
||||
max_current_A_import: 16
|
||||
min_current_A_import: 6
|
||||
max_phase_count_import: 3
|
||||
min_phase_count_import: 3
|
||||
max_current_A_export: 16
|
||||
min_current_A_export: 6
|
||||
max_phase_count_export: 3
|
||||
min_phase_count_export: 3
|
||||
supports_changing_phases_during_charging: false
|
||||
connector_type: "IEC62196Type2Cable"
|
||||
ovm_enabled: false
|
||||
ovm_module_id: cb_ovm
|
||||
|
||||
gpio:
|
||||
enable: true
|
||||
interval_s: 1
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_bind: 127.0.0.1
|
||||
mqtt_ping_interval_ms: 1000
|
||||
gpio_0:
|
||||
# RCD.TEST
|
||||
#mode: "Rcd_Selftest_Output"
|
||||
# Self test not fully supported yet
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
# RCD self test duration (ignore emergency input signals for this time after self test)
|
||||
# and show reason of safety decision on host somehow to simplify debugging
|
||||
config: 1000
|
||||
gpio_1:
|
||||
# RCD.ERROR
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
config: 0
|
||||
gpio_2:
|
||||
# MOTOR_1
|
||||
# simple Motor lock with only 2 wires (no feedback contacts)
|
||||
mode: "MotorLock_1"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
# 1000 ms motor drive time for locking/unlocking
|
||||
config: 1000
|
||||
gpio_3:
|
||||
# MOTOR_2
|
||||
# simple Motor lock with only 2 wires (no feedback contacts)
|
||||
mode: "MotorLock_2"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
config: 1000
|
||||
gpio_4:
|
||||
# RCD.PWM
|
||||
# not supported yet
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
config: 0
|
||||
gpio_5:
|
||||
# External GPIO on connector J4 pin 9 (10kOhm I/O)
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
config: 0
|
||||
gpio_6:
|
||||
# External GPIO on connector J4 pin 10 (10kOhm I/O)
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 0
|
||||
gpio_7:
|
||||
# External GPIO on connector J3 pin 11 (10kOhm I/O)
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
config: 0
|
||||
gpio_8:
|
||||
# External GPIO on connector J3 pin 12 (10kOhm I/O)
|
||||
mode: "Input"
|
||||
pulls: "PullDown"
|
||||
mdns: false
|
||||
config: 0
|
||||
gpio_9:
|
||||
# Not connected
|
||||
mode: "Input"
|
||||
pulls: "PullDown"
|
||||
mdns: false
|
||||
config: 0
|
||||
@@ -0,0 +1,143 @@
|
||||
charge_bridge_ip_list : [ "192.168.188.65", "192.168.188.65"]
|
||||
|
||||
charge_bridge:
|
||||
name: cb_##
|
||||
ip: ""
|
||||
fw_file: ./firmware/charge-bridge-fw_complete.cbfw
|
||||
fw_update_on_start: false
|
||||
mdns_name: "pionix_cb_##"
|
||||
|
||||
heartbeat:
|
||||
interval_s: 1
|
||||
connection_to_s: 10
|
||||
|
||||
safety:
|
||||
pp_mode: "disabled"
|
||||
cp_avg_ms: 10
|
||||
relay_1:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: false
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_2:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: false
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
relay_3:
|
||||
relay_mode: "PowerRelay"
|
||||
feedback_enabled: false
|
||||
feedback_delay_ms: 10
|
||||
feedback_inverted: false
|
||||
pwm_dc: 100
|
||||
pwm_delay_ms: 0
|
||||
switchoff_delay_ms: 10
|
||||
|
||||
can_0:
|
||||
enable: true
|
||||
local: "cb_##_can0"
|
||||
baudrate: 250000
|
||||
|
||||
serial_1:
|
||||
enable: true
|
||||
local: "/dev/cb_##_serial_1"
|
||||
baudrate: 19200
|
||||
stopbits: OneStopBit
|
||||
parity: None
|
||||
|
||||
serial_2:
|
||||
enable: false
|
||||
local: "/dev/cb_##_serial_2"
|
||||
baudrate: 19200
|
||||
stopbits: OneStopBit
|
||||
parity: None
|
||||
|
||||
plc:
|
||||
enable: false
|
||||
tap: "cb_##_tap0"
|
||||
ip: 172.25.6.1
|
||||
netmask: 255.255.255.0
|
||||
mtu: 1518
|
||||
powersaving_mode: 1
|
||||
|
||||
evse_bsp:
|
||||
enable: false
|
||||
module_id: "bsp_##"
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_ping_interval_ms: 1000
|
||||
capabilities:
|
||||
max_current_A_import: 16
|
||||
min_current_A_import: 6
|
||||
max_phase_count_import: 3
|
||||
min_phase_count_import: 3
|
||||
max_current_A_export: 16
|
||||
min_current_A_export: 6
|
||||
max_phase_count_export: 3
|
||||
min_phase_count_export: 3
|
||||
supports_changing_phases_during_charging: false
|
||||
connector_type: "IEC62196Type2Cable"
|
||||
ovm_enabled: true
|
||||
ovm_module_id: "ovm_1"
|
||||
|
||||
gpio:
|
||||
enable: false
|
||||
interval_s: 4
|
||||
mqtt_remote: "localhost"
|
||||
mqtt_port: 1883
|
||||
mqtt_ping_interval_ms: 1000
|
||||
gpio_0:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_1:
|
||||
mode: "Input"
|
||||
pulls: "NoPull"
|
||||
config: 32767
|
||||
gpio_2:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_3:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_4:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_5:
|
||||
mode: "Output"
|
||||
pulls: "NoPull"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_6:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_7:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_8:
|
||||
mode: "Output"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
gpio_9:
|
||||
mode: "Input"
|
||||
pulls: "PullUp"
|
||||
mdns: false
|
||||
config: 32767
|
||||
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <charge_bridge/everest_api/api_connector.hpp>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/udp/udp_client.hpp>
|
||||
#include <everest_api_types/evse_board_support/API.hpp>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
struct bsp_bridge_config {
|
||||
std::string cb;
|
||||
std::string item;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
evse_bsp::everest_api_config api;
|
||||
};
|
||||
|
||||
class bsp_bridge : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
bsp_bridge(bsp_bridge_config const& config);
|
||||
~bsp_bridge() = default;
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
private:
|
||||
void handle_timer_event();
|
||||
|
||||
evse_bsp::api_connector m_api;
|
||||
everest::lib::io::udp::udp_client m_udp;
|
||||
everest::lib::io::event::timer_fd m_timer;
|
||||
bool m_udp_on_error{false};
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <everest/io/can/can_payload.hpp>
|
||||
#include <everest/io/can/socket_can.hpp>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/udp/udp_client.hpp>
|
||||
#include <memory>
|
||||
|
||||
extern "C" struct cb_can_message;
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
struct can_bridge_config {
|
||||
std::string cb;
|
||||
std::string item;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
std::string can_device;
|
||||
};
|
||||
|
||||
class can_bridge : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
can_bridge(can_bridge_config const& config);
|
||||
~can_bridge();
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
private:
|
||||
void handle_error_timer();
|
||||
void handle_heartbeat_timer();
|
||||
void send_can_to_udp(cb_can_message const& pl);
|
||||
std::unique_ptr<everest::lib::io::can::socket_can> m_can;
|
||||
everest::lib::io::udp::udp_client m_udp;
|
||||
std::string m_can_device;
|
||||
std::string m_identifier;
|
||||
everest::lib::io::event::timer_fd m_heartbeat_timer;
|
||||
std::chrono::steady_clock::time_point m_last_msg_to_cb;
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <charge_bridge/bsp_bridge.hpp>
|
||||
#include <charge_bridge/can_bridge.hpp>
|
||||
#include <charge_bridge/discovery.hpp>
|
||||
#include <charge_bridge/firmware_update/sync_fw_updater.hpp>
|
||||
#include <charge_bridge/gpio_bridge.hpp>
|
||||
#include <charge_bridge/heartbeat_service.hpp>
|
||||
#include <charge_bridge/plc_bridge.hpp>
|
||||
#include <charge_bridge/serial_bridge.hpp>
|
||||
#include <charge_bridge/utilities/symlink.hpp>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/serial/event_pty.hpp>
|
||||
#include <everest/io/tun_tap/tap_client.hpp>
|
||||
#include <everest/util/async/monitor.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
struct charge_bridge_status {
|
||||
bool is_connected{false};
|
||||
bool discovery_pending{false};
|
||||
};
|
||||
|
||||
struct charge_bridge_config {
|
||||
std::string cb_name;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
std::optional<can_bridge_config> can0;
|
||||
std::optional<serial_bridge_config> serial1;
|
||||
std::optional<serial_bridge_config> serial2;
|
||||
std::optional<serial_bridge_config> serial3;
|
||||
std::optional<plc_bridge_config> plc;
|
||||
std::optional<bsp_bridge_config> bsp;
|
||||
std::optional<heartbeat_config> heartbeat;
|
||||
std::optional<gpio_config> gpio;
|
||||
firmware_update::fw_update_config firmware;
|
||||
};
|
||||
|
||||
void print_charge_bridge_config(charge_bridge_config const& config);
|
||||
|
||||
class charge_bridge : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
charge_bridge(charge_bridge_config const& config);
|
||||
~charge_bridge();
|
||||
|
||||
bool update_firmware(bool force);
|
||||
|
||||
std::string get_pty_1_slave_path();
|
||||
std::string get_pty_2_slave_path();
|
||||
std::string get_pty_3_slave_path();
|
||||
|
||||
void print_config();
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
void manage(everest::lib::io::event::fd_event_handler& handler, std::atomic_bool const& exit, bool force_update);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void init_discovery(discovery_device_type type, std::set<std::string> const& interfaces, bool excluding);
|
||||
void handle_discovery(std::string const& ip);
|
||||
|
||||
private:
|
||||
std::unique_ptr<can_bridge> m_can_0_client;
|
||||
std::unique_ptr<serial_bridge> m_pty_1;
|
||||
std::unique_ptr<serial_bridge> m_pty_2;
|
||||
std::unique_ptr<serial_bridge> m_pty_3;
|
||||
std::unique_ptr<bsp_bridge> m_bsp;
|
||||
std::unique_ptr<plc_bridge> m_plc;
|
||||
std::unique_ptr<heartbeat_service> m_heartbeat;
|
||||
std::unique_ptr<gpio_bridge> m_gpio;
|
||||
std::unique_ptr<discovery> m_discovery;
|
||||
|
||||
everest::lib::io::event::fd_event_handler* m_event_handler{nullptr};
|
||||
bool m_force_firmware_update{false};
|
||||
everest::lib::util::monitor<charge_bridge_status> m_cb_status;
|
||||
bool m_was_connected{false};
|
||||
bool m_discovery_active{false};
|
||||
|
||||
charge_bridge_config m_config;
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/mdns/mdns.hpp>
|
||||
#include <everest/io/mdns/mdns_client.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
enum class discovery_device_type {
|
||||
CB_EVSE,
|
||||
CB_EV
|
||||
};
|
||||
|
||||
class discovery : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
using discovery_cb = std::function<void(std::string const&)>;
|
||||
|
||||
discovery(discovery_device_type type);
|
||||
discovery(discovery_device_type type, std::set<std::string> const& interfaces, bool excluding);
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
void set_discovery_callback(discovery_cb const& cb);
|
||||
|
||||
private:
|
||||
void add_client(std::string const& interface);
|
||||
void query_registry();
|
||||
|
||||
std::vector<std::unique_ptr<everest::lib::io::mdns::mdns_client>> m_mdns;
|
||||
everest::lib::io::event::timer_fd m_timer;
|
||||
discovery_cb m_on_discover;
|
||||
everest::lib::io::mdns::mDNS_registry m_registry;
|
||||
discovery_device_type m_type;
|
||||
static const std::string discovery_id;
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <charge_bridge/everest_api/ev_bsp_api.hpp>
|
||||
#include <charge_bridge/everest_api/evse_bsp_api.hpp>
|
||||
#include <charge_bridge/everest_api/ovm_api.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/mqtt/mqtt_client.hpp>
|
||||
#include <everest_api_types/evse_board_support/API.hpp>
|
||||
#include <everest_api_types/evse_manager/API.hpp>
|
||||
#include <everest_api_types/utilities/Topics.hpp>
|
||||
#include <functional>
|
||||
#include <protocol/cb_common.h>
|
||||
#include <protocol/evse_bsp_cb_to_host.h>
|
||||
#include <protocol/evse_bsp_host_to_cb.h>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
|
||||
namespace API_BSP = everest::lib::API::V1_0::types::evse_board_support;
|
||||
|
||||
struct everest_api_config {
|
||||
std::string mqtt_remote;
|
||||
std::string mqtt_bind;
|
||||
uint16_t mqtt_port;
|
||||
uint32_t mqtt_ping_interval_ms;
|
||||
evse_bsp_config evse;
|
||||
evse_ovm_config ovm;
|
||||
evse_ev_bsp_config ev;
|
||||
};
|
||||
|
||||
class api_connector : public everest::lib::io::event::fd_event_register_interface {
|
||||
using tx_ftor = std::function<void(evse_bsp_host_to_cb const&)>;
|
||||
using rx_ftor = std::function<void(evse_bsp_cb_to_host const&)>;
|
||||
|
||||
public:
|
||||
api_connector(everest_api_config const& config, std::string const& cb_identifier);
|
||||
void set_cb_tx(tx_ftor const& handler);
|
||||
void set_cb_message(evse_bsp_cb_to_host const& msg);
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
private:
|
||||
void handle_mqtt_connect();
|
||||
void handle_cb_connection_state();
|
||||
bool check_cb_heartbeat();
|
||||
|
||||
std::string m_cb_identifier;
|
||||
everest::lib::io::mqtt::mqtt_client m_mqtt;
|
||||
tx_ftor m_tx;
|
||||
std::chrono::steady_clock::time_point m_last_cb_heartbeat;
|
||||
everest::lib::io::event::timer_fd m_sync_timer;
|
||||
|
||||
std::string m_evse_bsp_receive_topic;
|
||||
std::string m_evse_bsp_send_topic;
|
||||
std::string m_ovm_receive_topic;
|
||||
std::string m_ovm_send_topic;
|
||||
std::string m_ev_bsp_receive_topic;
|
||||
std::string m_ev_bsp_send_topic;
|
||||
bool m_evse_bsp_enabled{false};
|
||||
bool m_ovm_enabled{false};
|
||||
bool m_ev_bsp_enabled{false};
|
||||
bool m_cb_initial_comm_check{true};
|
||||
bool m_cb_connected{false};
|
||||
evse_bsp_host_to_cb m_host_status;
|
||||
|
||||
evse_bsp_api m_evse_bsp;
|
||||
ovm_api m_ovm;
|
||||
ev_bsp_api m_ev_bsp;
|
||||
};
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest_api_types/ev_board_support/API.hpp>
|
||||
#include <everest_api_types/evse_board_support/API.hpp>
|
||||
#include <everest_api_types/evse_manager/API.hpp>
|
||||
#include <everest_api_types/generic/API.hpp>
|
||||
#include <everest_api_types/utilities/Topics.hpp>
|
||||
#include <functional>
|
||||
#include <protocol/cb_common.h>
|
||||
#include <protocol/evse_bsp_cb_to_host.h>
|
||||
#include <protocol/evse_bsp_host_to_cb.h>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
namespace API_EVSE_BSP = everest::lib::API::V1_0::types::evse_board_support;
|
||||
namespace API_EV_BSP = everest::lib::API::V1_0::types::ev_board_support;
|
||||
namespace API_EVM = everest::lib::API::V1_0::types::evse_manager;
|
||||
namespace API_GENERIC = everest::lib::API::V1_0::types::generic;
|
||||
// namespace API_OVM = everest::lib::API::V1_0::types::over_voltage_monitor;
|
||||
|
||||
struct evse_ev_bsp_config {
|
||||
bool enabled{false};
|
||||
std::string module_id;
|
||||
};
|
||||
|
||||
class ev_bsp_api : public everest::lib::io::event::fd_event_register_interface {
|
||||
using tx_ftor = std::function<void(evse_bsp_host_to_cb const&)>;
|
||||
using rx_ftor = std::function<void(evse_bsp_cb_to_host const&)>;
|
||||
using mqtt_ftor = std::function<void(std::string const&, std::string const&)>;
|
||||
|
||||
public:
|
||||
ev_bsp_api(evse_ev_bsp_config const& config, std::string const& cb_identifier, evse_bsp_host_to_cb& host_status);
|
||||
void set_cb_tx(tx_ftor const& handler);
|
||||
void set_cb_message(evse_bsp_cb_to_host const& msg);
|
||||
void set_mqtt_tx(mqtt_ftor const& tx);
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
void dispatch(std::string const& operation, std::string const& payload);
|
||||
|
||||
void raise_comm_fault();
|
||||
void clear_comm_fault();
|
||||
void sync(bool cb_connected);
|
||||
|
||||
private:
|
||||
void tx(evse_bsp_host_to_cb const& msg);
|
||||
|
||||
void send_bsp_event(API_EVSE_BSP::Event data);
|
||||
void send_bsp_measurement(API_EV_BSP::BspMeasurement data);
|
||||
void send_ev_info(API_EVM::EVInfo data);
|
||||
|
||||
void send_raise_error(API_GENERIC::ErrorEnum error, std::string const& subtype, std::string const& msg);
|
||||
void send_clear_error(API_GENERIC::ErrorEnum error, std::string const& subtype);
|
||||
|
||||
void send_communication_check();
|
||||
|
||||
void send_mqtt(std::string const& topic, std::string const& message);
|
||||
|
||||
void send_event(API_EVSE_BSP::Event data);
|
||||
|
||||
void receive_enable(std::string const& payload);
|
||||
void receive_set_cp_state(std::string const& payload);
|
||||
void receive_allow_power_on(std::string const& payload);
|
||||
void receive_diode_fail(std::string const& payload);
|
||||
void receive_set_ac_max_current(std::string const& payload);
|
||||
void receive_set_three_phases(std::string const& payload);
|
||||
void receive_set_rcd_error(std::string const& payload);
|
||||
void receive_heartbeat(std::string const& pl);
|
||||
|
||||
void handle_error(const SafetyErrorFlags& data);
|
||||
void handle_event_cp(std::uint8_t cp);
|
||||
void handle_event_relay(std::uint8_t relay);
|
||||
void handle_bsp_measurement(uint16_t cp, uint8_t pp_1, uint8_t pp2);
|
||||
|
||||
bool check_everest_heartbeat();
|
||||
void handle_everest_connection_state();
|
||||
|
||||
evse_bsp_host_to_cb& host_status;
|
||||
evse_bsp_cb_to_host m_cb_status;
|
||||
|
||||
tx_ftor m_tx;
|
||||
bool m_everest_connected{false};
|
||||
bool m_cb_connected{false};
|
||||
bool m_cb_initial_comm_check{true};
|
||||
bool m_bc_initial_comm_check{true};
|
||||
std::string m_cb_identifier;
|
||||
std::chrono::steady_clock::time_point last_everest_heartbeat;
|
||||
|
||||
mqtt_ftor m_mqtt_tx;
|
||||
std::size_t m_last_hb_id{0};
|
||||
everest::lib::API::V1_0::types::evse_board_support::Event last_cp_event{
|
||||
everest::lib::API::V1_0::types::evse_board_support::Event::Disconnected};
|
||||
};
|
||||
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest_api_types/evse_board_support/API.hpp>
|
||||
#include <everest_api_types/evse_manager/API.hpp>
|
||||
#include <everest_api_types/utilities/Topics.hpp>
|
||||
#include <functional>
|
||||
#include <protocol/cb_common.h>
|
||||
#include <protocol/evse_bsp_cb_to_host.h>
|
||||
#include <protocol/evse_bsp_host_to_cb.h>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
namespace API_BSP = everest::lib::API::V1_0::types::evse_board_support;
|
||||
namespace API_EVM = everest::lib::API::V1_0::types::evse_manager;
|
||||
|
||||
struct evse_bsp_config {
|
||||
std::string module_id;
|
||||
bool enabled{false};
|
||||
API_BSP::HardwareCapabilities capabilities;
|
||||
};
|
||||
|
||||
class evse_bsp_api : public everest::lib::io::event::fd_event_register_interface {
|
||||
using tx_ftor = std::function<void(evse_bsp_host_to_cb const&)>;
|
||||
using rx_ftor = std::function<void(evse_bsp_cb_to_host const&)>;
|
||||
using mqtt_ftor = std::function<void(std::string const&, std::string const&)>;
|
||||
|
||||
public:
|
||||
evse_bsp_api(evse_bsp_config const& config, std::string const& cb_identifier, evse_bsp_host_to_cb& host_status);
|
||||
void set_cb_tx(tx_ftor const& handler);
|
||||
void set_cb_message(evse_bsp_cb_to_host const& msg);
|
||||
void set_mqtt_tx(mqtt_ftor const& tx);
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
void dispatch(std::string const& operation, std::string const& payload);
|
||||
|
||||
void raise_comm_fault();
|
||||
void clear_comm_fault();
|
||||
void sync(bool cb_connected);
|
||||
|
||||
private:
|
||||
void tx(evse_bsp_host_to_cb const& msg);
|
||||
|
||||
void handle_event_cp(std::uint8_t cp);
|
||||
void handle_event_relay(std::uint8_t relay);
|
||||
void handle_error(const SafetyErrorFlags& data);
|
||||
void handle_pp_type1(std::uint8_t data);
|
||||
void handle_pp_type2(std::uint8_t data);
|
||||
void handle_stop_button(std::uint8_t data);
|
||||
|
||||
void send_event(API_BSP::Event data);
|
||||
void send_ac_nr_of_phases(std::uint8_t data);
|
||||
void send_capabilities();
|
||||
void send_ac_pp_amapcity(API_BSP::Ampacity data);
|
||||
void send_request_stop_transaction(API_EVM::StopTransactionReason data);
|
||||
void send_rcd_current(std::uint8_t data);
|
||||
void send_raise_error(API_BSP::ErrorEnum error, std::string const& subtype, std::string const& msg);
|
||||
void send_clear_error(API_BSP::ErrorEnum error, std::string const& subtype, std::string const& msg);
|
||||
void send_communication_check();
|
||||
void send_reply_reset(std::string const& replyTo);
|
||||
|
||||
void send_mqtt(std::string const& topic, std::string const& message);
|
||||
|
||||
void receive_enable(std::string const& payload);
|
||||
void receive_pwm_on(std::string const& payload);
|
||||
void receive_cp_state_X1(std::string const& payload);
|
||||
void receive_cp_state_F(std::string const& payload);
|
||||
void receive_allow_power_on(std::string const& payload);
|
||||
void receive_ac_switch_three_phases_while_charging(std::string const& payload);
|
||||
void receive_ac_overcurrent_limit(std::string const& payload);
|
||||
void receive_lock();
|
||||
void receive_unlock();
|
||||
void receive_self_test(std::string const& payload);
|
||||
void receive_request_reset(std::string const& payload);
|
||||
void receive_heartbeat(std::string const& pl);
|
||||
|
||||
bool check_everest_heartbeat();
|
||||
void handle_everest_connection_state();
|
||||
|
||||
evse_bsp_host_to_cb& host_status;
|
||||
evse_bsp_cb_to_host cb_status;
|
||||
|
||||
tx_ftor m_tx;
|
||||
everest::lib::io::event::timer_fd m_capabilities_timer;
|
||||
API_BSP::HardwareCapabilities m_capabilities;
|
||||
bool m_enabled{false};
|
||||
bool everest_connected{false};
|
||||
bool m_cb_connected{false};
|
||||
bool m_bc_initial_comm_check{true};
|
||||
std::string m_cb_identifier;
|
||||
std::chrono::steady_clock::time_point last_everest_heartbeat;
|
||||
|
||||
mqtt_ftor m_mqtt_tx;
|
||||
std::size_t m_last_hb_id{0};
|
||||
};
|
||||
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,85 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest_api_types/evse_manager/API.hpp>
|
||||
#include <everest_api_types/over_voltage_monitor/API.hpp>
|
||||
#include <everest_api_types/utilities/Topics.hpp>
|
||||
#include <functional>
|
||||
#include <protocol/cb_common.h>
|
||||
#include <protocol/evse_bsp_cb_to_host.h>
|
||||
#include <protocol/evse_bsp_host_to_cb.h>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
namespace API_OVM = everest::lib::API::V1_0::types::over_voltage_monitor;
|
||||
|
||||
struct evse_ovm_config {
|
||||
bool enabled{false};
|
||||
std::string module_id;
|
||||
};
|
||||
|
||||
class ovm_api : public everest::lib::io::event::fd_event_register_interface {
|
||||
using tx_ftor = std::function<void(evse_bsp_host_to_cb const&)>;
|
||||
using rx_ftor = std::function<void(evse_bsp_cb_to_host const&)>;
|
||||
using mqtt_ftor = std::function<void(std::string const&, std::string const&)>;
|
||||
|
||||
public:
|
||||
ovm_api(evse_ovm_config const& config, std::string const& cb_identifier, evse_bsp_host_to_cb& host_status);
|
||||
void set_cb_tx(tx_ftor const& handler);
|
||||
void set_cb_message(evse_bsp_cb_to_host const& msg);
|
||||
void set_mqtt_tx(mqtt_ftor const& tx);
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
void dispatch(std::string const& operation, std::string const& payload);
|
||||
|
||||
void raise_comm_fault();
|
||||
void clear_comm_fault();
|
||||
void sync(bool cb_connected);
|
||||
|
||||
private:
|
||||
void tx(evse_bsp_host_to_cb const& msg);
|
||||
|
||||
void send_voltage_measurement_V(double data);
|
||||
void send_raise_error(API_OVM::ErrorEnum error, std::string const& subtype, std::string const& msg,
|
||||
API_OVM::ErrorSeverityEnum severity);
|
||||
void send_clear_error(API_OVM::ErrorEnum error, std::string const& subtype);
|
||||
void send_communication_check();
|
||||
|
||||
void send_mqtt(std::string const& topic, std::string const& message);
|
||||
|
||||
void handle_dc_hv_ov_emergency(bool high);
|
||||
void handle_dc_hv_ov_error(bool high);
|
||||
void handle_cp_state(CpState state);
|
||||
|
||||
void receive_set_limits(std::string const& payload);
|
||||
void receive_start();
|
||||
void receive_stop();
|
||||
void receive_reset_over_voltage_error();
|
||||
void receive_heartbeat(std::string const& pl);
|
||||
|
||||
bool check_everest_heartbeat();
|
||||
void handle_everest_connection_state();
|
||||
|
||||
evse_bsp_host_to_cb& host_status;
|
||||
evse_bsp_cb_to_host m_cb_status;
|
||||
|
||||
tx_ftor m_tx;
|
||||
bool m_everest_connected{false};
|
||||
bool m_cb_connected{false};
|
||||
bool m_cb_initial_comm_check{true};
|
||||
bool m_bc_initial_comm_check{true};
|
||||
std::string m_cb_identifier;
|
||||
std::chrono::steady_clock::time_point last_everest_heartbeat;
|
||||
|
||||
API_OVM::OverVoltageLimits m_limits{0, 0};
|
||||
mqtt_ftor m_mqtt_tx;
|
||||
std::size_t m_last_hb_id{0};
|
||||
};
|
||||
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <charge_bridge/utilities/filesystem.hpp>
|
||||
#include <charge_bridge/utilities/sync_udp_client.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace charge_bridge::firmware_update {
|
||||
|
||||
struct fw_update_config {
|
||||
std::string cb;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
std::string fw_path;
|
||||
bool fw_update_on_start;
|
||||
};
|
||||
|
||||
class sync_fw_updater {
|
||||
public:
|
||||
sync_fw_updater(fw_update_config const& config);
|
||||
~sync_fw_updater() = default;
|
||||
|
||||
std::optional<std::string> get_fw_version();
|
||||
bool switch_bank();
|
||||
bool ping();
|
||||
bool upload_fw();
|
||||
|
||||
void print_fw_version();
|
||||
bool print_switch_bank();
|
||||
bool quick_check_connection();
|
||||
bool check_connection();
|
||||
bool check_if_correct_fw_installed();
|
||||
|
||||
private:
|
||||
bool check_reply(utilities::sync_udp_client::reply const& val);
|
||||
|
||||
bool upload_firmware();
|
||||
|
||||
bool upload_init(const fs::path& file_path, std::uint32_t& offset,
|
||||
charge_bridge::filesystem_utils::CryptSignedHeader& hdr);
|
||||
bool upload_transfer(const fs::path& file_path, std::uint16_t& sector, std::uint32_t offset,
|
||||
std::uint32_t& total_bytes);
|
||||
bool upload_finish(const fs::path& file_path, std::uint32_t total_bytes,
|
||||
const charge_bridge::filesystem_utils::CryptSignedHeader& hdr);
|
||||
|
||||
everest::lib::io::udp::udp_payload make_fw_chunk(std::uint16_t sector, std::uint8_t last_chunk,
|
||||
std::vector<std::uint8_t> const& data);
|
||||
|
||||
utilities::sync_udp_client m_udp;
|
||||
fw_update_config m_config;
|
||||
static const std::uint32_t app_udp_sector_size;
|
||||
static const std::uint16_t sub_chunk_size;
|
||||
};
|
||||
} // namespace charge_bridge::firmware_update
|
||||
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include "everest/io/mqtt/mosquitto_cpp.hpp"
|
||||
#include <array>
|
||||
#include <everest/io/can/can_payload.hpp>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/mqtt/mqtt_client.hpp>
|
||||
#include <everest/io/udp/udp_client.hpp>
|
||||
#include <protocol/cb_management.h>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
struct gpio_config {
|
||||
std::string cb;
|
||||
std::string item;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
std::uint16_t interval_s;
|
||||
std::string mqtt_remote;
|
||||
std::string mqtt_bind;
|
||||
std::uint16_t mqtt_port;
|
||||
std::uint32_t mqtt_ping_interval_ms;
|
||||
};
|
||||
|
||||
class gpio_bridge : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
gpio_bridge(gpio_config const& config);
|
||||
~gpio_bridge();
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
private:
|
||||
void handle_error_timer();
|
||||
void handle_heartbeat_timer();
|
||||
void handle_udp_rx(everest::lib::io::udp::udp_payload const& payload);
|
||||
void dispatch(everest::lib::io::mqtt::mqtt_client::message const& data);
|
||||
void send_mqtt(std::string const& topic, std::string const& message);
|
||||
void send_udp();
|
||||
|
||||
everest::lib::io::udp::udp_client m_udp;
|
||||
bool m_udp_on_error{false};
|
||||
everest::lib::io::event::timer_fd m_heartbeat_timer;
|
||||
std::chrono::steady_clock::time_point last_heartbeat;
|
||||
CbManagementPacket<CbGpioPacket> m_message;
|
||||
std::string m_identifier;
|
||||
bool m_mqtt_on_error{false};
|
||||
everest::lib::io::mqtt::mqtt_client m_mqtt;
|
||||
std::string m_receive_topic;
|
||||
std::string m_send_topic;
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include "protocol/cb_management.h"
|
||||
#include <chrono>
|
||||
#include <everest/io/can/can_payload.hpp>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/udp/udp_client.hpp>
|
||||
#include <memory>
|
||||
#include <protocol/cb_config.h>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
struct heartbeat_config {
|
||||
std::string cb;
|
||||
std::string item;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
std::uint16_t interval_s;
|
||||
std::uint16_t connection_to_s;
|
||||
CbConfig cb_config;
|
||||
};
|
||||
|
||||
class heartbeat_service : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
heartbeat_service(heartbeat_config const& config, std::function<void(bool)> const& publish_connection_status);
|
||||
~heartbeat_service();
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
private:
|
||||
void handle_error_timer();
|
||||
void handle_heartbeat_timer();
|
||||
void handle_udp_rx(everest::lib::io::udp::udp_payload const& payload);
|
||||
|
||||
everest::lib::io::udp::udp_client m_udp;
|
||||
bool m_udp_on_error{false};
|
||||
everest::lib::io::event::timer_fd m_heartbeat_timer;
|
||||
std::string m_identifier;
|
||||
CbManagementPacket<CbConfig> m_config_message;
|
||||
std::chrono::steady_clock::time_point m_last_heartbeat_reply;
|
||||
bool m_cb_connected{false};
|
||||
bool m_inital_cb_commcheck{true};
|
||||
std::chrono::milliseconds m_heartbeat_interval;
|
||||
std::chrono::milliseconds m_connection_to;
|
||||
std::function<void(bool)> m_publish_connection_status;
|
||||
std::uint32_t m_mcu_timestamp{0};
|
||||
int m_mcu_reset_count{0};
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/tun_tap/tap_client.hpp>
|
||||
#include <everest/io/udp/udp_client.hpp>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
struct plc_bridge_config {
|
||||
std::string cb;
|
||||
std::string item;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
std::string plc_tap;
|
||||
std::string plc_ip;
|
||||
std::string plc_netmaks;
|
||||
int plc_mtu;
|
||||
};
|
||||
|
||||
class plc_bridge : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
plc_bridge(plc_bridge_config const& config);
|
||||
~plc_bridge() = default;
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
|
||||
private:
|
||||
void handle_timer_event();
|
||||
|
||||
everest::lib::io::tun_tap::tap_client m_tap;
|
||||
everest::lib::io::udp::udp_client m_udp;
|
||||
everest::lib::io::event::timer_fd m_timer;
|
||||
bool m_udp_on_error{false};
|
||||
bool m_tap_on_error{false};
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <charge_bridge/utilities/symlink.hpp>
|
||||
#include <everest/io/event/fd_event_register_interface.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <everest/io/serial/event_pty.hpp>
|
||||
#include <everest/io/tcp/tcp_client.hpp>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
struct serial_bridge_config {
|
||||
std::string cb;
|
||||
std::string item;
|
||||
std::uint16_t cb_port;
|
||||
std::string cb_remote;
|
||||
std::string serial_device;
|
||||
};
|
||||
|
||||
class serial_bridge : public everest::lib::io::event::fd_event_register_interface {
|
||||
public:
|
||||
serial_bridge(serial_bridge_config const& config);
|
||||
~serial_bridge() = default;
|
||||
|
||||
bool register_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
bool unregister_events(everest::lib::io::event::fd_event_handler& handler) override;
|
||||
std::string get_slave_path();
|
||||
|
||||
private:
|
||||
void reset_tcp();
|
||||
|
||||
everest::lib::io::serial::event_pty m_pty;
|
||||
everest::lib::io::tcp::tcp_client m_tcp;
|
||||
utilities::symlink m_symlink;
|
||||
int m_tcp_last_error_id = -1;
|
||||
};
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace charge_bridge::filesystem_utils {
|
||||
|
||||
bool read_from_file_partial(const fs::path& file_path, const std::size_t byte_count, std::string& out_data);
|
||||
|
||||
bool read_from_file(const fs::path& file_path, std::string& out_data);
|
||||
|
||||
/// @brief Process the file in chunks with the provided function. If the process function
|
||||
/// returns false, this function will also immediately return
|
||||
/// @return True if the file was properly opened false otherwise
|
||||
bool process_file(const fs::path& file_path, std::size_t buffer_size,
|
||||
std::function<bool(const std::vector<std::uint8_t>&, bool last_chunk)>&& func);
|
||||
|
||||
bool process_file(std::ifstream& file, std::size_t buffer_size,
|
||||
std::function<bool(const std::vector<std::uint8_t>&, bool last_chunk)>&& func);
|
||||
|
||||
struct CryptSignedHeader {
|
||||
std::string firmware_version; // max 32 bytes long string describing the fw version
|
||||
std::uint8_t sig_len = 0;
|
||||
std::vector<std::uint8_t> signature; // length = sig_len
|
||||
std::uint8_t num_sectors = 0; // global one-byte value
|
||||
std::array<std::uint8_t, 16> iv{}; // 16-byte IV from file #2
|
||||
};
|
||||
|
||||
bool read_crypt_signed_header(const fs::path& path, CryptSignedHeader& hdr, std::uint32_t& image_offset);
|
||||
|
||||
} // namespace charge_bridge::filesystem_utils
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
std::ostream& print_error(std::string const& device, std::string const& unit, int status);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <charge_bridge/charge_bridge.hpp>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
std::vector<charge_bridge_config> parse_config_multi(std::string const& config_file);
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
// Converts a struct to raw bytes
|
||||
template <typename T> static inline void struct_to_vector(const T& data_struct, std::vector<std::uint8_t>& buffer) {
|
||||
static constexpr auto struct_size = sizeof(T);
|
||||
|
||||
buffer.resize(struct_size);
|
||||
std::memcpy(buffer.data(), &data_struct, struct_size);
|
||||
}
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <protocol/cb_config.h>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
std::string to_string(CbCanBaudrate value);
|
||||
std::string to_string(CbUartBaudrate value);
|
||||
std::string to_string(CbUartParity value);
|
||||
std::string to_string(CbUartStopbits value);
|
||||
std::string to_string(CbUartConfig const& value);
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
bool string_starts_with(std::string_view const& str, std::string_view const& pattern);
|
||||
bool string_ends_with(std::string const& str, std::string const& pattern);
|
||||
|
||||
std::string string_after_pattern(std::string_view const& str, std::string_view const& pattern);
|
||||
std::string& replace_all_in_place(std::string& source, std::string const& placeholder, std::string const& substitute);
|
||||
std::string replace_all(std::string const& source, std::string const& placeholder, std::string const& substitute);
|
||||
|
||||
std::set<std::string> csv_to_set(std::string const& str);
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
class symlink {
|
||||
public:
|
||||
symlink(std::string const& src, std::string const& tar);
|
||||
symlink();
|
||||
bool set_link(std::string const& src, std::string const& tar);
|
||||
bool del_link();
|
||||
~symlink();
|
||||
|
||||
private:
|
||||
std::string m_tar;
|
||||
};
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include "everest/io/udp/udp_payload.hpp"
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/udp/udp_socket.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
class sync_udp_client {
|
||||
public:
|
||||
using udp_payload = everest::lib::io::udp::udp_payload;
|
||||
using reply = std::optional<udp_payload>;
|
||||
sync_udp_client(std::string const& remote, std::uint16_t port);
|
||||
sync_udp_client(std::string const& remote, std::uint16_t port, std::uint16_t retries, std::uint16_t timeout_ms);
|
||||
reply request_reply(udp_payload const& payload);
|
||||
reply request_reply(udp_payload const& payload, std::uint16_t timeout_ms, std::uint16_t retries);
|
||||
bool tx(udp_payload const& payload);
|
||||
reply rx();
|
||||
reply rx(std::uint16_t timeout_ms);
|
||||
bool is_open();
|
||||
|
||||
private:
|
||||
void init(std::string const& remote, std::uint16_t port);
|
||||
void clear_socket();
|
||||
|
||||
std::uint16_t m_retries;
|
||||
std::uint16_t m_timeout_ms;
|
||||
everest::lib::io::udp::udp_client_socket m_udp;
|
||||
everest::lib::io::event::fd_event_handler m_handler;
|
||||
};
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
#include <everest_api_types/evse_board_support/API.hpp>
|
||||
#include <protocol/cb_config.h>
|
||||
#include <protocol/cb_management.h>
|
||||
#include <ryml.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
class yml_node_error {
|
||||
public:
|
||||
yml_node_error(c4::yml::ConstNodeRef node);
|
||||
yml_node_error(c4::yml::ConstNodeRef node, std::string const& msg);
|
||||
|
||||
c4::yml::ConstNodeRef m_node;
|
||||
std::string m_msg;
|
||||
};
|
||||
|
||||
namespace EXT_API = everest::lib::API;
|
||||
namespace EXT_API_BSP = EXT_API::V1_0::types::evse_board_support;
|
||||
|
||||
bool decode_CbGpioMode(c4::yml::ConstNodeRef const& node, CbGpioMode& rhs);
|
||||
bool decode_CbGpioPulls(c4::yml::ConstNodeRef const& node, CbGpioPulls& rhs);
|
||||
bool decode_CbUartBaudrate(c4::yml::ConstNodeRef const& node, CbUartBaudrate& rhs);
|
||||
bool decode_CbUartStopbits(c4::yml::ConstNodeRef const& node, CbUartStopbits& rhs);
|
||||
bool decode_CbUartParity(c4::yml::ConstNodeRef const& node, CbUartParity& rhs);
|
||||
bool decode_CbCanBaudrate(c4::yml::ConstNodeRef const& node, CbCanBaudrate& rhs);
|
||||
bool decode_CbRelayMode(c4::yml::ConstNodeRef const& node, CbRelayMode& rhs);
|
||||
bool decode_CbSafetyMode(c4::yml::ConstNodeRef const& node, CbSafetyMode& rhs);
|
||||
bool decode_RelayConfig(c4::yml::ConstNodeRef const& node, RelayConfig& rhs);
|
||||
bool decode_SafetyConfig(c4::yml::ConstNodeRef const& node, SafetyConfig& rhs);
|
||||
bool decode_CbGpioConfig(c4::yml::ConstNodeRef const& node, CbGpioConfig& rhs);
|
||||
bool decode_CbUartConfig(c4::yml::ConstNodeRef const& node, CbUartConfig& rhs);
|
||||
bool decode_CbCanConfig(c4::yml::ConstNodeRef const& node, CbCanConfig& rhs);
|
||||
bool decode_CbNetworkConfig(c4::yml::ConstNodeRef const& node, CbNetworkConfig& rhs);
|
||||
bool decode_Connector_type(c4::yml::ConstNodeRef const& node, EXT_API_BSP::Connector_type& rhs);
|
||||
bool decode_HardwareCapabilities(c4::yml::ConstNodeRef const& node, EXT_API_BSP::HardwareCapabilities& rhs);
|
||||
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbGpioMode& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbGpioPulls& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartBaudrate& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartStopbits& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartParity& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbCanBaudrate& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbRelayMode& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbSafetyMode& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, RelayConfig& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, SafetyConfig& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbGpioConfig& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbUartConfig& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbCanConfig& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, CbNetworkConfig& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, EXT_API_BSP::Connector_type& rhs);
|
||||
c4::yml::ConstNodeRef const& operator>>(c4::yml::ConstNodeRef const& node, EXT_API_BSP::HardwareCapabilities& rhs);
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
120
tools/EVerest-main/applications/pionix_chargebridge/main.cpp
Normal file
120
tools/EVerest-main/applications/pionix_chargebridge/main.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include "charge_bridge/charge_bridge.hpp"
|
||||
#include "charge_bridge/utilities/string.hpp"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <charge_bridge/utilities/parse_config.hpp>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/event/timer_fd.hpp>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
|
||||
using namespace everest::lib::io::event;
|
||||
using namespace everest::lib::API::V1_0::types;
|
||||
using namespace charge_bridge;
|
||||
|
||||
enum class mode {
|
||||
error,
|
||||
connector,
|
||||
update,
|
||||
update_only,
|
||||
};
|
||||
|
||||
mode parse_args(int argc, char* argv[], std::vector<std::string>& config_files) {
|
||||
// clang-format off
|
||||
auto print_msg = []() {
|
||||
std::cout << "\nUSAGE: \n";
|
||||
std::cout << "pionix_chargebridge [--update][--update_only] {config_file [config_file_2 ....]} \n";
|
||||
std::cout << "\n";
|
||||
std::cout << "--update use this flag to execute an update at start and continue operation after\n";
|
||||
std::cout << "--update_only use this flag to execute an update and stop the application after\n";
|
||||
std::cout << "config_file use this configuration file\n";
|
||||
std::cout << "config_file_x add more configuration files for each additional ChargeBridge group\n";
|
||||
std::cout << "\n";
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
auto mode = mode::connector;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string current_arg = argv[i];
|
||||
if (current_arg == "--update_only") {
|
||||
mode = mode::update_only;
|
||||
} else if (current_arg == "--update") {
|
||||
mode = mode::update;
|
||||
} else if (utilities::string_starts_with(current_arg, "--")) {
|
||||
mode = mode::error;
|
||||
break;
|
||||
} else {
|
||||
config_files.push_back(current_arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (config_files.size() == 0) {
|
||||
mode = mode::error;
|
||||
}
|
||||
|
||||
if (mode == mode::error) {
|
||||
print_msg();
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
std::atomic<bool> g_run_application(true);
|
||||
void signal_handler(int signum) {
|
||||
std::cout << "\nSignal " << signum << " received. Initiating graceful shutdown." << std::endl;
|
||||
g_run_application = false;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << "PIONIX ChargeBridge (C) 2025-2026\n" << std::endl;
|
||||
|
||||
std::signal(SIGINT, signal_handler);
|
||||
std::signal(SIGHUP, signal_handler);
|
||||
std::signal(SIGTERM, signal_handler);
|
||||
|
||||
std::vector<std::string> config_files;
|
||||
std::vector<charge_bridge_config> cb_configs;
|
||||
std::vector<std::unique_ptr<::charge_bridge::charge_bridge>> cb_handler;
|
||||
|
||||
auto mode_of_operation = parse_args(argc, argv, config_files);
|
||||
if (mode_of_operation == mode::error) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
fd_event_handler ev_handler;
|
||||
|
||||
std::set<std::string> cb_ids_in_use;
|
||||
|
||||
for (auto const& elem : config_files) {
|
||||
auto config_list = utilities::parse_config_multi(elem);
|
||||
if (config_list.empty()) {
|
||||
g_run_application.store(false);
|
||||
break;
|
||||
}
|
||||
for (auto const& config : config_list) {
|
||||
print_charge_bridge_config(config);
|
||||
if (cb_ids_in_use.count(config.cb_name) > 0) {
|
||||
std::cerr << "Duplicate charge_bridge::name '" << config.cb_name << "'" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
cb_ids_in_use.insert(config.cb_name);
|
||||
cb_handler.push_back(std::make_unique<::charge_bridge::charge_bridge>(config));
|
||||
auto& cb = *cb_handler.rbegin();
|
||||
|
||||
if (mode_of_operation == mode::update_only) {
|
||||
cb->update_firmware(true);
|
||||
}
|
||||
|
||||
auto force_update = mode_of_operation == mode::update;
|
||||
cb->manage(ev_handler, g_run_application, force_update);
|
||||
}
|
||||
}
|
||||
|
||||
ev_handler.run(g_run_application);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Chargebridge Protocol
|
||||
|
||||
Contains the header definitions for the raw C structs that will be used for comms between the Linux system and the
|
||||
development board. Will also include various assertions and size/bounds checks to determine the that the client
|
||||
systems are compatible and have the same memory layout.
|
||||
|
||||
The headers are C compliant for both C and C++ user code.
|
||||
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cb_platform.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* Enum definitions */
|
||||
typedef enum _CanErrorState {
|
||||
CanErrorState_Error_Active = 0,
|
||||
CanErrorState_Error_Passive = 1,
|
||||
CanErrorState_ForceSize = 0xFFFFFFFF,
|
||||
} CanErrorState;
|
||||
|
||||
typedef enum _CanBitrate {
|
||||
CanBitrate_125kbps = 0,
|
||||
CanBitrate_250kbps = 1,
|
||||
CanBitrate_500kbps = 2,
|
||||
CanBitrate_1000kbps = 3,
|
||||
CanBitrate_ForceSize = 0xFFFFFFFF,
|
||||
} CanBitrate;
|
||||
|
||||
typedef enum _CanFDBitrate {
|
||||
CanFDBitrate_1MBps = 0,
|
||||
CanFDBitrate_2MBps = 1,
|
||||
CanFDBitrate_3MBps = 2,
|
||||
CanFDBitrate_4MBps = 3,
|
||||
CanFDBitrate_5MBps = 4,
|
||||
CanFDBitrate_6MBps = 5,
|
||||
CanFDBitrate_7MBps = 6,
|
||||
CanFDBitrate_8MBps = 7,
|
||||
CanFDBitrate_ForceSize = 0xFFFFFFFF,
|
||||
} CanFDBitrate;
|
||||
|
||||
typedef enum _CanFlags {
|
||||
CanFlags_EFF = 1,
|
||||
CanFlags_RTR = 1 << 1,
|
||||
CanFlags_ERR = 1 << 2,
|
||||
} CanFlags;
|
||||
|
||||
typedef struct CB_COMPILER_ATTR_PACK _CanStatistics {
|
||||
// tx: direction is from host to bus
|
||||
// rx: direction is from bus to host
|
||||
uint32_t frames_tx;
|
||||
uint32_t frames_rx;
|
||||
uint32_t event_rx_buf_full;
|
||||
uint32_t event_tx_buf_full;
|
||||
} CanStatistics;
|
||||
|
||||
typedef enum _CanPacketType : uint8_t{
|
||||
CanPacketType_Regular = 0,
|
||||
CanPacketType_Keep_Alive = 1,
|
||||
} CanPacketType;
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK cb_can_message {
|
||||
uint8_t version;
|
||||
CanPacketType packet_type; // 0: regular CAN packet, 1: dummy keep-alive packet
|
||||
CanErrorState error_state;
|
||||
CanStatistics statistics;
|
||||
CanBitrate bitrate;
|
||||
CanFDBitrate fd_bitrate; /* integer in MBit (1-8) */
|
||||
uint8_t can_flags; // EFF, RTR, ERR
|
||||
uint32_t can_id;
|
||||
|
||||
/* dlc 0..8: standard CAN frame with up to 8 bytes
|
||||
* FDCAN dlc:
|
||||
* 9: 12 bytes
|
||||
* 10: 16 bytes
|
||||
* 11: 20 bytes
|
||||
* 12: 24 bytes
|
||||
* 13: 32 bytes
|
||||
* 14: 48 bytes
|
||||
* 15: 64 bytes
|
||||
*/
|
||||
uint8_t dlc;
|
||||
|
||||
// Note: in UDP transmission, data bytes at the end may be omitted in the message.
|
||||
// Always check dlc first before accessing the data
|
||||
uint8_t data[64];
|
||||
};
|
||||
|
||||
#define cb_can_message_set_zero \
|
||||
{0, CanPacketType_Regular, CanErrorState_Error_Active, {0, 0, 0, 0}, CanBitrate_125kbps, CanFDBitrate_1MBps, 0, 0, \
|
||||
0, {0, 0, 0, 0, 0, 0, 0, 0}};
|
||||
|
||||
#include "test/cb_can_message_test.h"
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cb_platform.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// Structs
|
||||
|
||||
typedef union _SafetyErrorFlags {
|
||||
struct _flags {
|
||||
uint32_t cp_not_state_c : 1;
|
||||
uint32_t pwm_not_enabled : 1;
|
||||
uint32_t pp_invalid : 1;
|
||||
uint32_t plug_temperature_too_high : 1;
|
||||
uint32_t internal_temperature_too_high : 1;
|
||||
uint32_t emergency_input_latched : 1;
|
||||
uint32_t relay_health_latched : 1;
|
||||
uint32_t vdd_3v3_out_of_range : 1;
|
||||
uint32_t vdd_core_out_of_range : 1;
|
||||
uint32_t vdd_12V_out_of_range : 1;
|
||||
uint32_t vdd_N12V_out_of_range : 1;
|
||||
uint32_t vdd_refint_out_of_range : 1;
|
||||
uint32_t external_allow_power_on : 1;
|
||||
uint32_t config_mem_error : 1;
|
||||
uint32_t dc_hv_ov_emergency : 1;
|
||||
uint32_t dc_hv_ov_error : 1;
|
||||
uint32_t reserved : 17;
|
||||
} flags;
|
||||
uint32_t raw;
|
||||
} SafetyErrorFlags;
|
||||
|
||||
|
||||
typedef enum _CpState : uint8_t {
|
||||
CpState_A,
|
||||
CpState_B,
|
||||
CpState_C,
|
||||
CpState_D,
|
||||
CpState_E,
|
||||
CpState_F,
|
||||
CpState_DF,
|
||||
CpState_INVALID
|
||||
} CpState;
|
||||
@@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cb_platform.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define CB_NUMBER_OF_GPIOS 10
|
||||
#define CB_NUMBER_OF_UARTS 3
|
||||
|
||||
// enums
|
||||
|
||||
typedef enum _CbGpioMode : uint8_t {
|
||||
CBG_Input = 0x00,
|
||||
CBG_Output = 0x01,
|
||||
CBG_Pwm_Input = 0x02,
|
||||
CBG_Pwm_Output = 0x03,
|
||||
CBG_RS485_2_DE = 0x04,
|
||||
CBG_Rcd_Selftest_Output = 0x05,
|
||||
CBG_Rcd_Error_Input= 0x06,
|
||||
CBG_Rcd_PWM_Input= 0x07,
|
||||
CBG_MotorLock_1 = 0x08,
|
||||
CBG_MotorLock_2 = 0x09,
|
||||
} CbGpioMode;
|
||||
|
||||
typedef enum _CbRelayMode : uint8_t {
|
||||
CBR_PowerRelay = 0x00, CBR_UserRelay = 0x01,
|
||||
} CbRelayMode;
|
||||
|
||||
typedef enum _CbGpioPulls : uint8_t {
|
||||
CBGP_NoPull = 0x00, CBGP_PullUp = 0x01, CBGP_PullDown = 0x02,
|
||||
} CbGpioPulls;
|
||||
|
||||
typedef enum _CbUartBaudrate : uint8_t {
|
||||
CBUBR_9600 = 0x00,
|
||||
CBUBR_19200 = 0x01,
|
||||
CBUBR_38400 = 0x02,
|
||||
CBUBR_57600 = 0x03,
|
||||
CBUBR_115200 = 0x04,
|
||||
CBUBR_230400 = 0x05,
|
||||
CBUBR_250000 = 0x06,
|
||||
CBUBR_460800 = 0x07,
|
||||
CBUBR_500000 = 0x08,
|
||||
CBUBR_1000000 = 0x09,
|
||||
CBUBR_2000000 = 0x0A,
|
||||
CBUBR_3000000 = 0x0B,
|
||||
CBUBR_4000000 = 0x0C,
|
||||
CBUBR_6000000 = 0x0D,
|
||||
CBUBR_8000000 = 0x0E,
|
||||
CBUBR_10000000 = 0x0F,
|
||||
} CbUartBaudrate;
|
||||
|
||||
typedef enum _CbUartStopbits : uint8_t {
|
||||
CBUS_OneStopBit = 0x00, CBUS_TwoStopBits = 0x01,
|
||||
} CbUartStopbits;
|
||||
|
||||
typedef enum _CbUartParity : uint8_t {
|
||||
CBUP_None = 0x00, CBUP_Odd = 0x01, CBUP_Even = 0x02,
|
||||
} CbUartParity;
|
||||
|
||||
typedef enum _CbCanBaudrate : uint8_t {
|
||||
CBCBR_125000 = 0x00,
|
||||
CBCBR_250000 = 0x01,
|
||||
CBCBR_500000 = 0x02,
|
||||
CBCBR_1000000 = 0x03,
|
||||
} CbCanBaudrate;
|
||||
|
||||
typedef enum _CbSafetyMode : uint8_t {
|
||||
CBSM_disabled = 0x00, CBSM_US = 0x01, CBSM_EU = 0x02,
|
||||
|
||||
} CbSafetyMode;
|
||||
|
||||
// Structs
|
||||
|
||||
typedef struct CB_COMPILER_ATTR_PACK _relay_config {
|
||||
CbRelayMode relay_mode;
|
||||
uint8_t feedback_enabled; // 0: feedback unused, 1: feedback expected
|
||||
uint16_t feedback_delay_ms; // After switching, wait for this delay before evaluating feedback pin
|
||||
uint8_t feedback_inverted; // 0: feedback normal (mirror contact, high when relay is off), 1: inverted
|
||||
uint8_t pwm_dc; // 100: Do not use PWM. 1-99: Set PWM Duty cycle after delay
|
||||
uint16_t pwm_delay_ms; // Delay in ms after which the PWM starts
|
||||
uint16_t switchoff_delay_ms; // Delay before switching relay off. Can be used to set a small delay between EMG_OUT
|
||||
// and relays off [SR-SL-2]
|
||||
} RelayConfig;
|
||||
|
||||
typedef struct CB_COMPILER_ATTR_PACK _safety_config {
|
||||
CbSafetyMode pp_mode; // set to 0: disabled 1: US type 1, 2: EU type 2
|
||||
uint8_t cp_avg_ms; // default is 10ms / pulses
|
||||
RelayConfig relays[3]; // Config for the 3 relay I/Os
|
||||
uint8_t inverted_emergency_input; // 0: normal operation, 1: emergency input is inverted
|
||||
uint8_t temperature_limit_pt1000_C; // Temperature limit for the PT1000 inputs. Relays will switch off if temperature is above the limit. Setting this to 0 will disable the feature.
|
||||
} SafetyConfig;
|
||||
|
||||
typedef struct CB_COMPILER_ATTR_PACK _CbGpioConfig {
|
||||
CbGpioMode mode;
|
||||
CbGpioPulls pulls;
|
||||
uint8_t strap_option_mdns_naming; // sample as bit for mdns id;
|
||||
uint16_t mode_config; // Config value for the mode, e.g. frequency of PWM
|
||||
} CbGpioConfig;
|
||||
|
||||
typedef struct CB_COMPILER_ATTR_PACK _CbUartConfig {
|
||||
CbUartBaudrate baudrate;
|
||||
CbUartStopbits stopbits;
|
||||
CbUartParity parity;
|
||||
} CbUartConfig;
|
||||
|
||||
typedef struct CB_COMPILER_ATTR_PACK _CbCanConfig {
|
||||
CbCanBaudrate baudrate;
|
||||
} CbCanConfig;
|
||||
|
||||
typedef struct CB_COMPILER_ATTR_PACK _CbNetworkConfig {
|
||||
char mdns_name[20]; // custom MDNS name
|
||||
} CbNetworkConfig;
|
||||
|
||||
// Final complete config struct
|
||||
|
||||
#define CB_CONFIG_VERSION 0
|
||||
typedef struct CB_COMPILER_ATTR_PACK _cb_config {
|
||||
uint32_t config_version;
|
||||
SafetyConfig safety;
|
||||
CbGpioConfig gpios[CB_NUMBER_OF_GPIOS];
|
||||
CbUartConfig uarts[CB_NUMBER_OF_UARTS];
|
||||
CbCanConfig can;
|
||||
CbNetworkConfig network;
|
||||
uint8_t plc_powersaving_mode;
|
||||
} CbConfig;
|
||||
@@ -0,0 +1,151 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/**
|
||||
* \file Common structs used between linux and the STM32 dev board. Data
|
||||
* will be sent raw over the UDP socket, with the sender using
|
||||
* \ref send(struct, sizeof(CbStruct))
|
||||
* and the receiver using
|
||||
* \ref receive(raw_bytes, sizeof(CbStruct))
|
||||
* CbStruct *struct = reinterpret_cast<CbStruct *>(raw_bytes);
|
||||
*
|
||||
* Notes:
|
||||
* 1) After V1 structs will not be able to remove fields, only add fields
|
||||
* after the existing fields
|
||||
* 2) There can be problems with variable length structs, for example
|
||||
* CbFirmwarePacket that can have a payload with lengths 0-1024
|
||||
*
|
||||
* Test files are added at the end that check the sizes of the structs
|
||||
* at compile time, to determine the fact that they are consistent
|
||||
* across platforms.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cb_config.h"
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error "This header is C++ only"
|
||||
#endif
|
||||
|
||||
enum class AppUDPResponse : uint32_t {
|
||||
AUR_Ok = 0x500D500D, AUR_Bad = 0xBADBAD00,
|
||||
};
|
||||
|
||||
enum class CbType : uint8_t {
|
||||
CCS_EVSE = 0, CCS_EV = 1,
|
||||
};
|
||||
|
||||
/*
|
||||
* What type of message is on the socket
|
||||
*/
|
||||
enum class CbStructType : uint16_t {
|
||||
|
||||
// track IP with timeout and port
|
||||
// Housekeepig, heartbeat/config (safety_config, serial port(fixed)/CAN bitrate, gpio config, mdns module name), fw version, softreset,
|
||||
CST_HostToCb_Heartbeat = 1,
|
||||
CST_CbToHost_Heartbeat = 2,
|
||||
|
||||
// track IP with timeout and port
|
||||
// GPIO client
|
||||
CST_HostToCb_Gpio = 3,
|
||||
CST_CbToHost_Gpio = 4,
|
||||
|
||||
// FW update
|
||||
CST_CbFirmwareReply = 0xFFF9,
|
||||
CST_CbFirmwareStart = 0xFFFA,
|
||||
CST_CbFirmwarePacket = 0xFFFB,
|
||||
CST_CbFirmwareFinish = 0xFFFC,
|
||||
CST_CbFirmwareUpdateCancel = 0xFFFD,
|
||||
CST_CbFirmwarePing = 0xFFFE,
|
||||
CST_CbFirmwareGetVersion = 0xFFFF,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
This container message is used for all generic module management packets
|
||||
*/
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct CB_COMPILER_ATTR_PACK CbManagementPacket {
|
||||
CbStructType type;
|
||||
T data;
|
||||
};
|
||||
|
||||
template<> struct CB_COMPILER_ATTR_PACK CbManagementPacket<void> {
|
||||
CbStructType type;
|
||||
};
|
||||
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbGpioPacket {
|
||||
uint8_t number_of_gpios; // Just to check compatibility
|
||||
uint16_t gpio_values[CB_NUMBER_OF_GPIOS]; // Actual value, 0: low, 1: high, or duty cycle for PWM
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbHeartbeatPacket {
|
||||
CbConfig module_config;
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbHeartbeatReplyPacket {
|
||||
int32_t cp_hi_mV;
|
||||
int32_t cp_lo_mV;
|
||||
int32_t vdd_core;
|
||||
int32_t vdd_3v3;
|
||||
int32_t vdd_refint;
|
||||
int32_t vdd_12V;
|
||||
int32_t vdd_N12V;
|
||||
int32_t pp_mOhm;
|
||||
int32_t pp_voltage_mV;
|
||||
uint8_t relay_state_feedback[3];
|
||||
int16_t temperature_mcu_C;
|
||||
int16_t temperature_pcb_C;
|
||||
int16_t temperature_modem_C;
|
||||
int16_t temperature_PT1000_C[2];
|
||||
int32_t uptime_ms;
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbFirmwareStart {
|
||||
uint8_t is_secure_fw :1;
|
||||
uint8_t requires_crc_verification :1;
|
||||
uint8_t requires_sha256_verification :1;
|
||||
uint8_t requires_signature_verification :1;
|
||||
uint8_t requires_decryption :1;
|
||||
|
||||
// IV in case we require decryption
|
||||
uint8_t iv[16];
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbFirmwarePacket {
|
||||
// If it is the last packet sent, used when we need to take care of
|
||||
// the padding bytes in case of decryption or other operations
|
||||
uint8_t last_packet :1;
|
||||
|
||||
uint16_t sector;
|
||||
uint16_t data_len;
|
||||
uint8_t data[1024];
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbFirmwareEnd {
|
||||
uint32_t firmware_len;
|
||||
|
||||
// Signature for the firmware in the faw format
|
||||
uint8_t fw_signature[128];
|
||||
uint8_t fw_signature_len;
|
||||
uint8_t watermark_secure_end;
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbFirmwareUpdateCancel {
|
||||
uint8_t dummy;
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbFirmwarePing {
|
||||
uint8_t dummy;
|
||||
};
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK CbFirmwareGetVersion {
|
||||
uint8_t dummy;
|
||||
};
|
||||
|
||||
#include "test/cb_management_test.h"
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/**
|
||||
* \file compiler utilities and Pionix defines for
|
||||
* cross-compiling
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if defined(__cplusplus)
|
||||
#define CB_STATIC_ASSERT(cond, msg) static_assert(cond, msg)
|
||||
#else
|
||||
#define CB_STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
|
||||
#endif
|
||||
|
||||
#define CB_COMPILER_ATTR_PACK __attribute__((packed))
|
||||
|
||||
// Should be < MTU (defined as #define NX_DRIVER_ETHERNET_MTU 1514)
|
||||
#define CB_MAX_UDP_PACKET_SIZE (1024 + 256)
|
||||
// -128 since we might want some non-struct metadata
|
||||
#define CB_MAX_CB_STRUCT_SIZE (CB_MAX_UDP_PACKET_SIZE - 128)
|
||||
|
||||
#define CB_MAX_STRING_SIZE 64
|
||||
|
||||
#define cb_string(name) int8_t name[CB_MAX_STRING_SIZE]
|
||||
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef EVSE_BSP_CB_TO_HOST_H
|
||||
#define EVSE_BSP_CB_TO_HOST_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "cb_platform.h"
|
||||
#include "cb_common.h"
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK evse_bsp_cb_to_host {
|
||||
// add version number as first uint16 ????
|
||||
// potentially unused, ignore for now
|
||||
uint8_t reset_reason;
|
||||
CpState cp_state;
|
||||
uint8_t relay_state;
|
||||
SafetyErrorFlags error_flags;
|
||||
uint8_t pp_state_type1;
|
||||
uint8_t pp_state_type2;
|
||||
uint8_t lock_state;
|
||||
uint32_t hv_mV;
|
||||
// still define handling set for
|
||||
uint8_t stop_charging;
|
||||
uint16_t cp_duty_cycle;
|
||||
};
|
||||
|
||||
/* Enum definitions */
|
||||
typedef enum _RelayState {
|
||||
RelayState_Open = 0,
|
||||
RelayState_Closed = 1
|
||||
} RelaiseState;
|
||||
|
||||
typedef enum _ResetReason {
|
||||
ResetReason_USER = 0,
|
||||
ResetReason_WATCHDOG = 1
|
||||
} ResetReason;
|
||||
|
||||
typedef enum _PpState_Type2 {
|
||||
PpState_Type2_STATE_NC = 0,
|
||||
PpState_Type2_STATE_13A = 1,
|
||||
PpState_Type2_STATE_20A = 2,
|
||||
PpState_Type2_STATE_32A = 3,
|
||||
PpState_Type2_STATE_70A = 4,
|
||||
PpState_Type2_STATE_FAULT = 5
|
||||
} PpState_Type2;
|
||||
|
||||
typedef enum _PpState_Type1 {
|
||||
PpState_Type1_STATE_NotConnected,
|
||||
PpState_Type1_STATE_Connected_Button_Pressed,
|
||||
PpState_Type1_STATE_Connected,
|
||||
PpState_Type1_STATE_Invalid
|
||||
} PpState_Type1;
|
||||
|
||||
typedef enum _LockState {
|
||||
LockState_UNDEFINED = 0,
|
||||
LockState_UNLOCKED = 1,
|
||||
LockState_LOCKED = 2
|
||||
} LockState;
|
||||
|
||||
#include "test/evse_bsp_cb_to_host_test.h"
|
||||
|
||||
#endif // EVSE_BSP_CB_TO_HOST_H
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef EVSE_BSP_HOST_TO_CB_H
|
||||
#define EVSE_BSP_HOST_TO_CB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "cb_platform.h"
|
||||
|
||||
struct CB_COMPILER_ATTR_PACK evse_bsp_host_to_cb {
|
||||
uint8_t connector_lock; /* 0: unlock, otherwise: lock */
|
||||
uint32_t pwm_duty_cycle; /* in 0.01 %, 0 = State F, 10000 = X1 */
|
||||
uint8_t allow_power_on; /* 0 false, true otherwise */
|
||||
uint8_t reset; /* 0 false, true otherwise */
|
||||
uint8_t ovm_enable; /* 0 disabled, 1: enabled */
|
||||
uint8_t ovm_reset_errors; /* 0 leave errors untouched, 1: clear error bits for OVM */
|
||||
uint32_t ovm_limit_emergency_mV; /* 9ms limit in mV */
|
||||
uint32_t ovm_limit_error_mV; /* 400ms limit in mV */
|
||||
CpState ev_set_cp_state; /* Set CP state (EV side only) */
|
||||
uint8_t ev_set_diodefault; /* Set/Clear DF state (EV side only) */
|
||||
};
|
||||
|
||||
#include "test/evse_bsp_host_to_cb_test.h"
|
||||
|
||||
#endif // EVSE_BSP_H
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "protocol/cb_can_message.h"
|
||||
#include "protocol/cb_platform.h"
|
||||
CB_STATIC_ASSERT(sizeof(CanErrorState) == 4, "CanErrorState data size!!");
|
||||
CB_STATIC_ASSERT(sizeof(CanBitrate) == 4, "CanBitrate data size!!");
|
||||
CB_STATIC_ASSERT(sizeof(CanFDBitrate) == 4, "CanFDBitrate data size!!");
|
||||
CB_STATIC_ASSERT(sizeof(CanStatistics) == 4+4+4+4, "CanStatistics data size!!");
|
||||
CB_STATIC_ASSERT(sizeof(struct cb_can_message) == 1+4+16+4+4+1+4+1+64+1, "cb_can_message type size!!");
|
||||
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/**
|
||||
* \file Test utilities used to determine that the used types
|
||||
* have the same sizes independent of the platforms that we
|
||||
* are using. Added to make sure that reinterpret_cast or other
|
||||
* types of cast will yield the same types across platform
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
CB_STATIC_ASSERT(sizeof(AppUDPResponse) == 4, "Wrong AppUDPReponse type size!");
|
||||
CB_STATIC_ASSERT(sizeof(CbType) == 1, "Wrong CB type size!");
|
||||
CB_STATIC_ASSERT(sizeof(CbStructType) == 2, "Wrong CB type size!");
|
||||
CB_STATIC_ASSERT((sizeof(CbFirmwareStart) == 16 + 1 && sizeof(CbFirmwareStart) <= CB_MAX_CB_STRUCT_SIZE),
|
||||
"Wrong CB type size!");
|
||||
CB_STATIC_ASSERT((sizeof(CbFirmwarePacket) == 1 + 2 + 2 + 1024 && sizeof(CbFirmwarePacket) <= CB_MAX_CB_STRUCT_SIZE),
|
||||
"Wrong CB type size!");
|
||||
CB_STATIC_ASSERT((sizeof(CbFirmwareEnd) == 4 + 1 + (128 + 1) && sizeof(CbFirmwareEnd) <= CB_MAX_CB_STRUCT_SIZE),
|
||||
"Wrong CB type size!");
|
||||
CB_STATIC_ASSERT((sizeof(CbHeartbeatPacket) == 119 && sizeof(CbHeartbeatPacket) <= CB_MAX_CB_STRUCT_SIZE),
|
||||
"Wrong CB type size!");
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
CB_STATIC_ASSERT(sizeof(struct evse_bsp_cb_to_host)== 11+4+4+2, "Wrong evse_bsp_cb_to_host size!!!");
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#pragma once
|
||||
|
||||
CB_STATIC_ASSERT (sizeof(struct evse_bsp_host_to_cb) == 7+9+1+1+1, "Wrong evse_bsp_host_to_cb size!");
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include <charge_bridge/bsp_bridge.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <cstring>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
#include <iostream>
|
||||
#include <protocol/evse_bsp_cb_to_host.h>
|
||||
#include <protocol/evse_bsp_host_to_cb.h>
|
||||
|
||||
namespace {
|
||||
const int default_udp_timeout_ms = 1000;
|
||||
}
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
bsp_bridge::bsp_bridge(bsp_bridge_config const& config) :
|
||||
m_api(config.api, config.cb + "/" + config.item), m_udp(config.cb_remote, config.cb_port, default_udp_timeout_ms) {
|
||||
using namespace std::chrono_literals;
|
||||
m_timer.set_timeout(5s);
|
||||
|
||||
m_api.set_cb_tx([this](auto& data) {
|
||||
everest::lib::io::udp::udp_payload pl;
|
||||
pl.set_message(&data, sizeof(data));
|
||||
m_udp.tx(pl);
|
||||
});
|
||||
|
||||
m_udp.set_rx_handler([this](auto const& data, auto&) {
|
||||
evse_bsp_cb_to_host msg;
|
||||
std::memcpy(&msg, data.buffer.data(), data.size());
|
||||
m_api.set_cb_message(msg);
|
||||
});
|
||||
|
||||
auto identifier = config.cb + "/" + config.item;
|
||||
m_udp.set_error_handler([this, identifier](auto id, auto const& msg) {
|
||||
utilities::print_error(identifier, "BSP/UDP", id) << msg << std::endl;
|
||||
m_udp_on_error = id not_eq 0;
|
||||
});
|
||||
}
|
||||
|
||||
void bsp_bridge::handle_timer_event() {
|
||||
if (m_udp_on_error) {
|
||||
m_udp.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool bsp_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
result = handler.register_event_handler(&m_api) && result;
|
||||
result = handler.register_event_handler(&m_udp) && result;
|
||||
result = handler.register_event_handler(&m_timer, [this](auto&) { handle_timer_event(); }) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool bsp_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
result = handler.unregister_event_handler(&m_api) && result;
|
||||
result = handler.unregister_event_handler(&m_udp) && result;
|
||||
result = handler.unregister_event_handler(&m_timer) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,162 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <charge_bridge/can_bridge.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/netlink/vcan_netlink_manager.hpp>
|
||||
#include <memory>
|
||||
#include <protocol/cb_can_message.h>
|
||||
|
||||
namespace {
|
||||
const int default_udp_timeout_ms = 1000;
|
||||
}
|
||||
|
||||
namespace charge_bridge {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
|
||||
void msg_cb_to_host(cb_can_message const& src, everest::lib::io::can::socket_can::ClientPayloadT& tar) {
|
||||
tar.set_can_id_with_flags(src.can_id, src.can_flags & CanFlags_EFF, src.can_flags & CanFlags_RTR,
|
||||
src.can_flags & CanFlags_ERR);
|
||||
tar.len8_dlc = 0;
|
||||
tar.payload.resize(src.dlc);
|
||||
std::memcpy(tar.payload.data(), src.data, src.dlc);
|
||||
}
|
||||
|
||||
void msg_host_to_cb(everest::lib::io::can::socket_can::ClientPayloadT const& src, cb_can_message& tar) {
|
||||
tar = cb_can_message_set_zero;
|
||||
tar.can_id = src.get_can_id();
|
||||
tar.can_flags = 0;
|
||||
if (src.eff_flag()) {
|
||||
tar.can_flags |= CanFlags_EFF;
|
||||
}
|
||||
if (src.rtr_flag()) {
|
||||
tar.can_flags |= CanFlags_RTR;
|
||||
}
|
||||
if (src.err_flag()) {
|
||||
tar.can_flags |= CanFlags_ERR;
|
||||
}
|
||||
tar.dlc = std::min<uint8_t>(src.payload.size(), sizeof(tar.data));
|
||||
std::memcpy(tar.data, src.payload.data(), src.payload.size());
|
||||
}
|
||||
|
||||
bool is_data_msg([[maybe_unused]] cb_can_message const& msg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
can_bridge::can_bridge(can_bridge_config const& config) :
|
||||
m_udp(config.cb_remote, config.cb_port, default_udp_timeout_ms),
|
||||
m_can_device(config.can_device),
|
||||
m_last_msg_to_cb(std::chrono::steady_clock::time_point()) {
|
||||
|
||||
auto& manager = everest::lib::io::netlink::vcan_netlink_manager::Instance();
|
||||
auto success = manager.create(config.can_device) && manager.bring_up(config.can_device);
|
||||
if (success) {
|
||||
m_can = std::make_unique<everest::lib::io::can::socket_can>(config.can_device);
|
||||
} else {
|
||||
manager.destroy(config.can_device);
|
||||
success = manager.create(config.can_device) && manager.bring_up(config.can_device);
|
||||
if (success) {
|
||||
m_can = std::make_unique<everest::lib::io::can::socket_can>(config.can_device);
|
||||
} else {
|
||||
manager.destroy(config.can_device);
|
||||
throw std::runtime_error("Failed to setup virtual CAN device: " + config.can_device);
|
||||
}
|
||||
}
|
||||
|
||||
m_can->set_rx_handler([this](auto const& data, auto&) {
|
||||
everest::lib::io::udp::udp_client::ClientPayloadT pl;
|
||||
cb_can_message msg;
|
||||
msg_host_to_cb(data, msg);
|
||||
send_can_to_udp(msg);
|
||||
});
|
||||
|
||||
m_udp.set_rx_handler([this](auto const& data, auto&) {
|
||||
everest::lib::io::can::socket_can::ClientPayloadT pl;
|
||||
cb_can_message msg;
|
||||
std::memcpy(&msg, data.buffer.data(), sizeof(cb_can_message));
|
||||
|
||||
msg_cb_to_host(msg, pl);
|
||||
if (is_data_msg(msg)) {
|
||||
m_can->tx(pl);
|
||||
}
|
||||
});
|
||||
|
||||
m_identifier = config.cb + "/" + config.item;
|
||||
m_can->set_error_handler([this](auto id, auto const& msg) {
|
||||
utilities::print_error(m_identifier, "CAN/HW", id) << msg << std::endl;
|
||||
if (id not_eq 0) {
|
||||
// This is a smart pointer!! Using .reset() would delete the obj!
|
||||
m_can->reset();
|
||||
}
|
||||
});
|
||||
|
||||
m_udp.set_error_handler([this](auto id, auto const& msg) {
|
||||
utilities::print_error(m_identifier, "CAN/UDP", id) << msg << std::endl;
|
||||
if (id not_eq 0) {
|
||||
m_udp.reset();
|
||||
}
|
||||
});
|
||||
|
||||
m_heartbeat_timer.set_timeout(10s);
|
||||
}
|
||||
|
||||
can_bridge::~can_bridge() {
|
||||
auto& manager = everest::lib::io::netlink::vcan_netlink_manager::Instance();
|
||||
if (m_can) {
|
||||
m_can.reset();
|
||||
manager.destroy(m_can_device);
|
||||
}
|
||||
}
|
||||
|
||||
bool can_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = handler.register_event_handler(m_can.get());
|
||||
result = handler.register_event_handler(&m_udp) && result;
|
||||
result = handler.register_event_handler(&m_heartbeat_timer, [this](auto&) { handle_heartbeat_timer(); }) && result;
|
||||
|
||||
if (result) {
|
||||
handler.add_action([this]() { handle_heartbeat_timer(); });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool can_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = handler.unregister_event_handler(m_can.get());
|
||||
result = handler.unregister_event_handler(&m_udp) && result;
|
||||
result = handler.unregister_event_handler(&m_heartbeat_timer) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void can_bridge::send_can_to_udp(cb_can_message const& msg) {
|
||||
everest::lib::io::udp::udp_client::ClientPayloadT udp_pl;
|
||||
udp_pl.buffer.resize(sizeof(cb_can_message));
|
||||
std::memcpy(udp_pl.buffer.data(), &msg, sizeof(cb_can_message));
|
||||
m_udp.tx(udp_pl);
|
||||
m_last_msg_to_cb = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void can_bridge::handle_heartbeat_timer() {
|
||||
if (m_udp.on_error()) {
|
||||
// If the connection is not available, retry soon and invalidate last hearbeat
|
||||
m_heartbeat_timer.set_timeout(250ms);
|
||||
m_last_msg_to_cb = std::chrono::steady_clock::time_point();
|
||||
return;
|
||||
} else {
|
||||
// otherwise go back to regular interval
|
||||
m_heartbeat_timer.set_timeout(10s);
|
||||
}
|
||||
auto delta = std::chrono::steady_clock::now() - m_last_msg_to_cb;
|
||||
if (delta > 10s) {
|
||||
cb_can_message msg = cb_can_message_set_zero;
|
||||
msg.packet_type = CanPacketType_Keep_Alive;
|
||||
send_can_to_udp(msg);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,388 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include "protocol/cb_config.h"
|
||||
#include <charge_bridge/charge_bridge.hpp>
|
||||
#include <charge_bridge/discovery.hpp>
|
||||
#include <charge_bridge/firmware_update/sync_fw_updater.hpp>
|
||||
#include <charge_bridge/gpio_bridge.hpp>
|
||||
#include <charge_bridge/heartbeat_service.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <charge_bridge/utilities/print_config.hpp>
|
||||
#include <charge_bridge/utilities/string.hpp>
|
||||
#include <charge_bridge/utilities/sync_udp_client.hpp>
|
||||
#include <everest/io/event/fd_event_sync_interface.hpp>
|
||||
#include <everest/io/netlink/vcan_netlink_manager.hpp>
|
||||
#include <everest/util/misc/bind.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
namespace {
|
||||
std::pair<bool, std::set<std::string>> make_interface_list(std::string const& str, std::string const& pattern) {
|
||||
if (str == pattern) {
|
||||
return {false, {}};
|
||||
};
|
||||
auto raw = utilities::string_after_pattern(str, pattern).substr(1);
|
||||
if (raw.size() <= 2) {
|
||||
return {false, {}};
|
||||
}
|
||||
auto exclude = raw.substr(0, 1) == "!";
|
||||
auto items = utilities::csv_to_set(raw.substr(exclude ? 1 : 0));
|
||||
for (auto const& elem : items) {
|
||||
std::cout << elem << ", ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
return {exclude, items};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
charge_bridge::charge_bridge(charge_bridge_config const& config) : m_config(config) {
|
||||
if (utilities::string_starts_with(config.cb_remote, "ANY_EVSE")) {
|
||||
auto params = make_interface_list(config.cb_remote, "ANY_EVSE");
|
||||
init_discovery(discovery_device_type::CB_EVSE, params.second, params.first);
|
||||
} else if (utilities::string_starts_with(config.cb_remote, "ANY_EV")) {
|
||||
auto params = make_interface_list(config.cb_remote, "ANY_EV");
|
||||
init_discovery(discovery_device_type::CB_EV, params.second, params.first);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
void charge_bridge::init_discovery(discovery_device_type type, std::set<std::string> const& interfaces,
|
||||
bool excluding) {
|
||||
using namespace everest::lib::util;
|
||||
utilities::print_error(m_config.cb_name, "DISCOVERY", -1) << "Discovery pending" << std::endl;
|
||||
|
||||
m_discovery = std::make_unique<discovery>(type, interfaces, excluding);
|
||||
m_discovery->set_discovery_callback(bind_obj(&charge_bridge::handle_discovery, this));
|
||||
{
|
||||
auto handle = m_cb_status.handle();
|
||||
handle->discovery_pending = true;
|
||||
}
|
||||
m_cb_status.notify_one();
|
||||
}
|
||||
|
||||
void charge_bridge::handle_discovery(std::string const& ip) {
|
||||
utilities::print_error(m_config.cb_name, "DISCOVERY", 0) << "Discovered at: " + ip << std::endl;
|
||||
|
||||
m_config.cb_remote = ip;
|
||||
if (m_config.can0) {
|
||||
m_config.can0->cb_remote = ip;
|
||||
}
|
||||
if (m_config.serial1) {
|
||||
m_config.serial1->cb_remote = ip;
|
||||
}
|
||||
if (m_config.serial2) {
|
||||
m_config.serial2->cb_remote = ip;
|
||||
}
|
||||
if (m_config.serial3) {
|
||||
m_config.serial3->cb_remote = ip;
|
||||
}
|
||||
if (m_config.plc) {
|
||||
m_config.plc->cb_remote = ip;
|
||||
}
|
||||
if (m_config.bsp) {
|
||||
m_config.bsp->cb_remote = ip;
|
||||
}
|
||||
if (m_config.heartbeat) {
|
||||
m_config.heartbeat->cb_remote = ip;
|
||||
}
|
||||
if (m_config.gpio) {
|
||||
m_config.gpio->cb_remote = ip;
|
||||
}
|
||||
m_config.firmware.cb_remote = ip;
|
||||
|
||||
m_event_handler->add_action([this]() {
|
||||
std::unique_ptr<discovery> tmp;
|
||||
std::swap(m_discovery, tmp);
|
||||
|
||||
init();
|
||||
{
|
||||
auto handle = m_cb_status.handle();
|
||||
handle->discovery_pending = false;
|
||||
}
|
||||
m_cb_status.notify_one();
|
||||
});
|
||||
}
|
||||
|
||||
void charge_bridge::init() {
|
||||
if (m_config.can0.has_value()) {
|
||||
m_can_0_client = std::make_unique<can_bridge>(m_config.can0.value());
|
||||
}
|
||||
if (m_config.serial1.has_value()) {
|
||||
m_pty_1 = std::make_unique<serial_bridge>(m_config.serial1.value());
|
||||
}
|
||||
if (m_config.serial2.has_value()) {
|
||||
m_pty_2 = std::make_unique<serial_bridge>(m_config.serial2.value());
|
||||
}
|
||||
if (m_config.serial3.has_value()) {
|
||||
m_pty_3 = std::make_unique<serial_bridge>(m_config.serial3.value());
|
||||
}
|
||||
if (m_config.plc.has_value()) {
|
||||
m_plc = std::make_unique<plc_bridge>(m_config.plc.value());
|
||||
}
|
||||
if (m_config.bsp.has_value()) {
|
||||
m_bsp = std::make_unique<bsp_bridge>(m_config.bsp.value());
|
||||
}
|
||||
if (m_config.heartbeat.has_value()) {
|
||||
m_heartbeat = std::make_unique<heartbeat_service>(m_config.heartbeat.value(), [this](bool connected) {
|
||||
{
|
||||
auto handle = m_cb_status.handle();
|
||||
handle->is_connected = connected;
|
||||
}
|
||||
m_cb_status.notify_one();
|
||||
});
|
||||
}
|
||||
if (m_config.gpio.has_value()) {
|
||||
m_gpio = std::make_unique<gpio_bridge>(m_config.gpio.value());
|
||||
}
|
||||
}
|
||||
|
||||
charge_bridge::~charge_bridge() {
|
||||
m_cb_status.notify_one();
|
||||
}
|
||||
|
||||
void charge_bridge::manage(everest::lib::io::event::fd_event_handler& handler, std::atomic_bool const& run,
|
||||
bool force_update) {
|
||||
using namespace std::chrono_literals;
|
||||
m_event_handler = &handler;
|
||||
m_force_firmware_update = force_update;
|
||||
|
||||
auto action = [this](bool is_connected, bool discovery_pending, int& error_count) {
|
||||
if (discovery_pending) {
|
||||
if (m_discovery_active) {
|
||||
return;
|
||||
}
|
||||
m_discovery_active = true;
|
||||
m_event_handler->add_action([this]() { register_events(*m_event_handler); });
|
||||
return;
|
||||
}
|
||||
if (m_was_connected and not is_connected) {
|
||||
if (error_count > 1) {
|
||||
m_event_handler->add_action([this]() { unregister_events(*m_event_handler); });
|
||||
m_was_connected = false;
|
||||
} else {
|
||||
error_count++;
|
||||
}
|
||||
}
|
||||
if (not m_was_connected) {
|
||||
if (update_firmware(m_force_firmware_update)) {
|
||||
m_event_handler->add_action([this]() { register_events(*m_event_handler); });
|
||||
m_was_connected = true;
|
||||
error_count = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::thread manager([&run, action, this]() {
|
||||
auto handle = m_cb_status.handle();
|
||||
bool last_is_connected = handle->is_connected;
|
||||
bool last_discovery_pending = handle->discovery_pending;
|
||||
int error_count = 0;
|
||||
auto condition = [&] {
|
||||
if (handle->is_connected not_eq last_is_connected) {
|
||||
return true;
|
||||
}
|
||||
if (handle->discovery_pending not_eq last_discovery_pending) {
|
||||
return true;
|
||||
}
|
||||
if (not run.load()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
while (run.load()) {
|
||||
action(handle->is_connected, handle->discovery_pending, error_count);
|
||||
handle.wait_for(condition, 10s);
|
||||
last_is_connected = handle->is_connected;
|
||||
last_discovery_pending = handle->discovery_pending;
|
||||
}
|
||||
});
|
||||
manager.detach();
|
||||
}
|
||||
|
||||
bool charge_bridge::update_firmware(bool force) {
|
||||
firmware_update::sync_fw_updater updater(m_config.firmware);
|
||||
auto is_connected = updater.quick_check_connection();
|
||||
if (not is_connected) {
|
||||
return false;
|
||||
}
|
||||
updater.print_fw_version();
|
||||
|
||||
auto do_update = force or (m_config.firmware.fw_update_on_start and not updater.check_if_correct_fw_installed());
|
||||
|
||||
if (not do_update) {
|
||||
return true;
|
||||
}
|
||||
auto result = updater.upload_fw() && updater.check_connection();
|
||||
if (not result) {
|
||||
std::cout << "Error: could not install correct firmware version" << std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string charge_bridge::get_pty_1_slave_path() {
|
||||
if (m_pty_1) {
|
||||
return m_pty_1->get_slave_path();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string charge_bridge::get_pty_2_slave_path() {
|
||||
if (m_pty_2) {
|
||||
return m_pty_2->get_slave_path();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string charge_bridge::get_pty_3_slave_path() {
|
||||
if (m_pty_3) {
|
||||
return m_pty_3->get_slave_path();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool charge_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
if (m_can_0_client) {
|
||||
result = handler.register_event_handler(m_can_0_client.get()) && result;
|
||||
}
|
||||
if (m_pty_1) {
|
||||
result = handler.register_event_handler(m_pty_1.get()) && result;
|
||||
}
|
||||
if (m_pty_2) {
|
||||
result = handler.register_event_handler(m_pty_2.get()) && result;
|
||||
}
|
||||
if (m_pty_3) {
|
||||
result = handler.register_event_handler(m_pty_3.get()) && result;
|
||||
}
|
||||
if (m_bsp) {
|
||||
result = handler.register_event_handler(m_bsp.get()) && result;
|
||||
}
|
||||
if (m_plc) {
|
||||
result = handler.register_event_handler(m_plc.get()) && result;
|
||||
}
|
||||
if (m_heartbeat) {
|
||||
result = handler.register_event_handler(m_heartbeat.get()) && result;
|
||||
}
|
||||
if (m_gpio) {
|
||||
result = handler.register_event_handler(m_gpio.get()) && result;
|
||||
}
|
||||
if (m_discovery) {
|
||||
result = handler.register_event_handler(m_discovery.get()) && result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
bool charge_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
if (m_can_0_client) {
|
||||
result = handler.unregister_event_handler(m_can_0_client.get()) && result;
|
||||
}
|
||||
if (m_pty_1) {
|
||||
result = handler.unregister_event_handler(m_pty_1.get()) && result;
|
||||
}
|
||||
if (m_pty_2) {
|
||||
result = handler.unregister_event_handler(m_pty_2.get()) && result;
|
||||
}
|
||||
if (m_pty_3) {
|
||||
result = handler.unregister_event_handler(m_pty_3.get()) && result;
|
||||
}
|
||||
if (m_bsp) {
|
||||
result = handler.unregister_event_handler(m_bsp.get()) && result;
|
||||
}
|
||||
if (m_plc) {
|
||||
result = handler.unregister_event_handler(m_plc.get()) && result;
|
||||
}
|
||||
if (m_heartbeat) {
|
||||
result = handler.unregister_event_handler(m_heartbeat.get()) && result;
|
||||
}
|
||||
if (m_gpio) {
|
||||
result = handler.unregister_event_handler(m_gpio.get()) && result;
|
||||
}
|
||||
if (m_discovery) {
|
||||
result = handler.unregister_event_handler(m_discovery.get()) && result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void charge_bridge::print_config() {
|
||||
print_charge_bridge_config(m_config);
|
||||
}
|
||||
|
||||
void print_charge_bridge_config(charge_bridge_config const& c) {
|
||||
using namespace utilities;
|
||||
std::cout << "ChargeBridge: " << c.cb_name << std::endl;
|
||||
std::cout << " * remote: " << c.cb_remote << std::endl;
|
||||
if (c.serial1) {
|
||||
std::cout << " * serial 1: " << c.serial1->serial_device;
|
||||
if (c.heartbeat.has_value() && CB_NUMBER_OF_UARTS >= 1) {
|
||||
std::cout << " " << to_string(c.heartbeat->cb_config.uarts[0]);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
if (c.serial2) {
|
||||
std::cout << " * serial 2: " << c.serial2->serial_device;
|
||||
if (c.heartbeat.has_value() && CB_NUMBER_OF_UARTS >= 2) {
|
||||
std::cout << " " << to_string(c.heartbeat->cb_config.uarts[1]);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
if (c.serial3) {
|
||||
std::cout << " * serial 3: " << c.serial3->serial_device;
|
||||
if (c.heartbeat.has_value() && CB_NUMBER_OF_UARTS >= 3) {
|
||||
std::cout << " " << to_string(c.heartbeat->cb_config.uarts[2]);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
if (c.can0) {
|
||||
std::cout << " * can 0: " << c.can0->can_device;
|
||||
if (c.heartbeat.has_value()) {
|
||||
std::cout << " " << to_string(c.heartbeat->cb_config.can.baudrate) << "bps" << std::endl;
|
||||
}
|
||||
}
|
||||
if (c.plc) {
|
||||
std::cout << " * plc: " << c.plc->plc_tap << std::flush;
|
||||
std::cout << " " << c.cb_remote << ":" << c.plc->cb_port;
|
||||
std::cout << " adress " << c.plc->plc_ip;
|
||||
std::cout << " netmask " << c.plc->plc_netmaks;
|
||||
std::cout << " MTU " << c.plc->plc_mtu << std::endl;
|
||||
}
|
||||
if (c.bsp) {
|
||||
if (c.bsp->api.evse.enabled) {
|
||||
std::cout << " * evse_bsp: ";
|
||||
} else if (c.bsp->api.ev.enabled) {
|
||||
std::cout << " * ev_bsp: ";
|
||||
}
|
||||
std::cout << c.bsp->cb_remote << ":" << c.bsp->cb_port;
|
||||
std::cout << " module " << c.bsp->api.evse.module_id;
|
||||
std::cout << " MQTT " << c.bsp->api.mqtt_remote << ":" << c.bsp->api.mqtt_port;
|
||||
if (not c.bsp->api.mqtt_bind.empty()) {
|
||||
std::cout << " on " << c.bsp->api.mqtt_bind;
|
||||
}
|
||||
std::cout << " ping " << c.bsp->api.mqtt_ping_interval_ms << "ms";
|
||||
if (c.bsp->api.ovm.enabled) {
|
||||
std::cout << " OVM module " << c.bsp->api.ovm.module_id;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
if (c.heartbeat) {
|
||||
std::cout << " * heartbeat: " << c.cb_remote << ":" << c.cb_port;
|
||||
std::cout << " heartbeat interval " << c.heartbeat->interval_s << "s" << std::endl;
|
||||
}
|
||||
if (c.gpio) {
|
||||
std::cout << " * gpio: " << c.cb_remote << ":" << c.cb_port;
|
||||
std::cout << " MQTT " << c.gpio->mqtt_remote << ":" << c.gpio->mqtt_port;
|
||||
if (not c.gpio->mqtt_bind.empty()) {
|
||||
std::cout << " on " << c.gpio->mqtt_bind;
|
||||
}
|
||||
std::cout << " send interval " << c.gpio->interval_s << "s" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "\n" << std::endl;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,122 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include "charge_bridge/utilities/string.hpp"
|
||||
#include <charge_bridge/discovery.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <type_traits>
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
namespace {
|
||||
std::string to_string(discovery_device_type val) {
|
||||
switch (val) {
|
||||
case discovery_device_type::CB_EV:
|
||||
return "CB-CCS-EV-LU";
|
||||
|
||||
case discovery_device_type::CB_EVSE:
|
||||
return "CB-CCS-EVSE-LU";
|
||||
default:
|
||||
return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
bool is_cb_match(std::string const& board_type, discovery_device_type discriminator) {
|
||||
auto result = board_type == to_string(discriminator);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::string discovery::discovery_id = "_chargebridge._udp.local";
|
||||
|
||||
discovery::discovery(discovery_device_type type) : m_type(type) {
|
||||
using namespace std::chrono_literals;
|
||||
m_timer.set_timeout(1s);
|
||||
|
||||
for (auto const& item : everest::lib::io::socket::get_all_interaces()) {
|
||||
add_client(item.name);
|
||||
}
|
||||
}
|
||||
|
||||
discovery::discovery(discovery_device_type type, std::set<std::string> const& interfaces, bool excluding) :
|
||||
m_type(type) {
|
||||
using namespace std::chrono_literals;
|
||||
m_timer.set_timeout(1s);
|
||||
|
||||
for (auto const& item : everest::lib::io::socket::get_all_interaces()) {
|
||||
if (not interfaces.empty()) {
|
||||
if (interfaces.count(item.name) == 1 and excluding) {
|
||||
continue;
|
||||
}
|
||||
if (interfaces.count(item.name) == 0 and not excluding) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
std::cout << " using interface: " << item.name << std::endl;
|
||||
add_client(item.name);
|
||||
}
|
||||
}
|
||||
|
||||
void discovery::add_client(std::string const& interface) {
|
||||
auto client = std::make_unique<everest::lib::io::mdns::mdns_client>(interface);
|
||||
client->set_rx_handler([&](auto const& data, auto&) {
|
||||
auto discovery = everest::lib::io::mdns::parse_mdns_packet(data.buffer);
|
||||
if (discovery.has_value()) {
|
||||
if (m_registry.update(discovery.value())) {
|
||||
query_registry();
|
||||
}
|
||||
}
|
||||
});
|
||||
m_mdns.push_back(std::move(client));
|
||||
}
|
||||
|
||||
void discovery::query_registry() {
|
||||
auto obj = m_registry.get();
|
||||
for (auto const& [key, value] : obj) {
|
||||
if (not utilities::string_ends_with(key, discovery_id)) {
|
||||
continue;
|
||||
}
|
||||
if (not value.txt.count("board_type") or not is_cb_match(value.txt.at("board_type"), m_type)) {
|
||||
continue;
|
||||
}
|
||||
if (not m_on_discover) {
|
||||
continue;
|
||||
}
|
||||
m_on_discover(value.ip);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void discovery::set_discovery_callback(discovery_cb const& cb) {
|
||||
m_on_discover = cb;
|
||||
}
|
||||
|
||||
bool discovery::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
for (auto& item : m_mdns) {
|
||||
if (item) {
|
||||
result = handler.register_event_handler(item.get()) && result;
|
||||
}
|
||||
}
|
||||
handler.register_event_handler(&m_timer, [&](auto) {
|
||||
for (auto& item : m_mdns) {
|
||||
item->get_raw_handler()->query(discovery_id);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool discovery::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
for (auto& item : m_mdns) {
|
||||
if (item) {
|
||||
result = handler.unregister_event_handler(item.get()) && result;
|
||||
}
|
||||
}
|
||||
handler.unregister_event_handler(&m_timer);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,214 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "charge_bridge/utilities/string.hpp"
|
||||
#include "everest/io/mqtt/mosquitto_cpp.hpp"
|
||||
#include "protocol/evse_bsp_cb_to_host.h"
|
||||
#include <charge_bridge/everest_api/api_connector.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
namespace mqtt = everest::lib::io::mqtt;
|
||||
|
||||
namespace {
|
||||
const int mqtt_reconnect_to_ms = 1000;
|
||||
}
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
api_connector::api_connector(everest_api_config const& config, std::string const& cb_identifier) :
|
||||
m_cb_identifier(cb_identifier),
|
||||
m_mqtt(mqtt_reconnect_to_ms),
|
||||
m_evse_bsp(config.evse, cb_identifier, m_host_status),
|
||||
m_ovm(config.ovm, cb_identifier, m_host_status),
|
||||
m_ev_bsp(config.ev, cb_identifier, m_host_status) {
|
||||
|
||||
everest::lib::API::Topics api_topics;
|
||||
m_evse_bsp_enabled = config.evse.enabled;
|
||||
m_ovm_enabled = config.ovm.enabled;
|
||||
m_ev_bsp_enabled = config.ev.enabled;
|
||||
|
||||
if (m_evse_bsp_enabled && m_ev_bsp_enabled) {
|
||||
throw std::runtime_error("Configuration error: Cannot enable EV and EVSE BSP at the same time");
|
||||
}
|
||||
utilities::print_error(m_cb_identifier, "BSP/CB", 0) << "ChargeBridge connected." << std::endl;
|
||||
|
||||
if (m_evse_bsp_enabled) {
|
||||
api_topics.setup(config.evse.module_id, "evse_board_support", 1);
|
||||
m_evse_bsp_receive_topic = api_topics.everest_to_extern("");
|
||||
m_evse_bsp_send_topic = api_topics.extern_to_everest("");
|
||||
m_evse_bsp.set_mqtt_tx(
|
||||
[this](auto const& topic, auto const& payload) { m_mqtt.publish(m_evse_bsp_send_topic + topic, payload); });
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
api_topics.setup(config.ovm.module_id, "over_voltage_monitor", 1);
|
||||
m_ovm_receive_topic = api_topics.everest_to_extern("");
|
||||
m_ovm_send_topic = api_topics.extern_to_everest("");
|
||||
m_ovm.set_mqtt_tx(
|
||||
[this](auto const& topic, auto const& payload) { m_mqtt.publish(m_ovm_send_topic + topic, payload); });
|
||||
}
|
||||
|
||||
if (m_ev_bsp_enabled) {
|
||||
api_topics.setup(config.ev.module_id, "ev_board_support", 1);
|
||||
m_ev_bsp_receive_topic = api_topics.everest_to_extern("");
|
||||
m_ev_bsp_send_topic = api_topics.extern_to_everest("");
|
||||
m_ev_bsp.set_mqtt_tx(
|
||||
[this](auto const& topic, auto const& payload) { m_mqtt.publish(m_ev_bsp_send_topic + topic, payload); });
|
||||
}
|
||||
|
||||
m_mqtt.set_error_handler([this](int code, std::string const& msg) {
|
||||
auto is_error = code == 0 ? 0 : 1;
|
||||
utilities::print_error(m_cb_identifier, "BSP/MQTT", is_error) << msg << std::endl;
|
||||
});
|
||||
|
||||
m_mqtt.set_callback_connect([this, config](auto&, auto, auto, auto const&) { handle_mqtt_connect(); });
|
||||
|
||||
m_mqtt.connect(config.mqtt_bind, config.mqtt_remote, config.mqtt_port, config.mqtt_ping_interval_ms);
|
||||
|
||||
m_sync_timer.set_timeout(1s);
|
||||
|
||||
std::memset(&m_host_status, 0, sizeof(m_host_status));
|
||||
}
|
||||
|
||||
bool api_connector::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
if (m_evse_bsp_enabled) {
|
||||
result = handler.register_event_handler(&m_evse_bsp) && result;
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
result = handler.register_event_handler(&m_ovm) && result;
|
||||
}
|
||||
if (m_ev_bsp_enabled) {
|
||||
result = handler.register_event_handler(&m_ev_bsp) && result;
|
||||
}
|
||||
result = handler.register_event_handler(&m_mqtt) && result;
|
||||
result = handler.register_event_handler(&m_sync_timer, [this](auto&) {
|
||||
if (m_evse_bsp_enabled) {
|
||||
m_evse_bsp.sync(m_cb_connected);
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
m_ovm.sync(m_cb_connected);
|
||||
}
|
||||
if (m_ev_bsp_enabled) {
|
||||
m_ev_bsp.sync(m_cb_connected);
|
||||
}
|
||||
handle_cb_connection_state();
|
||||
}) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool api_connector::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
if (m_evse_bsp_enabled) {
|
||||
result = handler.unregister_event_handler(&m_evse_bsp) && result;
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
result = handler.unregister_event_handler(&m_ovm) && result;
|
||||
}
|
||||
if (m_ev_bsp_enabled) {
|
||||
result = handler.unregister_event_handler(&m_ev_bsp) && result;
|
||||
}
|
||||
result = handler.unregister_event_handler(&m_mqtt) && result;
|
||||
result = handler.unregister_event_handler(&m_sync_timer) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void api_connector::set_cb_tx(tx_ftor const& handler) {
|
||||
m_tx = handler;
|
||||
m_evse_bsp.set_cb_tx(handler);
|
||||
m_ev_bsp.set_cb_tx(handler);
|
||||
}
|
||||
|
||||
void api_connector::set_cb_message(evse_bsp_cb_to_host const& msg) {
|
||||
m_last_cb_heartbeat = std::chrono::steady_clock::now();
|
||||
if (m_evse_bsp_enabled) {
|
||||
m_evse_bsp.set_cb_message(msg);
|
||||
}
|
||||
if (m_ev_bsp_enabled) {
|
||||
m_ev_bsp.set_cb_message(msg);
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
m_ovm.set_cb_message(msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool api_connector::check_cb_heartbeat() {
|
||||
if (m_last_cb_heartbeat == std::chrono::steady_clock::time_point::max()) {
|
||||
return false;
|
||||
}
|
||||
return std::chrono::steady_clock::now() - m_last_cb_heartbeat < 2s;
|
||||
}
|
||||
|
||||
void api_connector::handle_mqtt_connect() {
|
||||
if (m_evse_bsp_enabled) {
|
||||
m_mqtt.subscribe(m_evse_bsp_receive_topic + "#",
|
||||
[this](auto&, auto const& topic, auto const& payload, auto, auto const&) {
|
||||
auto operation = utilities::string_after_pattern(topic, m_evse_bsp_receive_topic);
|
||||
if (not operation.empty()) {
|
||||
m_evse_bsp.dispatch(operation, static_cast<std::string>(payload));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
m_mqtt.subscribe(m_ovm_receive_topic + "#",
|
||||
[this](auto&, auto const& topic, auto const& payload, auto, auto const&) {
|
||||
auto operation = utilities::string_after_pattern(topic, m_ovm_receive_topic);
|
||||
if (not operation.empty()) {
|
||||
m_ovm.dispatch(operation, static_cast<std::string>(payload));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (m_ev_bsp_enabled) {
|
||||
m_mqtt.subscribe(m_ev_bsp_receive_topic + "#",
|
||||
[this](auto&, auto const& topic, auto const& payload, auto, auto const&) {
|
||||
auto operation = utilities::string_after_pattern(topic, m_ev_bsp_receive_topic);
|
||||
if (not operation.empty()) {
|
||||
m_ev_bsp.dispatch(operation, static_cast<std::string>(payload));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void api_connector::handle_cb_connection_state() {
|
||||
m_tx(m_host_status);
|
||||
auto current = check_cb_heartbeat();
|
||||
auto handle_status = [this](bool status) {
|
||||
if (status) {
|
||||
utilities::print_error(m_cb_identifier, "BSP/CB", 0) << "ChargeBridge connected." << std::endl;
|
||||
if (m_evse_bsp_enabled) {
|
||||
m_evse_bsp.clear_comm_fault();
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
m_ovm.clear_comm_fault();
|
||||
}
|
||||
if (m_ev_bsp_enabled) {
|
||||
m_ev_bsp.clear_comm_fault();
|
||||
}
|
||||
|
||||
} else {
|
||||
if (m_evse_bsp_enabled) {
|
||||
m_evse_bsp.raise_comm_fault();
|
||||
}
|
||||
if (m_ovm_enabled) {
|
||||
m_ovm.raise_comm_fault();
|
||||
}
|
||||
if (m_ev_bsp_enabled) {
|
||||
m_ev_bsp.raise_comm_fault();
|
||||
}
|
||||
|
||||
utilities::print_error(m_cb_identifier, "BSP/CB", 1) << "Waiting for ChargeBridge...." << std::endl;
|
||||
}
|
||||
};
|
||||
if (m_cb_initial_comm_check) {
|
||||
handle_status(current);
|
||||
m_cb_initial_comm_check = false;
|
||||
}
|
||||
if (m_cb_connected != current) {
|
||||
handle_status(not m_cb_connected);
|
||||
}
|
||||
m_cb_connected = current;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,418 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "protocol/cb_common.h"
|
||||
#include "protocol/evse_bsp_cb_to_host.h"
|
||||
#include <charge_bridge/everest_api/ev_bsp_api.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <charge_bridge/utilities/string.hpp>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <everest_api_types/ev_board_support/codec.hpp>
|
||||
#include <everest_api_types/evse_board_support/codec.hpp>
|
||||
#include <everest_api_types/generic/codec.hpp>
|
||||
#include <everest_api_types/utilities/codec.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace everest::lib::API::V1_0::types::generic;
|
||||
using namespace everest::lib::API;
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
|
||||
ev_bsp_api::ev_bsp_api([[maybe_unused]] evse_ev_bsp_config const& config, std::string const& cb_identifier,
|
||||
evse_bsp_host_to_cb& host_status) :
|
||||
host_status(host_status), m_cb_identifier(cb_identifier) {
|
||||
|
||||
last_everest_heartbeat = std::chrono::steady_clock::time_point();
|
||||
}
|
||||
|
||||
void ev_bsp_api::sync(bool cb_connected) {
|
||||
m_cb_connected = cb_connected;
|
||||
handle_everest_connection_state();
|
||||
}
|
||||
|
||||
bool ev_bsp_api::register_events([[maybe_unused]] everest::lib::io::event::fd_event_handler& handler) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ev_bsp_api::unregister_events([[maybe_unused]] everest::lib::io::event::fd_event_handler& handler) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ev_bsp_api::set_cb_tx(tx_ftor const& handler) {
|
||||
m_tx = handler;
|
||||
}
|
||||
|
||||
void ev_bsp_api::tx(evse_bsp_host_to_cb const& msg) {
|
||||
if (m_tx) {
|
||||
m_tx(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::set_mqtt_tx(mqtt_ftor const& handler) {
|
||||
m_mqtt_tx = handler;
|
||||
}
|
||||
|
||||
void ev_bsp_api::send_bsp_event(API_EVSE_BSP::Event data) {
|
||||
API_EVSE_BSP::BspEvent event{data};
|
||||
send_mqtt("bsp_event", serialize(event));
|
||||
}
|
||||
|
||||
void ev_bsp_api::send_bsp_measurement(API_EV_BSP::BspMeasurement data) {
|
||||
API_EV_BSP::BspMeasurement measurement{data};
|
||||
send_mqtt("bsp_measurement", serialize(measurement));
|
||||
}
|
||||
|
||||
void ev_bsp_api::handle_event_relay(std::uint8_t relay) {
|
||||
using bc_event = API_EVSE_BSP::Event;
|
||||
bc_event relaise_event;
|
||||
bool relaise_state_valid = true;
|
||||
switch (relay) {
|
||||
case RelaiseState::RelayState_Open:
|
||||
relaise_event = bc_event::PowerOff;
|
||||
break;
|
||||
case RelaiseState::RelayState_Closed:
|
||||
relaise_event = bc_event::PowerOn;
|
||||
break;
|
||||
default:
|
||||
relaise_state_valid = false;
|
||||
}
|
||||
if (relaise_state_valid) {
|
||||
send_bsp_event(relaise_event);
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::handle_event_cp(std::uint8_t cp) {
|
||||
using bc_event = API_EVSE_BSP::Event;
|
||||
bc_event cp_event;
|
||||
bool cp_state_valid = true;
|
||||
switch (cp) {
|
||||
case CpState_A:
|
||||
cp_event = bc_event::A;
|
||||
break;
|
||||
case CpState_B:
|
||||
cp_event = bc_event::B;
|
||||
break;
|
||||
case CpState_C:
|
||||
cp_event = bc_event::C;
|
||||
break;
|
||||
case CpState_D:
|
||||
cp_event = bc_event::D;
|
||||
break;
|
||||
case CpState_E:
|
||||
cp_event = bc_event::Disconnected;
|
||||
break;
|
||||
case CpState_F:
|
||||
cp_event = bc_event::F;
|
||||
break;
|
||||
case CpState_DF:
|
||||
cp_event = bc_event::E;
|
||||
break;
|
||||
case CpState::CpState_INVALID:
|
||||
cp_event = bc_event::E;
|
||||
break;
|
||||
default:
|
||||
cp_state_valid = false;
|
||||
}
|
||||
if (cp_state_valid) {
|
||||
last_cp_event = cp_event;
|
||||
send_bsp_event(cp_event);
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::handle_bsp_measurement(uint16_t cp, [[maybe_unused]] uint8_t pp_1, [[maybe_unused]] uint8_t pp2) {
|
||||
// FIXME implement PP correctly
|
||||
API_EV_BSP::BspMeasurement data;
|
||||
data.cp_pwm_duty_cycle = cp / 65536. * 100.;
|
||||
API_EVSE_BSP::ProximityPilot pp;
|
||||
API_EVSE_BSP::Ampacity amp;
|
||||
amp = API_EVSE_BSP::Ampacity::None;
|
||||
pp.ampacity = amp;
|
||||
data.proximity_pilot = pp;
|
||||
send_bsp_measurement(data);
|
||||
}
|
||||
|
||||
inline static bool operator==(const SafetyErrorFlags& a, const SafetyErrorFlags& b) {
|
||||
return a.raw == b.raw;
|
||||
}
|
||||
inline static bool operator!=(const SafetyErrorFlags& a, const SafetyErrorFlags& b) {
|
||||
return a.raw != b.raw;
|
||||
}
|
||||
|
||||
void ev_bsp_api::set_cb_message(evse_bsp_cb_to_host const& msg) {
|
||||
|
||||
if (m_cb_status.cp_state not_eq msg.cp_state) {
|
||||
handle_event_cp(msg.cp_state);
|
||||
}
|
||||
|
||||
if (m_cb_status.relay_state != msg.relay_state) {
|
||||
handle_event_relay(msg.relay_state);
|
||||
}
|
||||
|
||||
if (m_cb_status.cp_duty_cycle not_eq msg.cp_duty_cycle or m_cb_status.pp_state_type1 not_eq msg.pp_state_type1 or
|
||||
m_cb_status.pp_state_type2 not_eq msg.pp_state_type2) {
|
||||
handle_bsp_measurement(msg.cp_duty_cycle, m_cb_status.pp_state_type1, m_cb_status.pp_state_type2);
|
||||
}
|
||||
|
||||
if (m_cb_status.error_flags not_eq msg.error_flags) {
|
||||
handle_error(msg.error_flags);
|
||||
}
|
||||
|
||||
// This is not supported in EVerest yet but should be added at some point
|
||||
/*
|
||||
if (cb_status.stop_charging not_eq msg.stop_charging) {
|
||||
handle_stop_button(msg.stop_charging);
|
||||
}*/
|
||||
|
||||
// The ev_board_support interface in EVerest does not yet have proper errors defined, so we do not handle errors
|
||||
// here yet
|
||||
/*
|
||||
if (m_cb_status.error_flags not_eq msg.error_flags) {
|
||||
handle_error(msg.error_flags);
|
||||
}*/
|
||||
|
||||
m_cb_status = msg;
|
||||
}
|
||||
|
||||
enum class SafetyErrorMask : std::uint32_t {
|
||||
cp_not_state_c = (1 << 0),
|
||||
pwm_not_enabled = (1 << 1),
|
||||
pp_invalid = (1 << 2),
|
||||
plug_temperature_too_high = (1 << 3),
|
||||
internal_temperature_too_high = (1 << 4),
|
||||
emergency_input_latched = (1 << 5),
|
||||
relay_health_latched = (1 << 6),
|
||||
vdd_3v3_out_of_range = (1 << 7),
|
||||
vdd_core_out_of_range = (1 << 8),
|
||||
vdd_12V_out_of_range = (1 << 9),
|
||||
vdd_N12V_out_of_range = (1 << 10),
|
||||
vdd_refint_out_of_range = (1 << 11),
|
||||
external_allow_power_on = (1 << 12),
|
||||
config_mem_error = (1 << 13),
|
||||
dc_hv_ov = (1 << 14),
|
||||
};
|
||||
|
||||
// Table that maps a mask to our API error + message
|
||||
struct FlagSpec {
|
||||
SafetyErrorMask mask;
|
||||
const char* message;
|
||||
};
|
||||
|
||||
static constexpr FlagSpec error_specs[] = {
|
||||
{SafetyErrorMask::pp_invalid, "PP invalid"},
|
||||
{SafetyErrorMask::plug_temperature_too_high, "Plug temperature too high"},
|
||||
{SafetyErrorMask::internal_temperature_too_high, "ChargeBridge internal over temperature"},
|
||||
{SafetyErrorMask::emergency_input_latched, "Emergency input latched"},
|
||||
{SafetyErrorMask::relay_health_latched, "Relay welded error"},
|
||||
{SafetyErrorMask::vdd_3v3_out_of_range, "Supply voltage 3.3V out of range"},
|
||||
{SafetyErrorMask::vdd_core_out_of_range, "Internal supply core voltage out of range"},
|
||||
{SafetyErrorMask::vdd_12V_out_of_range, "Internal supply 12V voltage out of range"},
|
||||
{SafetyErrorMask::vdd_N12V_out_of_range, "Internal supply -12V voltage out of range"},
|
||||
{SafetyErrorMask::vdd_refint_out_of_range, "Internal supply VREF voltage out of range"},
|
||||
{SafetyErrorMask::config_mem_error, "Internal config memory error"},
|
||||
{SafetyErrorMask::dc_hv_ov, "DC HV OVM. FIXME: This should be on OVM not EVSE interface"},
|
||||
};
|
||||
|
||||
static constexpr FlagSpec print_warning_specs[] = {
|
||||
{SafetyErrorMask::cp_not_state_c, "CP is not state C"},
|
||||
{SafetyErrorMask::pwm_not_enabled, "PWM not enabled"},
|
||||
{SafetyErrorMask::external_allow_power_on, "Allow power on from EVerest missing"},
|
||||
};
|
||||
|
||||
void ev_bsp_api::handle_error(const SafetyErrorFlags& data) {
|
||||
std::uint32_t next = data.raw; // current raw value
|
||||
std::stringstream log;
|
||||
|
||||
for (const auto& s : print_warning_specs) {
|
||||
if (next & static_cast<std::uint32_t>(s.mask)) {
|
||||
log << "[" << s.message << "] ";
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& s : error_specs) {
|
||||
if (next & static_cast<std::uint32_t>(s.mask)) {
|
||||
log << "[" << s.message << "] ";
|
||||
}
|
||||
}
|
||||
|
||||
if (m_everest_connected && m_cb_connected) {
|
||||
if (log.str().empty()) {
|
||||
utilities::print_error(m_cb_identifier, "EV/EVEREST", 0) << "Relays can be switched on." << std::endl;
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "EV/EVEREST", 0) << "Relays off due to:" << log.str() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::dispatch(std::string const& operation, std::string const& payload) {
|
||||
if (operation == "enable") {
|
||||
receive_enable(payload);
|
||||
} else if (operation == "set_cp_state") {
|
||||
receive_set_cp_state(payload);
|
||||
} else if (operation == "allow_power_on") {
|
||||
receive_allow_power_on(payload);
|
||||
} else if (operation == "diode_fail") {
|
||||
receive_diode_fail(payload);
|
||||
} else if (operation == "set_ac_max_current") {
|
||||
receive_set_ac_max_current(payload);
|
||||
} else if (operation == "set_three_phases") {
|
||||
receive_set_three_phases(payload);
|
||||
} else if (operation == "set_rcd_error") {
|
||||
receive_set_rcd_error(payload);
|
||||
} else if (operation == "heartbeat") {
|
||||
receive_heartbeat(payload);
|
||||
} else {
|
||||
std::cerr << "ev_bsp_api: RECEIVE invalid operation: " << operation << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::raise_comm_fault() {
|
||||
send_raise_error(API_GENERIC::ErrorEnum::CommunicationFault, "ChargeBridge not available", "");
|
||||
}
|
||||
|
||||
void ev_bsp_api::clear_comm_fault() {
|
||||
send_clear_error(API_GENERIC::ErrorEnum::CommunicationFault, "ChargeBridge not available");
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_enable([[maybe_unused]] std::string const& payload) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
static CpState evcpstate_to_cpstate(API_EV_BSP::EvCpState s) {
|
||||
switch (s) {
|
||||
case API_EV_BSP::EvCpState::A:
|
||||
return CpState::CpState_A;
|
||||
case API_EV_BSP::EvCpState::B:
|
||||
return CpState::CpState_B;
|
||||
case API_EV_BSP::EvCpState::C:
|
||||
return CpState::CpState_C;
|
||||
case API_EV_BSP::EvCpState::D:
|
||||
return CpState::CpState_D;
|
||||
case API_EV_BSP::EvCpState::E:
|
||||
return CpState::CpState_E;
|
||||
default:
|
||||
return CpState::CpState_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_set_cp_state(std::string const& payload) {
|
||||
API_EV_BSP::EvCpState cp;
|
||||
|
||||
if (everest::lib::API::deserialize(payload, cp)) {
|
||||
host_status.ev_set_cp_state = evcpstate_to_cpstate(cp);
|
||||
tx(host_status);
|
||||
} else {
|
||||
std::cerr << "ev_bsp_api::receive_set_cp_state: payload invalid -> " << payload << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_allow_power_on(std::string const& payload) {
|
||||
bool on;
|
||||
|
||||
if (everest::lib::API::deserialize(payload, on)) {
|
||||
host_status.allow_power_on = static_cast<std::uint8_t>(on);
|
||||
tx(host_status);
|
||||
} else {
|
||||
std::cerr << "ev_bsp_api::receive_allow_power_on: payload invalid -> " << payload << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_diode_fail(std::string const& payload) {
|
||||
bool on;
|
||||
|
||||
if (everest::lib::API::deserialize(payload, on)) {
|
||||
host_status.ev_set_diodefault = static_cast<std::uint8_t>(on);
|
||||
tx(host_status);
|
||||
} else {
|
||||
std::cerr << "ev_bsp_api::receive_diode_fail: payload invalid -> " << payload << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_set_ac_max_current([[maybe_unused]] std::string const& payload) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_set_three_phases([[maybe_unused]] std::string const& payload) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_set_rcd_error([[maybe_unused]] std::string const& payload) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
void ev_bsp_api::receive_heartbeat(std::string const& pl) {
|
||||
last_everest_heartbeat = std::chrono::steady_clock::now();
|
||||
std::size_t id = 0;
|
||||
if (deserialize(pl, id)) {
|
||||
auto delta = id - m_last_hb_id;
|
||||
if (delta > 1) {
|
||||
utilities::print_error(m_cb_identifier, "EV_BSP/EVEREST", -1)
|
||||
<< "EVerest heartbeat missmatch: " << m_last_hb_id << "<->" << id << std::endl;
|
||||
}
|
||||
m_last_hb_id = id;
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "EV_BSP/EVEREST", -1)
|
||||
<< "EVerest invalid heartbeat message: " << pl << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ev_bsp_api::send_communication_check() {
|
||||
send_mqtt("communication_check", serialize(true));
|
||||
}
|
||||
|
||||
void ev_bsp_api::send_mqtt(std::string const& topic, std::string const& message) {
|
||||
if (m_mqtt_tx) {
|
||||
m_mqtt_tx(topic, message);
|
||||
}
|
||||
}
|
||||
|
||||
bool ev_bsp_api::check_everest_heartbeat() {
|
||||
return std::chrono::steady_clock::now() - last_everest_heartbeat < 2s;
|
||||
}
|
||||
|
||||
void ev_bsp_api::handle_everest_connection_state() {
|
||||
send_communication_check();
|
||||
auto current = check_everest_heartbeat();
|
||||
auto handle_status = [this](bool status) {
|
||||
if (status) {
|
||||
utilities::print_error(m_cb_identifier, "EV/EVEREST", 0) << "EVerest connected" << std::endl;
|
||||
// re-send last CP state event
|
||||
send_bsp_event(last_cp_event);
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "EV/EVEREST", 1) << "Waiting for EVerest..." << std::endl;
|
||||
// unplug CP if EVerest disconnects
|
||||
host_status.ev_set_cp_state = CpState_A;
|
||||
tx(host_status);
|
||||
}
|
||||
};
|
||||
|
||||
if (m_bc_initial_comm_check) {
|
||||
handle_status(current);
|
||||
m_bc_initial_comm_check = false;
|
||||
} else if (m_everest_connected != current) {
|
||||
handle_status(not m_everest_connected);
|
||||
}
|
||||
m_everest_connected = current;
|
||||
}
|
||||
|
||||
void ev_bsp_api::send_raise_error(API_GENERIC::ErrorEnum error, std::string const& subtype, std::string const& msg) {
|
||||
API_GENERIC::Error error_msg;
|
||||
error_msg.type = error;
|
||||
error_msg.sub_type = subtype;
|
||||
error_msg.message = msg;
|
||||
send_mqtt("raise_error", serialize(error_msg));
|
||||
}
|
||||
|
||||
void ev_bsp_api::send_clear_error(API_GENERIC::ErrorEnum error, std::string const& subtype) {
|
||||
API_GENERIC::Error error_msg;
|
||||
error_msg.type = error;
|
||||
error_msg.sub_type = subtype;
|
||||
send_mqtt("clear_error", serialize(error_msg));
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,514 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "protocol/evse_bsp_cb_to_host.h"
|
||||
#include <charge_bridge/everest_api/evse_bsp_api.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <charge_bridge/utilities/string.hpp>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest_api_types/evse_board_support/API.hpp>
|
||||
#include <everest_api_types/evse_board_support/codec.hpp>
|
||||
#include <everest_api_types/evse_manager/codec.hpp>
|
||||
#include <everest_api_types/generic/codec.hpp>
|
||||
#include <everest_api_types/utilities/codec.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace everest::lib::API::V1_0::types::generic;
|
||||
using namespace everest::lib::API;
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
|
||||
evse_bsp_api::evse_bsp_api(evse_bsp_config const& config, std::string const& cb_identifier,
|
||||
evse_bsp_host_to_cb& host_status) :
|
||||
host_status(host_status), m_capabilities(config.capabilities), m_cb_identifier(cb_identifier) {
|
||||
|
||||
last_everest_heartbeat = std::chrono::steady_clock::time_point();
|
||||
|
||||
m_capabilities_timer.set_timeout(10s);
|
||||
|
||||
std::memset(&cb_status, 0, sizeof(cb_status));
|
||||
|
||||
m_enabled = true;
|
||||
}
|
||||
|
||||
void evse_bsp_api::sync(bool cb_connected) {
|
||||
m_cb_connected = cb_connected;
|
||||
handle_everest_connection_state();
|
||||
}
|
||||
|
||||
bool evse_bsp_api::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
// clang-format off
|
||||
return
|
||||
handler.register_event_handler(&m_capabilities_timer, [this](auto&) {
|
||||
send_capabilities();
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
bool evse_bsp_api::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
// clang-format off
|
||||
return
|
||||
handler.unregister_event_handler(&m_capabilities_timer);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void evse_bsp_api::set_cb_tx(tx_ftor const& handler) {
|
||||
m_tx = handler;
|
||||
}
|
||||
|
||||
void evse_bsp_api::tx(evse_bsp_host_to_cb const& msg) {
|
||||
if (m_tx) {
|
||||
m_tx(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::set_mqtt_tx(mqtt_ftor const& handler) {
|
||||
m_mqtt_tx = handler;
|
||||
}
|
||||
|
||||
inline static bool operator==(const SafetyErrorFlags& a, const SafetyErrorFlags& b) {
|
||||
return a.raw == b.raw;
|
||||
}
|
||||
inline static bool operator!=(const SafetyErrorFlags& a, const SafetyErrorFlags& b) {
|
||||
return a.raw != b.raw;
|
||||
}
|
||||
|
||||
void evse_bsp_api::set_cb_message(evse_bsp_cb_to_host const& msg) {
|
||||
if (cb_status.reset_reason not_eq msg.reset_reason) {
|
||||
}
|
||||
if (cb_status.cp_state not_eq msg.cp_state) {
|
||||
handle_event_cp(msg.cp_state);
|
||||
}
|
||||
if (cb_status.relay_state != msg.relay_state) {
|
||||
handle_event_relay(msg.relay_state);
|
||||
}
|
||||
if (cb_status.error_flags not_eq msg.error_flags) {
|
||||
handle_error(msg.error_flags);
|
||||
}
|
||||
if (cb_status.pp_state_type1 not_eq msg.pp_state_type1) {
|
||||
handle_pp_type1(msg.pp_state_type1);
|
||||
}
|
||||
if (cb_status.pp_state_type2 not_eq msg.pp_state_type2) {
|
||||
handle_pp_type2(msg.pp_state_type2);
|
||||
}
|
||||
if (cb_status.stop_charging not_eq msg.stop_charging) {
|
||||
handle_stop_button(msg.stop_charging);
|
||||
}
|
||||
// cb_status.lock_state is not checked here as it cannot be reported to EVerest.
|
||||
|
||||
cb_status = msg;
|
||||
}
|
||||
|
||||
void evse_bsp_api::dispatch(std::string const& operation, std::string const& payload) {
|
||||
if (operation == "enable") {
|
||||
receive_enable(payload);
|
||||
} else if (operation == "pwm_on") {
|
||||
receive_pwm_on(payload);
|
||||
} else if (operation == "cp_state_X1") {
|
||||
receive_cp_state_X1(payload);
|
||||
} else if (operation == "cp_state_F") {
|
||||
receive_cp_state_F(payload);
|
||||
} else if (operation == "allow_power_on") {
|
||||
receive_allow_power_on(payload);
|
||||
} else if (operation == "ac_switch_three_phases_while_charging") {
|
||||
receive_ac_switch_three_phases_while_charging(payload);
|
||||
} else if (operation == "ac_overcurrent_limit") {
|
||||
receive_ac_overcurrent_limit(payload);
|
||||
} else if (operation == "lock") {
|
||||
receive_lock();
|
||||
} else if (operation == "unlock") {
|
||||
receive_unlock();
|
||||
} else if (operation == "self_test") {
|
||||
receive_self_test(payload);
|
||||
} else if (operation == "reset") {
|
||||
receive_request_reset(payload);
|
||||
} else if (operation == "heartbeat") {
|
||||
receive_heartbeat(payload);
|
||||
} else {
|
||||
std::cerr << "evse_bsp: RECEIVE invalid operation: " << operation << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::raise_comm_fault() {
|
||||
send_raise_error(API_BSP::ErrorEnum::CommunicationFault, "ChargeBridge not available", "");
|
||||
}
|
||||
|
||||
void evse_bsp_api::clear_comm_fault() {
|
||||
send_clear_error(API_BSP::ErrorEnum::CommunicationFault, "ChargeBridge not available", "");
|
||||
}
|
||||
|
||||
void evse_bsp_api::handle_event_cp(std::uint8_t cp) {
|
||||
using bc_event = API_BSP::Event;
|
||||
bc_event cp_event;
|
||||
bool cp_state_valid = true;
|
||||
switch (cp) {
|
||||
case CpState_A:
|
||||
cp_event = bc_event::A;
|
||||
send_clear_error(API_BSP::ErrorEnum::MREC14PilotFault, "", "");
|
||||
send_clear_error(API_BSP::ErrorEnum::DiodeFault, "", "");
|
||||
break;
|
||||
case CpState_B:
|
||||
cp_event = bc_event::B;
|
||||
break;
|
||||
case CpState_C:
|
||||
cp_event = bc_event::C;
|
||||
break;
|
||||
case CpState_D:
|
||||
cp_event = bc_event::D;
|
||||
break;
|
||||
case CpState_E:
|
||||
cp_event = bc_event::E;
|
||||
break;
|
||||
case CpState_F:
|
||||
cp_event = bc_event::F;
|
||||
break;
|
||||
case CpState_DF:
|
||||
cp_event = bc_event::E;
|
||||
send_raise_error(API_BSP::ErrorEnum::DiodeFault, "", "Diode Fault");
|
||||
break;
|
||||
case CpState::CpState_INVALID:
|
||||
cp_event = bc_event::E;
|
||||
send_raise_error(API_BSP::ErrorEnum::MREC14PilotFault, "", "Pilot Fault");
|
||||
break;
|
||||
default:
|
||||
cp_state_valid = false;
|
||||
}
|
||||
if (cp_state_valid and m_enabled) {
|
||||
send_event(cp_event);
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::handle_event_relay(std::uint8_t relay) {
|
||||
using bc_event = API_BSP::Event;
|
||||
bc_event relaise_event;
|
||||
bool relaise_state_valid = true;
|
||||
switch (relay) {
|
||||
case RelaiseState::RelayState_Open:
|
||||
relaise_event = bc_event::PowerOff;
|
||||
break;
|
||||
case RelaiseState::RelayState_Closed:
|
||||
relaise_event = bc_event::PowerOn;
|
||||
break;
|
||||
default:
|
||||
relaise_state_valid = false;
|
||||
}
|
||||
if (relaise_state_valid) {
|
||||
send_event(relaise_event);
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::handle_pp_type2(std::uint8_t data) {
|
||||
API_BSP::Ampacity bc_ampacity;
|
||||
bool bc_ampacity_valid = true;
|
||||
switch (data) {
|
||||
case PpState_Type2_STATE_NC:
|
||||
bc_ampacity = API_BSP::Ampacity::None;
|
||||
break;
|
||||
case PpState_Type2_STATE_13A:
|
||||
bc_ampacity = API_BSP::Ampacity::A_13;
|
||||
break;
|
||||
case PpState_Type2_STATE_20A:
|
||||
bc_ampacity = API_BSP::Ampacity::A_20;
|
||||
break;
|
||||
case PpState_Type2_STATE_32A:
|
||||
bc_ampacity = API_BSP::Ampacity::A_32;
|
||||
break;
|
||||
case PpState_Type2_STATE_70A:
|
||||
bc_ampacity = API_BSP::Ampacity::A_63_3ph_70_1ph;
|
||||
break;
|
||||
case PpState_Type2_STATE_FAULT:
|
||||
// Raise error check state
|
||||
bc_ampacity_valid = false;
|
||||
send_raise_error(API_BSP::ErrorEnum::MREC23ProximityFault, "", "Proximity Pilot Fault State");
|
||||
break;
|
||||
default:
|
||||
bc_ampacity_valid = false;
|
||||
}
|
||||
if (bc_ampacity_valid) {
|
||||
send_ac_pp_amapcity(bc_ampacity);
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::handle_pp_type1(std::uint8_t data) {
|
||||
// EVerest does not really have support for type 1 PP.
|
||||
// We just send a stop charging if some one presses the button,
|
||||
// for everything else the PP state does not really matter.
|
||||
if (data == PpState_Type1_STATE_Connected_Button_Pressed) {
|
||||
auto reason = API_EVM::StopTransactionReason::EVDisconnected;
|
||||
send_request_stop_transaction(reason);
|
||||
}
|
||||
}
|
||||
|
||||
// Error handling
|
||||
// Define bit masks
|
||||
enum class SafetyErrorMask : std::uint32_t {
|
||||
cp_not_state_c = (1 << 0),
|
||||
pwm_not_enabled = (1 << 1),
|
||||
pp_invalid = (1 << 2),
|
||||
plug_temperature_too_high = (1 << 3),
|
||||
internal_temperature_too_high = (1 << 4),
|
||||
emergency_input_latched = (1 << 5),
|
||||
relay_health_latched = (1 << 6),
|
||||
vdd_3v3_out_of_range = (1 << 7),
|
||||
vdd_core_out_of_range = (1 << 8),
|
||||
vdd_12V_out_of_range = (1 << 9),
|
||||
vdd_N12V_out_of_range = (1 << 10),
|
||||
vdd_refint_out_of_range = (1 << 11),
|
||||
external_allow_power_on = (1 << 12),
|
||||
config_mem_error = (1 << 13),
|
||||
dc_hv_ov = (1 << 14),
|
||||
};
|
||||
|
||||
// Table that maps a mask to our API error + message
|
||||
struct FlagSpec {
|
||||
SafetyErrorMask mask;
|
||||
API_BSP::ErrorEnum error;
|
||||
const char* subtype;
|
||||
const char* message;
|
||||
};
|
||||
|
||||
static constexpr FlagSpec error_specs[] = {
|
||||
{SafetyErrorMask::pp_invalid, API_BSP::ErrorEnum::MREC23ProximityFault, "", "PP invalid"},
|
||||
{SafetyErrorMask::plug_temperature_too_high, API_BSP::ErrorEnum::MREC19CableOverTempStop, "",
|
||||
"Plug temperature too high"},
|
||||
{SafetyErrorMask::internal_temperature_too_high, API_BSP::ErrorEnum::VendorError, "INTTEMP",
|
||||
"ChargeBridge internal over temperature"},
|
||||
{SafetyErrorMask::emergency_input_latched, API_BSP::ErrorEnum::VendorError, "EMGINPUT", "Emergency input latched"},
|
||||
{SafetyErrorMask::relay_health_latched, API_BSP::ErrorEnum::VendorError, "RELAYS", "Relay welded error"},
|
||||
{SafetyErrorMask::vdd_3v3_out_of_range, API_BSP::ErrorEnum::VendorError, "3V3", "Supply voltage 3.3V out of range"},
|
||||
{SafetyErrorMask::vdd_core_out_of_range, API_BSP::ErrorEnum::VendorError, "VDDCORE",
|
||||
"Internal supply core voltage out of range"},
|
||||
{SafetyErrorMask::vdd_12V_out_of_range, API_BSP::ErrorEnum::VendorError, "VCC12",
|
||||
"Internal supply 12V voltage out of range"},
|
||||
{SafetyErrorMask::vdd_N12V_out_of_range, API_BSP::ErrorEnum::VendorError, "VCCN12",
|
||||
"Internal supply -12V voltage out of range"},
|
||||
{SafetyErrorMask::vdd_refint_out_of_range, API_BSP::ErrorEnum::VendorError, "VCCREF",
|
||||
"Internal supply VREF voltage out of range"},
|
||||
{SafetyErrorMask::config_mem_error, API_BSP::ErrorEnum::VendorError, "CONFIGMEM", "Internal config memory error"},
|
||||
{SafetyErrorMask::dc_hv_ov, API_BSP::ErrorEnum::VendorError, "DV_HV",
|
||||
"DC HV OVM. FIXME: This should be on OVM not EVSE interface"},
|
||||
};
|
||||
|
||||
static constexpr FlagSpec print_warning_specs[] = {
|
||||
{SafetyErrorMask::cp_not_state_c, API_BSP::ErrorEnum::VendorWarning, "", "CP is not state C"},
|
||||
{SafetyErrorMask::pwm_not_enabled, API_BSP::ErrorEnum::VendorWarning, "", "PWM not enabled"},
|
||||
{SafetyErrorMask::external_allow_power_on, API_BSP::ErrorEnum::VendorWarning, "",
|
||||
"Allow power on from EVerest missing"},
|
||||
};
|
||||
|
||||
// 4) Edge-driven handler
|
||||
void evse_bsp_api::handle_error(const SafetyErrorFlags& data) {
|
||||
std::uint32_t prev = cb_status.error_flags.raw; // cached raw value from before
|
||||
std::uint32_t next = data.raw; // current raw value
|
||||
|
||||
std::uint32_t became_active = next & ~prev; // rising edges
|
||||
std::uint32_t became_inactive = prev & ~next; // falling edges
|
||||
|
||||
for (const auto& s : error_specs) {
|
||||
if (became_active & static_cast<std::uint32_t>(s.mask)) {
|
||||
send_raise_error(s.error, s.subtype, s.message);
|
||||
}
|
||||
if (became_inactive & static_cast<std::uint32_t>(s.mask)) {
|
||||
send_clear_error(s.error, s.subtype, "");
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream log;
|
||||
|
||||
for (const auto& s : print_warning_specs) {
|
||||
if (next & static_cast<std::uint32_t>(s.mask)) {
|
||||
log << "[" << s.message << "] ";
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& s : error_specs) {
|
||||
if (next & static_cast<std::uint32_t>(s.mask)) {
|
||||
log << "[" << s.message << "] ";
|
||||
}
|
||||
}
|
||||
|
||||
if (everest_connected && m_cb_connected) {
|
||||
if (log.str().empty()) {
|
||||
utilities::print_error(m_cb_identifier, "EVSE/EVEREST", 0) << "Relays can be switched on." << std::endl;
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "EVSE/EVEREST", 0)
|
||||
<< "Relays off due to:" << log.str() << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::handle_stop_button([[maybe_unused]] std::uint8_t data) {
|
||||
auto reason = API_EVM::StopTransactionReason::Local;
|
||||
send_request_stop_transaction(reason);
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_enable(std::string const& payload) {
|
||||
if (everest::lib::API::deserialize(payload, m_enabled)) {
|
||||
handle_event_cp(cb_status.cp_state);
|
||||
handle_event_relay(cb_status.relay_state);
|
||||
} else {
|
||||
std::cerr << "evse_bsp_api::receive_enabled: payload invalid -> " << payload << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_pwm_on(std::string const& payload) {
|
||||
double pwm = 0;
|
||||
if (everest::lib::API::deserialize(payload, pwm)) {
|
||||
host_status.pwm_duty_cycle = pwm * 100;
|
||||
tx(host_status);
|
||||
} else {
|
||||
std::cerr << "evse_bsp_api::receive_pwm_on: payload invalid -> " << payload << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_cp_state_X1([[maybe_unused]] std::string const& payload) {
|
||||
host_status.pwm_duty_cycle = 10001;
|
||||
tx(host_status);
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_cp_state_F([[maybe_unused]] std::string const& payload) {
|
||||
host_status.pwm_duty_cycle = 0;
|
||||
tx(host_status);
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_allow_power_on(std::string const& payload) {
|
||||
API_BSP::PowerOnOff obj;
|
||||
if (everest::lib::API::deserialize(payload, obj)) {
|
||||
host_status.allow_power_on = obj.allow_power_on;
|
||||
tx(host_status);
|
||||
} else {
|
||||
std::cerr << "evse_bsp_api::receive_allow_power_on: payload invalid -> " << payload << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_ac_switch_three_phases_while_charging(std::string const&) {
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_ac_overcurrent_limit(std::string const&) {
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_lock() {
|
||||
host_status.connector_lock = 1;
|
||||
tx(host_status);
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_unlock() {
|
||||
host_status.connector_lock = 0;
|
||||
tx(host_status);
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_self_test([[maybe_unused]] std::string const& payload) {
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_request_reset(std::string const&) {
|
||||
}
|
||||
|
||||
void evse_bsp_api::receive_heartbeat(std::string const& pl) {
|
||||
last_everest_heartbeat = std::chrono::steady_clock::now();
|
||||
std::size_t id = 0;
|
||||
if (deserialize(pl, id)) {
|
||||
auto delta = id - m_last_hb_id;
|
||||
if (delta > 1) {
|
||||
utilities::print_error(m_cb_identifier, "EVSE/EVEREST", -1)
|
||||
<< "EVerest heartbeat missmatch: " << m_last_hb_id << "<->" << id << std::endl;
|
||||
}
|
||||
m_last_hb_id = id;
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "EVSE/EVEREST", -1)
|
||||
<< "EVerest invalid heartbeat message: " << pl << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_event(API_BSP::Event data) {
|
||||
API_BSP::BspEvent event{data};
|
||||
send_mqtt("event", serialize(event));
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_ac_nr_of_phases(std::uint8_t data) {
|
||||
auto phases = static_cast<int>(data);
|
||||
if (phases > 0 && phases <= 3) {
|
||||
send_mqtt("ac_nr_of_phases", serialize(phases));
|
||||
}
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_capabilities() {
|
||||
send_mqtt("capabilities", serialize(m_capabilities));
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_ac_pp_amapcity(API_BSP::Ampacity data) {
|
||||
API_BSP::ProximityPilot msg{data};
|
||||
send_mqtt("ac_pp_ampacity", serialize(msg));
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_request_stop_transaction(API_EVM::StopTransactionReason data) {
|
||||
API_EVM::StopTransactionRequest request;
|
||||
request.reason = data;
|
||||
send_mqtt("request_stop_transaction", serialize(request));
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_rcd_current(std::uint8_t) {
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_raise_error(API_BSP::ErrorEnum error, std::string const& subtype, std::string const& msg) {
|
||||
API_BSP::Error error_msg;
|
||||
error_msg.type = error;
|
||||
error_msg.sub_type = subtype;
|
||||
error_msg.message = msg;
|
||||
send_mqtt("raise_error", serialize(error_msg));
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_clear_error(API_BSP::ErrorEnum error, std::string const& subtype, std::string const& msg) {
|
||||
API_BSP::Error error_msg;
|
||||
error_msg.type = error;
|
||||
error_msg.sub_type = subtype;
|
||||
error_msg.message = msg;
|
||||
send_mqtt("clear_error", serialize(error_msg));
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_communication_check() {
|
||||
send_mqtt("communication_check", serialize(true));
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_reply_reset([[maybe_unused]] std::string const& replyTo) {
|
||||
}
|
||||
|
||||
void evse_bsp_api::send_mqtt(std::string const& topic, std::string const& message) {
|
||||
m_mqtt_tx(topic, message);
|
||||
}
|
||||
|
||||
bool evse_bsp_api::check_everest_heartbeat() {
|
||||
return std::chrono::steady_clock::now() - last_everest_heartbeat < 2s;
|
||||
}
|
||||
|
||||
void evse_bsp_api::handle_everest_connection_state() {
|
||||
send_communication_check();
|
||||
auto current = check_everest_heartbeat();
|
||||
auto handle_status = [this](bool status) {
|
||||
if (status) {
|
||||
utilities::print_error(m_cb_identifier, "EVSE/EVEREST", 0) << "EVerest connected" << std::endl;
|
||||
send_capabilities();
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "EVSE/EVEREST", 1) << "Waiting for EVerest...." << std::endl;
|
||||
host_status.allow_power_on = 0;
|
||||
host_status.pwm_duty_cycle = 65535;
|
||||
tx(host_status);
|
||||
}
|
||||
};
|
||||
|
||||
if (m_bc_initial_comm_check) {
|
||||
handle_status(current);
|
||||
m_bc_initial_comm_check = false;
|
||||
} else if (everest_connected != current) {
|
||||
handle_status(not everest_connected);
|
||||
}
|
||||
everest_connected = current;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,226 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "protocol/cb_common.h"
|
||||
#include "protocol/evse_bsp_cb_to_host.h"
|
||||
#include <charge_bridge/everest_api/ovm_api.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <charge_bridge/utilities/string.hpp>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <everest_api_types/generic/codec.hpp>
|
||||
#include <everest_api_types/over_voltage_monitor/API.hpp>
|
||||
#include <everest_api_types/over_voltage_monitor/codec.hpp>
|
||||
#include <everest_api_types/utilities/codec.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace everest::lib::API::V1_0::types::generic;
|
||||
using namespace everest::lib::API;
|
||||
|
||||
namespace charge_bridge::evse_bsp {
|
||||
|
||||
ovm_api::ovm_api([[maybe_unused]] evse_ovm_config const& config, std::string const& cb_identifier,
|
||||
evse_bsp_host_to_cb& host_status) :
|
||||
host_status(host_status), m_cb_identifier(cb_identifier) {
|
||||
|
||||
last_everest_heartbeat = std::chrono::steady_clock::time_point();
|
||||
}
|
||||
|
||||
void ovm_api::sync(bool cb_connected) {
|
||||
m_cb_connected = cb_connected;
|
||||
handle_everest_connection_state();
|
||||
}
|
||||
|
||||
bool ovm_api::register_events([[maybe_unused]] everest::lib::io::event::fd_event_handler& handler) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ovm_api::unregister_events([[maybe_unused]] everest::lib::io::event::fd_event_handler& handler) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ovm_api::set_cb_tx(tx_ftor const& handler) {
|
||||
m_tx = handler;
|
||||
}
|
||||
|
||||
void ovm_api::tx(evse_bsp_host_to_cb const& msg) {
|
||||
if (m_tx) {
|
||||
m_tx(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ovm_api::set_mqtt_tx(mqtt_ftor const& handler) {
|
||||
m_mqtt_tx = handler;
|
||||
}
|
||||
|
||||
void ovm_api::set_cb_message(evse_bsp_cb_to_host const& msg) {
|
||||
const double voltage_V = msg.hv_mV * 0.001;
|
||||
send_voltage_measurement_V(voltage_V);
|
||||
|
||||
if (msg.error_flags.flags.dc_hv_ov_emergency not_eq m_cb_status.error_flags.flags.dc_hv_ov_emergency) {
|
||||
handle_dc_hv_ov_emergency(msg.error_flags.flags.dc_hv_ov_emergency not_eq 0);
|
||||
}
|
||||
if (msg.error_flags.flags.dc_hv_ov_error not_eq m_cb_status.error_flags.flags.dc_hv_ov_error) {
|
||||
handle_dc_hv_ov_error(msg.error_flags.flags.dc_hv_ov_error not_eq 0);
|
||||
}
|
||||
|
||||
if (msg.cp_state not_eq m_cb_status.cp_state) {
|
||||
handle_cp_state(static_cast<CpState>(msg.cp_state));
|
||||
}
|
||||
|
||||
m_cb_status = msg;
|
||||
}
|
||||
|
||||
void ovm_api::dispatch(std::string const& operation, std::string const& payload) {
|
||||
if (operation == "set_limits") {
|
||||
receive_set_limits(payload);
|
||||
} else if (operation == "start") {
|
||||
receive_start();
|
||||
} else if (operation == "stop") {
|
||||
receive_stop();
|
||||
} else if (operation == "reset_over_voltage_error") {
|
||||
receive_reset_over_voltage_error();
|
||||
} else if (operation == "heartbeat") {
|
||||
receive_heartbeat(payload);
|
||||
} else {
|
||||
std::cerr << "ovm_api: RECEIVE invalid operation: " << operation << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ovm_api::raise_comm_fault() {
|
||||
send_raise_error(API_OVM::ErrorEnum::CommunicationFault, "ChargeBridge not available", "",
|
||||
API_OVM::ErrorSeverityEnum::High);
|
||||
}
|
||||
|
||||
void ovm_api::clear_comm_fault() {
|
||||
send_clear_error(API_OVM::ErrorEnum::CommunicationFault, "ChargeBridge not available");
|
||||
}
|
||||
|
||||
void ovm_api::handle_dc_hv_ov_emergency(bool high) {
|
||||
static const std::string subtype = "Emergency";
|
||||
if (high) {
|
||||
send_raise_error(API_OVM::ErrorEnum::MREC5OverVoltage, subtype, "", API_OVM::ErrorSeverityEnum::High);
|
||||
} else {
|
||||
send_clear_error(API_OVM::ErrorEnum::MREC5OverVoltage, subtype);
|
||||
}
|
||||
}
|
||||
|
||||
void ovm_api::handle_dc_hv_ov_error(bool high) {
|
||||
static const std::string subtype = "Error";
|
||||
if (high) {
|
||||
send_raise_error(API_OVM::ErrorEnum::MREC5OverVoltage, subtype, "", API_OVM::ErrorSeverityEnum::Medium);
|
||||
} else {
|
||||
send_clear_error(API_OVM::ErrorEnum::MREC5OverVoltage, subtype);
|
||||
}
|
||||
}
|
||||
|
||||
void ovm_api::handle_cp_state(CpState state) {
|
||||
if (state == CpState_A) {
|
||||
send_clear_error(API_OVM::ErrorEnum::MREC5OverVoltage, "");
|
||||
}
|
||||
}
|
||||
|
||||
void ovm_api::receive_set_limits(std::string const& payload) {
|
||||
static auto const V_to_mV_factor = 1000;
|
||||
if (everest::lib::API::deserialize(payload, m_limits)) {
|
||||
host_status.ovm_limit_emergency_mV = static_cast<std::uint32_t>(m_limits.emergency_limit_V * V_to_mV_factor);
|
||||
host_status.ovm_limit_error_mV = static_cast<std::uint32_t>(m_limits.error_limit_V * V_to_mV_factor);
|
||||
tx(host_status);
|
||||
} else {
|
||||
std::cerr << "ovm_api::receive_set_limits: payload invalid -> " << payload << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ovm_api::receive_start() {
|
||||
host_status.ovm_enable = 1;
|
||||
host_status.ovm_reset_errors = 0;
|
||||
tx(host_status);
|
||||
}
|
||||
|
||||
void ovm_api::receive_stop() {
|
||||
host_status.ovm_enable = 0;
|
||||
tx(host_status);
|
||||
}
|
||||
|
||||
void ovm_api::receive_reset_over_voltage_error() {
|
||||
host_status.ovm_reset_errors = 1;
|
||||
tx(host_status);
|
||||
}
|
||||
|
||||
void ovm_api::receive_heartbeat(std::string const& pl) {
|
||||
last_everest_heartbeat = std::chrono::steady_clock::now();
|
||||
std::size_t id = 0;
|
||||
if (deserialize(pl, id)) {
|
||||
auto delta = id - m_last_hb_id;
|
||||
if (delta > 1) {
|
||||
utilities::print_error(m_cb_identifier, "OVM/EVEREST", -1)
|
||||
<< "EVerest heartbeat missmatch: " << m_last_hb_id << "<->" << id << std::endl;
|
||||
}
|
||||
m_last_hb_id = id;
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "EVSE/EVEREST", -1)
|
||||
<< "EVerest invalid heartbeat message: " << pl << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void ovm_api::send_voltage_measurement_V(double data) {
|
||||
send_mqtt("voltage_measurement_V", serialize(data));
|
||||
}
|
||||
|
||||
void ovm_api::send_raise_error(API_OVM::ErrorEnum error, std::string const& subtype, std::string const& msg,
|
||||
API_OVM::ErrorSeverityEnum severity) {
|
||||
API_OVM::Error error_msg;
|
||||
error_msg.type = error;
|
||||
error_msg.sub_type = subtype;
|
||||
error_msg.message = msg;
|
||||
error_msg.severity = severity;
|
||||
send_mqtt("raise_error", serialize(error_msg));
|
||||
}
|
||||
|
||||
void ovm_api::send_clear_error(API_OVM::ErrorEnum error, std::string const& subtype) {
|
||||
API_OVM::Error error_msg;
|
||||
error_msg.type = error;
|
||||
error_msg.sub_type = subtype;
|
||||
send_mqtt("clear_error", serialize(error_msg));
|
||||
}
|
||||
|
||||
void ovm_api::send_communication_check() {
|
||||
send_mqtt("communication_check", serialize(true));
|
||||
}
|
||||
|
||||
void ovm_api::send_mqtt(std::string const& topic, std::string const& message) {
|
||||
if (m_mqtt_tx) {
|
||||
m_mqtt_tx(topic, message);
|
||||
}
|
||||
}
|
||||
|
||||
bool ovm_api::check_everest_heartbeat() {
|
||||
return std::chrono::steady_clock::now() - last_everest_heartbeat < 2s;
|
||||
}
|
||||
|
||||
void ovm_api::handle_everest_connection_state() {
|
||||
send_communication_check();
|
||||
auto current = check_everest_heartbeat();
|
||||
auto handle_status = [this](bool status) {
|
||||
if (status) {
|
||||
utilities::print_error(m_cb_identifier, "OVM/EVEREST", 0) << "EVerest connected" << std::endl;
|
||||
} else {
|
||||
utilities::print_error(m_cb_identifier, "OVM/EVEREST", 1) << "Waiting for EVerest...." << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
if (m_bc_initial_comm_check) {
|
||||
handle_status(current);
|
||||
m_bc_initial_comm_check = false;
|
||||
} else if (m_everest_connected != current) {
|
||||
handle_status(not m_everest_connected);
|
||||
}
|
||||
m_everest_connected = current;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::evse_bsp
|
||||
@@ -0,0 +1,295 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include "everest/io/udp/udp_payload.hpp"
|
||||
#include <charge_bridge/firmware_update/sync_fw_updater.hpp>
|
||||
#include <charge_bridge/utilities/filesystem.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <charge_bridge/utilities/platform_utils.hpp>
|
||||
|
||||
#include <protocol/cb_management.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace {
|
||||
const int default_udp_timeout_ms = 3000;
|
||||
}
|
||||
|
||||
namespace charge_bridge::firmware_update {
|
||||
|
||||
const std::uint32_t sync_fw_updater::app_udp_sector_size = 0x2000;
|
||||
const std::uint16_t sync_fw_updater::sub_chunk_size = 1024;
|
||||
|
||||
using namespace everest::lib::io::udp;
|
||||
|
||||
static everest::lib::io::udp::udp_payload make_ping_command() {
|
||||
everest::lib::io::udp::udp_payload payload;
|
||||
|
||||
CbManagementPacket<CbFirmwarePing> packet;
|
||||
packet.type = CbStructType::CST_CbFirmwarePing;
|
||||
packet.data.dummy = 0;
|
||||
utilities::struct_to_vector(packet, payload.buffer);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
static everest::lib::io::udp::udp_payload make_get_version_command() {
|
||||
everest::lib::io::udp::udp_payload payload;
|
||||
|
||||
CbManagementPacket<CbFirmwareGetVersion> packet;
|
||||
packet.type = CbStructType::CST_CbFirmwareGetVersion;
|
||||
packet.data.dummy = 0;
|
||||
utilities::struct_to_vector(packet, payload.buffer);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
sync_fw_updater::sync_fw_updater(fw_update_config const& config) :
|
||||
m_udp(config.cb_remote, config.cb_port, 3, default_udp_timeout_ms), m_config(config) {
|
||||
}
|
||||
|
||||
std::optional<std::string> sync_fw_updater::get_fw_version() {
|
||||
auto pl = make_get_version_command();
|
||||
|
||||
auto result = m_udp.request_reply(pl);
|
||||
if (not result) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result->buffer[result->buffer.size() - 1] = 0x00; // ensure it is actually a 0 terminated string
|
||||
auto* str_ptr = reinterpret_cast<char*>(result->buffer.data()); // reinterpret for string conversion
|
||||
return std::string(str_ptr + 2); // skip 2 byte header
|
||||
}
|
||||
|
||||
void sync_fw_updater::print_fw_version() {
|
||||
auto result = get_fw_version();
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", not result.has_value())
|
||||
<< "Firmware version " << result.value_or("ERROR") << std::endl;
|
||||
}
|
||||
|
||||
bool sync_fw_updater::check_if_correct_fw_installed() {
|
||||
auto installed_fw = get_fw_version();
|
||||
|
||||
if (not installed_fw.has_value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
charge_bridge::filesystem_utils::CryptSignedHeader hdr;
|
||||
std::uint32_t offset;
|
||||
if (not read_crypt_signed_header(m_config.fw_path, hdr, offset)) {
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 1)
|
||||
<< "Could not read header for file: " << m_config.fw_path << std::endl;
|
||||
return false;
|
||||
}
|
||||
auto available_fw = hdr.firmware_version;
|
||||
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 0)
|
||||
<< "Firmware installed: \"" << installed_fw.value() << "\" Firmware available: \"" << available_fw << "\""
|
||||
<< std::endl;
|
||||
|
||||
if (installed_fw.value() == available_fw) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool sync_fw_updater::quick_check_connection() {
|
||||
static const std::uint16_t rr_timeout_ms = 200;
|
||||
static const std::uint16_t rr_retires_ms = 10;
|
||||
|
||||
everest::lib::io::udp::udp_payload pl = make_ping_command();
|
||||
auto result = m_udp.request_reply(pl, rr_timeout_ms, rr_retires_ms).has_value();
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", not result)
|
||||
<< (result ? "ChargeBride Connected" : "No connection to ChargeBridge") << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sync_fw_updater::check_connection() {
|
||||
static const std::uint16_t rr_timeout_ms = 150;
|
||||
static const std::uint16_t rr_retires_ms = 100;
|
||||
|
||||
everest::lib::io::udp::udp_payload pl = make_ping_command();
|
||||
auto result = m_udp.request_reply(pl, rr_timeout_ms, rr_retires_ms).has_value();
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", not result)
|
||||
<< (result ? "ChargeBride Connected" : "No connection to ChargeBridge") << std::endl;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool sync_fw_updater::ping() {
|
||||
everest::lib::io::udp::udp_payload pl = make_ping_command();
|
||||
|
||||
return m_udp.request_reply(pl).has_value();
|
||||
}
|
||||
|
||||
bool sync_fw_updater::check_reply(utilities::sync_udp_client::reply const& val) {
|
||||
if (val && val->size() == (sizeof(AppUDPResponse) + 2)) {
|
||||
AppUDPResponse reply;
|
||||
memcpy(&reply, val->buffer.data() + 2, sizeof(AppUDPResponse));
|
||||
return (reply == AppUDPResponse::AUR_Ok);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sync_fw_updater::upload_fw() {
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 0) << "Upload in progress" << std::endl;
|
||||
|
||||
if (not upload_firmware()) {
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 1) << "Upload of firmware image: " << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 0) << "Upload completed" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sync_fw_updater::upload_firmware() {
|
||||
auto path = m_config.fw_path;
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 0) << path << std::endl;
|
||||
|
||||
if (not fs::exists(path) || not fs::is_regular_file(path)) {
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 1) << "firmware file not found: " << path << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t offset;
|
||||
charge_bridge::filesystem_utils::CryptSignedHeader hdr;
|
||||
|
||||
if (not upload_init(path, offset, hdr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint32_t total_bytes = 0;
|
||||
std::uint16_t sector = 0;
|
||||
|
||||
if (not upload_transfer(path, sector, offset, total_bytes)) {
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 1) << "Upload failed at sector: " << sector << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (not upload_finish(path, total_bytes, hdr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
# File format for the binary update bundle:
|
||||
# 32 byte header [reserved]
|
||||
# 1 byte length of signature
|
||||
# signature binary
|
||||
# 1 byte NUM_SECTORS: This is the number of secure sectors
|
||||
# 16 byte IV
|
||||
# ... rest of the file is assembled firmware image: secure part...padding...non secure part (encrypted)
|
||||
*/
|
||||
|
||||
bool sync_fw_updater::upload_init(const fs::path& file_path, std::uint32_t& offset,
|
||||
charge_bridge::filesystem_utils::CryptSignedHeader& hdr) {
|
||||
everest::lib::io::udp::udp_payload payload;
|
||||
|
||||
if (not read_crypt_signed_header(file_path, hdr, offset)) {
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 1)
|
||||
<< "Could not read header for file: " << file_path << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 0)
|
||||
<< "Loaded firmware version file: " << file_path << " Version: " << hdr.firmware_version << std::endl;
|
||||
|
||||
CbManagementPacket<CbFirmwareStart> msg;
|
||||
msg.type = CbStructType::CST_CbFirmwareStart;
|
||||
|
||||
msg.data.is_secure_fw = true;
|
||||
msg.data.requires_crc_verification = true;
|
||||
msg.data.requires_sha256_verification = true;
|
||||
msg.data.requires_signature_verification = true;
|
||||
msg.data.requires_decryption = true;
|
||||
|
||||
// Copy the IV from the header
|
||||
std::memcpy(msg.data.iv, hdr.iv.data(), sizeof(msg.data.iv));
|
||||
|
||||
utilities::struct_to_vector(msg, payload.buffer);
|
||||
auto result = m_udp.request_reply(payload);
|
||||
|
||||
return check_reply(result);
|
||||
}
|
||||
|
||||
bool sync_fw_updater::upload_transfer(const fs::path& file_path, std::uint16_t& sector, std::uint32_t offset,
|
||||
std::uint32_t& total_bytes) {
|
||||
bool send_failed = false;
|
||||
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the header
|
||||
file.seekg(offset, std::ios::beg);
|
||||
|
||||
bool processed_file = filesystem_utils::process_file(
|
||||
file, sub_chunk_size, [&](const std::vector<std::uint8_t>& buffer, bool last_chunk) -> bool {
|
||||
total_bytes += buffer.size();
|
||||
|
||||
// Care must be taken when sending this over, since on the
|
||||
// receiving end we must remove the PKCS#7 added bytes
|
||||
auto block = make_fw_chunk(sector, last_chunk, buffer);
|
||||
auto result = m_udp.request_reply(block);
|
||||
|
||||
if (not check_reply(result)) {
|
||||
utilities::print_error(m_config.cb, "FIRMWARE", 1) << "chunk could not be sent" << std::endl;
|
||||
|
||||
send_failed = true;
|
||||
return true; // Interrupt
|
||||
}
|
||||
|
||||
sector++;
|
||||
|
||||
return false; // Continue
|
||||
});
|
||||
|
||||
return (processed_file) && (send_failed == false);
|
||||
}
|
||||
|
||||
bool sync_fw_updater::upload_finish([[maybe_unused]] const fs::path& file_path, std::uint32_t total_bytes,
|
||||
const charge_bridge::filesystem_utils::CryptSignedHeader& hdr) {
|
||||
CbManagementPacket<CbFirmwareEnd> fw_check_packet;
|
||||
|
||||
fw_check_packet.type = CbStructType::CST_CbFirmwareFinish;
|
||||
fw_check_packet.data.firmware_len = total_bytes;
|
||||
fw_check_packet.data.watermark_secure_end = hdr.num_sectors;
|
||||
|
||||
if (hdr.sig_len > sizeof(fw_check_packet.data.fw_signature) || hdr.sig_len > hdr.signature.size()) {
|
||||
return false;
|
||||
}
|
||||
memcpy(fw_check_packet.data.fw_signature, hdr.signature.data(), hdr.sig_len);
|
||||
fw_check_packet.data.fw_signature_len = hdr.sig_len;
|
||||
|
||||
udp_payload payload;
|
||||
utilities::struct_to_vector(fw_check_packet, payload.buffer);
|
||||
|
||||
// The final check can be a very slow operation due to the cryptography involved
|
||||
static const std::uint16_t rr_timeout_ms = 10000;
|
||||
static const std::uint16_t rr_retires_ms = 1;
|
||||
auto result = m_udp.request_reply(payload, rr_timeout_ms, rr_retires_ms);
|
||||
|
||||
return check_reply(result);
|
||||
}
|
||||
|
||||
udp_payload sync_fw_updater::make_fw_chunk(std::uint16_t sector, std::uint8_t last_chunk,
|
||||
std::vector<std::uint8_t> const& data) {
|
||||
CbManagementPacket<CbFirmwarePacket> fw_data_packet;
|
||||
fw_data_packet.type = CbStructType::CST_CbFirmwarePacket;
|
||||
fw_data_packet.data.last_packet = last_chunk;
|
||||
fw_data_packet.data.sector = sector;
|
||||
fw_data_packet.data.data_len = data.size();
|
||||
std::memcpy(fw_data_packet.data.data, data.data(), data.size());
|
||||
|
||||
udp_payload result;
|
||||
utilities::struct_to_vector(fw_data_packet, result.buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::firmware_update
|
||||
@@ -0,0 +1,156 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <charge_bridge/gpio_bridge.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <charge_bridge/utilities/platform_utils.hpp>
|
||||
#include <charge_bridge/utilities/string.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/mqtt/mqtt_client.hpp>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <protocol/cb_management.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace charge_bridge {
|
||||
using namespace std::chrono_literals;
|
||||
namespace mqtt = everest::lib::io::mqtt;
|
||||
|
||||
namespace {
|
||||
const int default_udp_timeout_ms = 1000;
|
||||
const int mqtt_reconnect_timeout_ms = 1000;
|
||||
} // namespace
|
||||
|
||||
gpio_bridge::gpio_bridge(gpio_config const& config) :
|
||||
m_udp(config.cb_remote, config.cb_port, default_udp_timeout_ms),
|
||||
m_mqtt(mqtt_reconnect_timeout_ms)
|
||||
|
||||
{
|
||||
m_identifier = config.cb + "/" + config.item;
|
||||
|
||||
m_heartbeat_timer.set_timeout(std::chrono::seconds(config.interval_s));
|
||||
|
||||
m_udp.set_rx_handler([this](auto const& data, auto&) { handle_udp_rx(data); });
|
||||
|
||||
m_udp.set_error_handler([this](auto id, auto const& msg) {
|
||||
utilities::print_error(m_identifier, "GPIO/UDP", id) << msg << std::endl;
|
||||
m_udp_on_error = id not_eq 0;
|
||||
});
|
||||
|
||||
m_receive_topic = "pionix/chargebridge/" + config.cb + "/gpio/output/";
|
||||
m_send_topic = "pionix/chargebridge/" + config.cb + "/gpio/input/";
|
||||
|
||||
m_mqtt.set_error_handler([this, config](int id, std::string const& msg) {
|
||||
utilities::print_error(m_identifier, "GPIO/MQTT", id) << msg << std::endl;
|
||||
m_mqtt_on_error = id not_eq 0;
|
||||
});
|
||||
|
||||
m_mqtt.set_callback_connect([this](auto&, auto, auto, auto const&) {
|
||||
m_mqtt.subscribe(
|
||||
m_receive_topic + "#", [this](auto&, auto const& payload) { dispatch(payload); },
|
||||
everest::lib::io::mqtt::mqtt_client::QoS::at_most_once);
|
||||
});
|
||||
|
||||
m_mqtt.connect(config.mqtt_bind, config.mqtt_remote, config.mqtt_port, config.mqtt_ping_interval_ms);
|
||||
|
||||
m_message.type = CbStructType::CST_HostToCb_Gpio;
|
||||
m_message.data.number_of_gpios = CB_NUMBER_OF_GPIOS;
|
||||
std::memset(m_message.data.gpio_values, 0, sizeof(m_message.data.gpio_values));
|
||||
}
|
||||
|
||||
gpio_bridge::~gpio_bridge() {
|
||||
}
|
||||
|
||||
bool gpio_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = handler.register_event_handler(&m_udp);
|
||||
result = handler.register_event_handler(&m_mqtt) && result;
|
||||
result = handler.register_event_handler(&m_heartbeat_timer, [this](auto&) { handle_heartbeat_timer(); }) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool gpio_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = handler.unregister_event_handler(&m_udp);
|
||||
result = handler.unregister_event_handler(&m_mqtt) && result;
|
||||
result = handler.unregister_event_handler(&m_heartbeat_timer) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void gpio_bridge::dispatch(everest::lib::io::mqtt::mqtt_client::message const& data) {
|
||||
auto& topic = data.topic;
|
||||
auto& payload = data.payload;
|
||||
auto operation = utilities::string_after_pattern(topic, m_receive_topic);
|
||||
uint16_t value = 0;
|
||||
int id = 0;
|
||||
|
||||
auto stous = [](std::string const& data) {
|
||||
auto val = stoi(data);
|
||||
if (val < 0 or val > std::numeric_limits<uint16_t>::max()) {
|
||||
throw std::range_error("");
|
||||
}
|
||||
return static_cast<uint16_t>(val);
|
||||
};
|
||||
|
||||
try {
|
||||
value = stous(payload);
|
||||
} catch (...) {
|
||||
std::cout << "INVALID DATA on MQTT for GPIO DATA" << std::endl;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
id = std::stoi(operation);
|
||||
} catch (...) {
|
||||
std::cout << "INVALID DATA on MQTT for GPIO ID" << std::endl;
|
||||
return;
|
||||
}
|
||||
if (id < 0 or id >= CB_NUMBER_OF_GPIOS) {
|
||||
std::cout << "INVALID GPIO ID" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
m_message.data.gpio_values[id] = value;
|
||||
send_udp();
|
||||
}
|
||||
|
||||
void gpio_bridge::send_mqtt(std::string const& topic, std::string const& message) {
|
||||
everest::lib::io::mqtt::mqtt_client::message payload;
|
||||
payload.topic = m_send_topic + topic;
|
||||
payload.payload = message;
|
||||
m_mqtt.publish(payload);
|
||||
}
|
||||
|
||||
void gpio_bridge::send_udp() {
|
||||
if (not m_udp_on_error) {
|
||||
everest::lib::io::udp::udp_payload payload;
|
||||
utilities::struct_to_vector(m_message, payload.buffer);
|
||||
m_udp.tx(payload);
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_bridge::handle_error_timer() {
|
||||
if (m_udp_on_error) {
|
||||
m_udp.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void gpio_bridge::handle_heartbeat_timer() {
|
||||
send_udp();
|
||||
}
|
||||
|
||||
void gpio_bridge::handle_udp_rx(everest::lib::io::udp::udp_payload const& payload) {
|
||||
CbManagementPacket<CbGpioPacket> data;
|
||||
if (payload.size() == sizeof(data)) {
|
||||
std::memcpy(&data, payload.buffer.data(), sizeof(data));
|
||||
for (std::size_t i = 0; i < sizeof(data.data.gpio_values) / sizeof(data.data.gpio_values[0]); ++i) {
|
||||
send_mqtt(std::to_string(i), std::to_string(data.data.gpio_values[i]));
|
||||
}
|
||||
} else {
|
||||
std::cout << "INVALID DATA SIZE in UDP RX of GPIO: " << payload.size() << " vs " << sizeof(data) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <charge_bridge/heartbeat_service.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <charge_bridge/utilities/platform_utils.hpp>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <protocol/cb_management.h>
|
||||
|
||||
namespace {
|
||||
const int default_udp_timeout_ms = 1000;
|
||||
const std::uint16_t s_to_ms_factor = 1000;
|
||||
} // namespace
|
||||
|
||||
namespace charge_bridge {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
heartbeat_service::heartbeat_service(heartbeat_config const& config,
|
||||
std::function<void(bool)> const& publish_connection_status) :
|
||||
m_udp(config.cb_remote, config.cb_port, default_udp_timeout_ms),
|
||||
m_publish_connection_status(publish_connection_status) {
|
||||
m_identifier = config.cb + "/" + config.item;
|
||||
std::memcpy(&m_config_message.data, &config.cb_config, sizeof(CbConfig));
|
||||
m_config_message.type = CbStructType::CST_HostToCb_Heartbeat;
|
||||
m_heartbeat_interval = std::chrono::milliseconds(config.interval_s * s_to_ms_factor);
|
||||
m_connection_to = std::chrono::milliseconds(config.connection_to_s * s_to_ms_factor);
|
||||
m_heartbeat_timer.set_timeout(m_heartbeat_interval);
|
||||
m_last_heartbeat_reply = std::chrono::steady_clock::time_point();
|
||||
|
||||
m_udp.set_rx_handler([this](auto const& data, auto&) { handle_udp_rx(data); });
|
||||
|
||||
m_udp.set_error_handler([this](auto id, auto const& msg) {
|
||||
if (m_inital_cb_commcheck and id == 0) {
|
||||
utilities::print_error(m_identifier, "HEARTBEAT/UDP", 1) << "Waiting for ChargeBridge" << std::endl;
|
||||
} else {
|
||||
utilities::print_error(m_identifier, "HEARTBEAT/UDP", id) << msg << std::endl;
|
||||
}
|
||||
m_udp_on_error = id not_eq 0;
|
||||
});
|
||||
}
|
||||
|
||||
heartbeat_service::~heartbeat_service() {
|
||||
}
|
||||
|
||||
bool heartbeat_service::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
// clang-format off
|
||||
return
|
||||
handler.register_event_handler(&m_udp) &&
|
||||
handler.register_event_handler(&m_heartbeat_timer, [this](auto&) { handle_heartbeat_timer(); });
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
bool heartbeat_service::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
// clang-format off
|
||||
return
|
||||
handler.unregister_event_handler(&m_udp) &&
|
||||
handler.unregister_event_handler(&m_heartbeat_timer);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void heartbeat_service::handle_error_timer() {
|
||||
if (m_udp_on_error) {
|
||||
m_udp.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void heartbeat_service::handle_heartbeat_timer() {
|
||||
if (not m_udp_on_error) {
|
||||
everest::lib::io::udp::udp_payload payload;
|
||||
utilities::struct_to_vector(m_config_message, payload.buffer);
|
||||
m_udp.tx(payload);
|
||||
}
|
||||
auto timeout = std::chrono::steady_clock::now() - m_last_heartbeat_reply > m_connection_to;
|
||||
if (timeout and m_cb_connected) {
|
||||
utilities::print_error(m_identifier, "HEARTBEAT/UDP", 1) << "ChargeBridge connection lost" << std::endl;
|
||||
m_cb_connected = false;
|
||||
}
|
||||
|
||||
else if (not timeout and not m_cb_connected) {
|
||||
utilities::print_error(m_identifier, "HEARTBEAT/UDP", 0) << "ChargeBridge connected" << std::endl;
|
||||
m_cb_connected = true;
|
||||
}
|
||||
if (m_publish_connection_status) {
|
||||
m_publish_connection_status(m_cb_connected);
|
||||
}
|
||||
}
|
||||
|
||||
void heartbeat_service::handle_udp_rx(everest::lib::io::udp::udp_payload const& payload) {
|
||||
CbManagementPacket<CbHeartbeatReplyPacket> data;
|
||||
if (payload.size() == sizeof(data)) {
|
||||
std::memcpy(&data, payload.buffer.data(), sizeof(data));
|
||||
m_last_heartbeat_reply = std::chrono::steady_clock::now();
|
||||
auto mcu_current = static_cast<uint32_t>(data.data.uptime_ms);
|
||||
if (mcu_current <= m_mcu_timestamp) {
|
||||
m_mcu_reset_count++;
|
||||
utilities::print_error(m_identifier, "HEARTBEAT/UDP", -1)
|
||||
<< "ChargeBridge reset count " << m_mcu_reset_count << std::endl;
|
||||
}
|
||||
m_mcu_timestamp = mcu_current;
|
||||
|
||||
// TODO: Once we have the telemetry framework in EVerest, we should publish those values.
|
||||
/*printf(
|
||||
"CP: %.2f/%.2f PP: %i MCU_temp %i degC\nVoltages: 12V: %.2f, -12V: %.2f, ref %.3f, 3.3V: %.3f, core:
|
||||
%.3f\n", data.data.cp_hi_mV / 1000., data.data.cp_lo_mV / 1000., (int)data.data.pp_mOhm / 1000,
|
||||
data.data.temperature_mcu_C, data.data.vdd_12V/1000., data.data.vdd_N12V/1000., data.data.vdd_refint/1000.,
|
||||
data.data.vdd_3v3/1000., data.data.vdd_core/1000.);*/
|
||||
} else {
|
||||
std::cout << "INVALID DATA SIZE in UDP RX of HEARTBEAT: " << payload.size() << " vs " << sizeof(data)
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <charge_bridge/plc_bridge.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <everest/io/udp/udp_payload.hpp>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
const int default_udp_timeout_ms = 1000;
|
||||
} // namespace
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
plc_bridge::plc_bridge(plc_bridge_config const& config) :
|
||||
m_tap(config.plc_tap, config.plc_ip, config.plc_netmaks, config.plc_mtu),
|
||||
m_udp(config.cb_remote, config.cb_port, default_udp_timeout_ms) {
|
||||
using namespace std::chrono_literals;
|
||||
m_timer.set_timeout(5s);
|
||||
|
||||
m_tap.set_rx_handler([this](auto const& data, auto&) {
|
||||
everest::lib::io::udp::udp_payload pl;
|
||||
pl.buffer = data;
|
||||
m_udp.tx(pl);
|
||||
});
|
||||
|
||||
m_udp.set_rx_handler([this](auto const& data, auto&) { m_tap.tx(data.buffer); });
|
||||
|
||||
auto identifier = config.cb + "/" + config.item;
|
||||
m_tap.set_error_handler([this, identifier](auto id, auto const& msg) {
|
||||
utilities::print_error(identifier, "PLC/TAP", id) << msg << std::endl;
|
||||
m_tap_on_error = id not_eq 0;
|
||||
});
|
||||
|
||||
m_udp.set_error_handler([this, identifier](auto id, auto const& msg) {
|
||||
utilities::print_error(identifier, "PLC/UDP", id) << msg << std::endl;
|
||||
m_udp_on_error = id not_eq 0;
|
||||
});
|
||||
}
|
||||
|
||||
void plc_bridge::handle_timer_event() {
|
||||
if (m_udp_on_error) {
|
||||
m_udp.reset();
|
||||
}
|
||||
if (m_tap_on_error) {
|
||||
m_tap.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool plc_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
result = handler.register_event_handler(&m_tap) && result;
|
||||
result = handler.register_event_handler(&m_udp) && result;
|
||||
result = handler.register_event_handler(&m_timer, [this](auto) { handle_timer_event(); }) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool plc_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
result = handler.unregister_event_handler(&m_tap) && result;
|
||||
result = handler.unregister_event_handler(&m_udp) && result;
|
||||
result = handler.unregister_event_handler(&m_timer) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include "everest/io/serial/event_pty.hpp"
|
||||
#include <charge_bridge/serial_bridge.hpp>
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <cstring>
|
||||
#include <everest/io/event/fd_event_handler.hpp>
|
||||
#include <protocol/cb_can_message.h>
|
||||
|
||||
namespace {
|
||||
const int default_udp_timeout_ms = 1000;
|
||||
const std::uint32_t tcp_user_timeout_ms = 4000;
|
||||
} // namespace
|
||||
|
||||
namespace charge_bridge {
|
||||
|
||||
serial_bridge::serial_bridge(serial_bridge_config const& config) :
|
||||
m_pty(), m_tcp(config.cb_remote, config.cb_port, default_udp_timeout_ms) {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto link_ok = m_symlink.set_link(m_pty.get_slave_path(), config.serial_device);
|
||||
if (not link_ok) {
|
||||
throw std::runtime_error("Failed to setup symbolic links for serial ports");
|
||||
}
|
||||
|
||||
m_tcp.set_on_ready_action([this]() {
|
||||
m_tcp.get_raw_handler()->set_keep_alive(3, 1, 1);
|
||||
m_tcp.get_raw_handler()->set_user_timeout(tcp_user_timeout_ms);
|
||||
});
|
||||
|
||||
m_pty.set_data_handler([this](auto const& data, auto&) { m_tcp.tx(data); });
|
||||
|
||||
m_tcp.set_rx_handler([this](auto const& data, auto&) { m_pty.tx(data); });
|
||||
|
||||
auto identifier = config.cb + "/" + config.item;
|
||||
m_pty.set_error_handler([this, identifier](auto id, auto const& msg) {
|
||||
utilities::print_error(identifier, "SERIAL/PTY", id) << msg << std::endl;
|
||||
if (id not_eq 0) {
|
||||
m_pty.reset();
|
||||
}
|
||||
});
|
||||
|
||||
m_tcp.set_error_handler([this, identifier](auto id, auto const& msg) {
|
||||
if (m_tcp_last_error_id not_eq id) {
|
||||
utilities::print_error(identifier, "SERIAL/TCP", id) << msg << std::endl;
|
||||
m_tcp_last_error_id = id;
|
||||
}
|
||||
if (id not_eq 0) {
|
||||
m_tcp.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void serial_bridge::reset_tcp() {
|
||||
m_tcp.reset();
|
||||
}
|
||||
|
||||
std::string serial_bridge::get_slave_path() {
|
||||
return m_pty.get_slave_path();
|
||||
}
|
||||
|
||||
bool serial_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
result = handler.register_event_handler(&m_pty) && result;
|
||||
result = handler.register_event_handler(&m_tcp) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool serial_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
||||
auto result = true;
|
||||
result = handler.unregister_event_handler(&m_pty) && result;
|
||||
result = handler.unregister_event_handler(&m_tcp) && result;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge
|
||||
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <charge_bridge/utilities/filesystem.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
|
||||
namespace charge_bridge::filesystem_utils {
|
||||
|
||||
bool read_from_file_partial(const fs::path& file_path, const std::size_t byte_count, std::string& out_data) {
|
||||
try {
|
||||
if (fs::is_regular_file(file_path)) {
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
|
||||
if (file.is_open()) {
|
||||
std::vector<char> buffer(byte_count);
|
||||
file.read(buffer.data(), byte_count);
|
||||
|
||||
std::size_t read_bytes = file.gcount();
|
||||
|
||||
if (read_bytes == byte_count) {
|
||||
out_data.assign(buffer.data(), read_bytes);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool read_from_file(const fs::path& file_path, std::string& out_data) {
|
||||
try {
|
||||
if (fs::is_regular_file(file_path)) {
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
|
||||
if (file.is_open()) {
|
||||
out_data = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool process_file(const fs::path& file_path, std::size_t buffer_size,
|
||||
std::function<bool(const std::vector<std::uint8_t>&, bool last_chunk)>&& func) {
|
||||
std::ifstream file(file_path, std::ios::binary);
|
||||
|
||||
return process_file(file, buffer_size, std::move(func));
|
||||
}
|
||||
|
||||
bool process_file(std::ifstream& file, std::size_t buffer_size,
|
||||
std::function<bool(const std::vector<std::uint8_t>&, bool last_chunk)>&& func) {
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> buffer(buffer_size);
|
||||
bool interupted = false;
|
||||
|
||||
while (file.read(reinterpret_cast<char*>(buffer.data()), buffer_size)) {
|
||||
interupted = func(buffer, false);
|
||||
|
||||
if (interupted) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process the remaining bytes
|
||||
if (interupted == false) {
|
||||
std::size_t remaining = file.gcount();
|
||||
|
||||
// Keep only remaining elements
|
||||
buffer.resize(remaining);
|
||||
func(buffer, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns true on success, fills `hdr`, and sets `image_offset` to the byte
|
||||
// position (from start of file) where the firmware image begins.
|
||||
bool read_crypt_signed_header(const fs::path& path, CryptSignedHeader& hdr, std::uint32_t& image_offset) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto read_exact = [&](void* dst, std::size_t n) -> bool {
|
||||
f.read(reinterpret_cast<char*>(dst), static_cast<std::streamsize>(n));
|
||||
return f.good() || (f.eof() && static_cast<std::size_t>(f.gcount()) == n);
|
||||
};
|
||||
|
||||
char firmware_version_str[32];
|
||||
std::memset(firmware_version_str, 0, sizeof(firmware_version_str)); // all zeros
|
||||
// 32-byte reserved header
|
||||
if (!read_exact(firmware_version_str, sizeof(firmware_version_str))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hdr.firmware_version = std::string(firmware_version_str);
|
||||
|
||||
// 1-byte signature length
|
||||
if (!read_exact(&hdr.sig_len, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// L-byte signature
|
||||
hdr.signature.resize(hdr.sig_len);
|
||||
if (hdr.sig_len > 0) {
|
||||
if (!read_exact(hdr.signature.data(), hdr.signature.size())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 1-byte NUM_SECTORS
|
||||
if (!read_exact(&hdr.num_sectors, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 16-byte IV
|
||||
if (!read_exact(hdr.iv.data(), hdr.iv.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Where the firmware image starts:
|
||||
// offset = 32 + 1 + L + 1 + 16
|
||||
image_offset = static_cast<std::uint32_t>(f.tellg());
|
||||
// As a sanity fallback, compute if tellg() failed:
|
||||
// Disabled, since it is always false
|
||||
// if (static_cast<std::streamoff>(image_offset)< 0) {
|
||||
// image_offset = 32u + 1u + static_cast<std::uint64_t>(hdr.sig_len) + 1u + 16u;
|
||||
// }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::filesystem_utils
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include <charge_bridge/utilities/logging.hpp>
|
||||
#include <iomanip>
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
enum class color {
|
||||
error,
|
||||
success,
|
||||
warning,
|
||||
message,
|
||||
unit,
|
||||
standard,
|
||||
terminal,
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& s, color c) {
|
||||
switch (c) {
|
||||
case color::error:
|
||||
s << "\033[31m";
|
||||
break;
|
||||
case color::success:
|
||||
s << "\033[32m";
|
||||
break;
|
||||
case color::warning:
|
||||
s << "\033[33m";
|
||||
break;
|
||||
case color::message:
|
||||
s << "\033[37m";
|
||||
break;
|
||||
case color::unit:
|
||||
s << "\033[1;37m";
|
||||
break;
|
||||
case color::terminal:
|
||||
s << "\033[m";
|
||||
break;
|
||||
case color::standard:
|
||||
default:
|
||||
s << "\033[39;49m";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::ostream& print_error(std::string const& device, std::string const& unit, int status) {
|
||||
// clang-format off
|
||||
auto ctrl =
|
||||
status == 0 ? color::success :
|
||||
status == -1 ? color::warning:
|
||||
color::error;
|
||||
std::cout << "[ " << ctrl << std::setw(13) << std::left << unit << color::terminal << " ] "
|
||||
<< color::unit << std::setw(20) << device << color::terminal << " ";
|
||||
if(status not_eq 0){
|
||||
if(status == -1){
|
||||
std::cout << color::standard << "WARNING ";
|
||||
}
|
||||
else{
|
||||
std::cout << color::standard << "ERROR ( " << status << " ) ";
|
||||
}
|
||||
}
|
||||
return std::cout << color::standard;
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,415 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include "c4/yml/node.hpp"
|
||||
#include <charge_bridge/utilities/parse_config.hpp>
|
||||
#include <charge_bridge/utilities/string.hpp>
|
||||
#include <charge_bridge/utilities/type_converters.hpp>
|
||||
#include <everest_api_types/evse_board_support/API.hpp>
|
||||
#include <everest_api_types/evse_board_support/codec.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include <filesystem>
|
||||
// clang-format off
|
||||
#include <ryml_std.hpp>
|
||||
#include <ryml.hpp>
|
||||
// clang-format on
|
||||
using namespace everest::lib::API::V1_0::types;
|
||||
|
||||
namespace {
|
||||
static const int g_cb_port_management = 6000;
|
||||
static const int g_cb_port_evse_bsp = 6001;
|
||||
static const int g_cb_port_plc = 6002;
|
||||
static const int g_cb_port_can0 = 6003;
|
||||
static const int g_cb_port_serial_1 = 6004;
|
||||
static const int g_cb_port_serial_2 = 6005;
|
||||
static const std::uint16_t default_mqtt_ping_interval_ms = 1000;
|
||||
|
||||
std::string print_yaml_location(ryml::Location const& loc) {
|
||||
std::stringstream error_msg;
|
||||
|
||||
if (loc) {
|
||||
if (not loc.name.empty()) {
|
||||
auto tmp = std::string(loc.name.str, loc.name.len);
|
||||
if (charge_bridge::utilities::string_ends_with(tmp, ".hpp")) {
|
||||
return "";
|
||||
}
|
||||
error_msg << "\n file ";
|
||||
error_msg << tmp;
|
||||
}
|
||||
error_msg << "\n line " << loc.line;
|
||||
if (loc.col) {
|
||||
error_msg << " column " << loc.col;
|
||||
}
|
||||
if (loc.offset) {
|
||||
error_msg << " offset " << loc.offset << "B";
|
||||
}
|
||||
error_msg << "\n";
|
||||
}
|
||||
return error_msg.str();
|
||||
}
|
||||
|
||||
void yaml_error_handler(const char* msg, std::size_t len, ryml::Location loc, void*) {
|
||||
std::stringstream error_msg;
|
||||
error_msg << "YAML parsing error: ";
|
||||
error_msg << print_yaml_location(loc);
|
||||
error_msg.write(msg, len);
|
||||
|
||||
std::cerr << error_msg.str() << std::endl;
|
||||
throw std::runtime_error(error_msg.str());
|
||||
}
|
||||
|
||||
void print_location(ryml::ConstNodeRef node, ryml::Parser& parser) {
|
||||
std::cerr << print_yaml_location(node.location(parser)) << std::endl;
|
||||
}
|
||||
|
||||
void load_yaml_file(const std::string& filename, ryml::Parser* parser, ryml::Tree* t) {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Could not open file: " + filename);
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
std::string file_content = buffer.str();
|
||||
|
||||
parse_in_arena(parser, ryml::to_csubstr(filename), ryml::to_csubstr(file_content), t);
|
||||
}
|
||||
|
||||
template <class T> c4::yml::ConstNodeRef decode(c4::yml::ConstNodeRef const& node, T& rhs) {
|
||||
using namespace charge_bridge::utilities;
|
||||
node >> rhs;
|
||||
return node;
|
||||
}
|
||||
|
||||
std::pair<std::string, c4::yml::ConstNodeRef> find_node(c4::yml::NodeRef& config, std::string const& main,
|
||||
std::string const& sub) {
|
||||
auto main_str = ryml::to_csubstr(main);
|
||||
auto node_str = main;
|
||||
c4::yml::ConstNodeRef node;
|
||||
if (not sub.empty()) {
|
||||
node_str = node_str + "::" + sub;
|
||||
auto sub_str = ryml::to_csubstr(sub);
|
||||
node = config.find_child(main_str);
|
||||
if (not node.invalid()) {
|
||||
node = config.find_child(main_str).find_child(sub_str);
|
||||
}
|
||||
} else {
|
||||
node = config[main_str];
|
||||
}
|
||||
return {node_str, node};
|
||||
}
|
||||
|
||||
template <class DataT>
|
||||
bool get_node_impl(c4::yml::ConstNodeRef node, ryml::Parser& parser, std::string const& node_str, DataT& data) {
|
||||
if (node.invalid()) {
|
||||
std::cerr << "Node not found: " << node_str << std::endl;
|
||||
throw std::runtime_error("");
|
||||
}
|
||||
try {
|
||||
decode(node, data);
|
||||
return true;
|
||||
} catch (std::exception const& e) {
|
||||
std::cerr << "Cannot parse config: " << node_str << std::endl;
|
||||
std::cerr << e.what() << std::endl;
|
||||
} catch (charge_bridge::utilities::yml_node_error const& e) {
|
||||
std::cerr << "Error source: \n"
|
||||
<< " parent " << node_str << "\n"
|
||||
<< " data " << e.m_msg << std::flush;
|
||||
print_location(e.m_node, parser);
|
||||
}
|
||||
throw std::runtime_error("");
|
||||
}
|
||||
|
||||
struct RymlCallbackInitializer {
|
||||
RymlCallbackInitializer() {
|
||||
ryml::set_callbacks({nullptr, nullptr, nullptr, yaml_error_handler});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
void parse_config_impl(c4::yml::NodeRef& config, charge_bridge_config& c, std::filesystem::path const& config_path,
|
||||
ryml::Parser& parser) {
|
||||
auto get_node = [&config, &parser](auto& data, std::string const& main, std::string const& sub = "") {
|
||||
auto [node_str, node] = find_node(config, main, sub);
|
||||
get_node_impl(node, parser, node_str, data);
|
||||
};
|
||||
|
||||
auto get_node_or_default = [&get_node, &config](auto& data, std::string const& main, std::string const& sub,
|
||||
auto fallback) {
|
||||
auto [node_str, node] = find_node(config, main, sub);
|
||||
if (node.invalid()) {
|
||||
data = fallback;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
get_node(data, main, sub);
|
||||
} catch (...) {
|
||||
data = fallback;
|
||||
}
|
||||
};
|
||||
|
||||
auto get_block = [&config, &c](std::string const& block, auto& block_cfg, auto const& ftor) {
|
||||
bool enable = false;
|
||||
auto block_str = ryml::to_csubstr(block);
|
||||
if (not config.find_child(block_str).invalid()) {
|
||||
if (config[block_str].find_child("enable").invalid()) {
|
||||
enable = true;
|
||||
} else {
|
||||
decode(config[block_str]["enable"], enable);
|
||||
}
|
||||
}
|
||||
if (enable) {
|
||||
block_cfg.emplace();
|
||||
ftor(*block_cfg, block);
|
||||
block_cfg->cb = c.cb_name;
|
||||
block_cfg->item = block;
|
||||
}
|
||||
};
|
||||
|
||||
get_node(c.cb_name, "charge_bridge", "name");
|
||||
|
||||
get_node(c.cb_remote, "charge_bridge", "ip");
|
||||
|
||||
c.cb_port = g_cb_port_management;
|
||||
|
||||
get_block("can_0", c.can0, [&](auto& cfg, auto const& main) {
|
||||
get_node(cfg.can_device, main, "local");
|
||||
cfg.cb_port = g_cb_port_can0;
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
});
|
||||
|
||||
get_block("serial_1", c.serial1, [&](auto& cfg, auto const& main) {
|
||||
get_node(cfg.serial_device, main, "local");
|
||||
cfg.cb_port = g_cb_port_serial_1;
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
});
|
||||
|
||||
get_block("serial_2", c.serial2, [&](auto& cfg, auto const& main) {
|
||||
get_node(cfg.serial_device, main, "local");
|
||||
cfg.cb_port = g_cb_port_serial_2;
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
});
|
||||
|
||||
// FIXME (JH) serial3 not availabe in first release
|
||||
// get_block("serial_3", c.serial3, [&](auto& cfg, auto const& main) {
|
||||
// get_node(main, "local", cfg.serial_device);
|
||||
// get_node(main, "port", cfg.cb_port);
|
||||
// cfg.cb_remote = c.cb_remote;
|
||||
// });
|
||||
|
||||
get_block("plc", c.plc, [&](auto& cfg, auto const& main) {
|
||||
get_node(cfg.plc_tap, main, "tap");
|
||||
get_node(cfg.plc_ip, main, "ip");
|
||||
get_node(cfg.plc_netmaks, main, "netmask");
|
||||
get_node(cfg.plc_mtu, main, "mtu");
|
||||
cfg.cb_port = g_cb_port_plc;
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
});
|
||||
|
||||
{
|
||||
bool wants_ev = false;
|
||||
bool wants_evse = false;
|
||||
get_node_or_default(wants_ev, "ev_bsp", "enable", false);
|
||||
get_node_or_default(wants_evse, "evse_bsp", "enable", false);
|
||||
if (wants_ev && wants_evse) {
|
||||
std::cerr << "Configuration error: Cannot enable EVSE and EV BSP at the same time" << std::endl;
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
get_block("evse_bsp", c.bsp, [&](auto& cfg, auto const& main) {
|
||||
cfg.cb_port = g_cb_port_evse_bsp;
|
||||
cfg.api.evse.enabled = true;
|
||||
get_node(cfg.api.evse.module_id, main, "module_id");
|
||||
get_node(cfg.api.mqtt_remote, main, "mqtt_remote");
|
||||
get_node_or_default(cfg.api.mqtt_bind, main, "mqtt_bind", "");
|
||||
get_node(cfg.api.mqtt_port, main, "mqtt_port");
|
||||
get_node_or_default(cfg.api.mqtt_ping_interval_ms, main, "mqtt_ping_interval_ms",
|
||||
default_mqtt_ping_interval_ms);
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
get_node(cfg.api.evse.capabilities, main, "capabilities");
|
||||
get_node(cfg.api.ovm.enabled, main, "ovm_enabled");
|
||||
get_node(cfg.api.ovm.module_id, main, "ovm_module_id");
|
||||
});
|
||||
|
||||
if (not c.bsp.has_value()) {
|
||||
get_block("ev_bsp", c.bsp, [&](auto& cfg, auto const& main) {
|
||||
cfg.cb_port = g_cb_port_evse_bsp;
|
||||
cfg.api.ev.enabled = true;
|
||||
get_node(cfg.api.ev.module_id, main, "module_id");
|
||||
get_node(cfg.api.mqtt_remote, main, "mqtt_remote");
|
||||
get_node_or_default(cfg.api.mqtt_bind, main, "mqtt_bind", "");
|
||||
get_node(cfg.api.mqtt_port, main, "mqtt_port");
|
||||
get_node_or_default(cfg.api.mqtt_ping_interval_ms, main, "mqtt_ping_interval_ms",
|
||||
default_mqtt_ping_interval_ms);
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
get_node(cfg.api.ovm.enabled, main, "ovm_enabled");
|
||||
get_node(cfg.api.ovm.module_id, main, "ovm_module_id");
|
||||
});
|
||||
}
|
||||
|
||||
get_block("gpio", c.gpio, [&](auto& cfg, auto const& main) {
|
||||
get_node(cfg.interval_s, main, "interval_s");
|
||||
get_node(cfg.mqtt_remote, main, "mqtt_remote");
|
||||
get_node_or_default(cfg.mqtt_bind, main, "mqtt_bind", "");
|
||||
get_node(cfg.mqtt_port, main, "mqtt_port");
|
||||
get_node_or_default(cfg.mqtt_ping_interval_ms, main, "mqtt_ping_interval_ms", default_mqtt_ping_interval_ms);
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
cfg.cb_port = c.cb_port;
|
||||
});
|
||||
|
||||
get_block("heartbeat", c.heartbeat, [&](auto& cfg, auto const& main) {
|
||||
get_node_or_default(cfg.interval_s, main, "interval_s", 1);
|
||||
get_node_or_default(cfg.connection_to_s, main, "connection_to_s", 3 * cfg.interval_s);
|
||||
cfg.cb_remote = c.cb_remote;
|
||||
cfg.cb_port = c.cb_port;
|
||||
get_node(cfg.cb_config.network, "charge_bridge");
|
||||
get_node(cfg.cb_config.safety, "safety");
|
||||
|
||||
std::memset(cfg.cb_config.gpios, 0, CB_NUMBER_OF_GPIOS * sizeof(CbGpioConfig));
|
||||
std::memset(cfg.cb_config.uarts, 0, CB_NUMBER_OF_UARTS * sizeof(CbUartConfig));
|
||||
if (c.serial1) {
|
||||
get_node(cfg.cb_config.uarts[0], "serial_1");
|
||||
}
|
||||
if (c.serial2) {
|
||||
get_node(cfg.cb_config.uarts[1], "serial_2");
|
||||
}
|
||||
// FIXME (JH) serial 3 not available in first release
|
||||
// if (c.serial3) {
|
||||
// get_main_node("serial_3", cfg.cb_config.uarts[2]);
|
||||
// }
|
||||
if (c.gpio) {
|
||||
for (auto i = 0; i < CB_NUMBER_OF_GPIOS; ++i) {
|
||||
get_node(cfg.cb_config.gpios[i], "gpio", "gpio_" + std::to_string(i));
|
||||
}
|
||||
}
|
||||
if (c.can0) {
|
||||
get_node(cfg.cb_config.can, "can_0");
|
||||
}
|
||||
get_node(cfg.cb_config.plc_powersaving_mode, "plc", "powersaving_mode");
|
||||
cfg.cb_config.config_version = CB_CONFIG_VERSION;
|
||||
});
|
||||
|
||||
get_node(c.firmware.fw_path, "charge_bridge", "fw_file");
|
||||
get_node(c.firmware.fw_update_on_start, "charge_bridge", "fw_update_on_start");
|
||||
|
||||
// If the path to the firmware file is relative, make it relative to the config file
|
||||
std::filesystem::path fw_path = c.firmware.fw_path;
|
||||
if (fw_path.is_relative()) {
|
||||
c.firmware.fw_path = config_path.parent_path().append(c.firmware.fw_path);
|
||||
}
|
||||
|
||||
c.firmware.cb_remote = c.cb_remote;
|
||||
c.firmware.cb_port = c.cb_port;
|
||||
c.firmware.cb = c.cb_name;
|
||||
}
|
||||
|
||||
charge_bridge_config set_config_placeholders(charge_bridge_config const& src, charge_bridge_config& result,
|
||||
std::string const& ip, std::size_t index) {
|
||||
auto index_str = std::to_string(index);
|
||||
result = src;
|
||||
auto replace = [index_str](std::string& src) { replace_all_in_place(src, "##", index_str); };
|
||||
|
||||
result.cb_remote = ip;
|
||||
result.firmware.cb_remote = ip;
|
||||
replace(result.cb_name);
|
||||
result.firmware.cb = result.cb_name;
|
||||
if (result.can0.has_value()) {
|
||||
result.can0->cb_remote = ip;
|
||||
result.can0->cb = result.cb_name;
|
||||
replace(result.can0->can_device);
|
||||
}
|
||||
if (result.serial1.has_value()) {
|
||||
result.serial1->cb_remote = ip;
|
||||
result.serial1->cb = result.cb_name;
|
||||
replace(result.serial1->serial_device);
|
||||
}
|
||||
if (result.serial2.has_value()) {
|
||||
result.serial2->cb_remote = ip;
|
||||
result.serial2->cb = result.cb_name;
|
||||
replace(result.serial2->serial_device);
|
||||
}
|
||||
if (result.serial3.has_value()) {
|
||||
result.serial3->cb_remote = ip;
|
||||
result.serial3->cb = result.cb_name;
|
||||
replace(result.serial3->serial_device);
|
||||
}
|
||||
if (result.plc.has_value()) {
|
||||
result.plc->cb_remote = ip;
|
||||
result.plc->cb = result.cb_name;
|
||||
replace(result.plc->plc_tap);
|
||||
}
|
||||
if (result.bsp.has_value()) {
|
||||
result.bsp->cb_remote = ip;
|
||||
result.bsp->cb = result.cb_name;
|
||||
replace(result.bsp->api.evse.module_id);
|
||||
replace(result.bsp->api.ev.module_id);
|
||||
replace(result.bsp->api.ovm.module_id);
|
||||
}
|
||||
if (result.heartbeat.has_value()) {
|
||||
result.heartbeat->cb = result.cb_name;
|
||||
result.heartbeat->cb_remote = ip;
|
||||
}
|
||||
if (result.gpio.has_value()) {
|
||||
result.gpio->cb = result.cb_name;
|
||||
result.gpio->cb_remote = ip;
|
||||
}
|
||||
|
||||
if (result.heartbeat.has_value()) {
|
||||
auto& raw = result.heartbeat->cb_config.network.mdns_name;
|
||||
std::string item = raw;
|
||||
replace(item);
|
||||
auto limit = sizeof(raw);
|
||||
if (item.size() > limit) {
|
||||
item = "cb_" + index_str;
|
||||
std::cout << "WARNING: Replacement for mdns_name is too long. Fallback to '" + item + "'" << std::endl;
|
||||
}
|
||||
std::memset(raw, 0, limit);
|
||||
std::memcpy(raw, item.c_str(), std::min(item.size(), limit));
|
||||
|
||||
result.heartbeat->cb_remote = ip;
|
||||
result.heartbeat->cb = result.cb_name;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<charge_bridge_config> parse_config_multi(std::string const& config_file) {
|
||||
const static RymlCallbackInitializer ryml_callback_initializer;
|
||||
|
||||
try {
|
||||
ryml::EventHandlerTree evt_handler = {};
|
||||
ryml::Parser parser(&evt_handler, ryml::ParserOptions().locations(true));
|
||||
ryml::Tree config_tree;
|
||||
load_yaml_file(config_file, &parser, &config_tree);
|
||||
c4::yml::NodeRef config = config_tree.rootref();
|
||||
if (config.invalid()) {
|
||||
std::cerr << "Config file not found: " << config_file << std::endl;
|
||||
return {};
|
||||
}
|
||||
charge_bridge_config base_config;
|
||||
parse_config_impl(config, base_config, config_file, parser);
|
||||
|
||||
auto ip_list_node = config.find_child("charge_bridge_ip_list");
|
||||
if (ip_list_node.invalid()) {
|
||||
return {base_config};
|
||||
}
|
||||
std::vector<std::string> ip_list;
|
||||
ip_list_node >> ip_list;
|
||||
std::vector<charge_bridge_config> cb_config_list(ip_list.size());
|
||||
|
||||
for (std::size_t i = 0; i < ip_list.size(); ++i) {
|
||||
set_config_placeholders(base_config, cb_config_list[i], ip_list[i], i);
|
||||
}
|
||||
|
||||
return cb_config_list;
|
||||
} catch (...) {
|
||||
std::cerr << "FAILED to parse configuration!" << std::endl;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,97 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#include "protocol/cb_config.h"
|
||||
#include <charge_bridge/charge_bridge.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
|
||||
std::string to_string(CbCanBaudrate value) {
|
||||
switch (value) {
|
||||
case CBCBR_125000:
|
||||
return "125000";
|
||||
case CBCBR_250000:
|
||||
return "250000";
|
||||
case CBCBR_500000:
|
||||
return "500000";
|
||||
case CBCBR_1000000:
|
||||
return "1000000";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Invalid bitrate";
|
||||
}
|
||||
|
||||
std::string to_string(CbUartBaudrate value) {
|
||||
switch (value) {
|
||||
case CBUBR_9600:
|
||||
return "9600";
|
||||
case CBUBR_19200:
|
||||
return "19200";
|
||||
case CBUBR_38400:
|
||||
return "38400";
|
||||
case CBUBR_57600:
|
||||
return "57600";
|
||||
case CBUBR_115200:
|
||||
return "115200";
|
||||
case CBUBR_230400:
|
||||
return "230400";
|
||||
case CBUBR_250000:
|
||||
return "250000";
|
||||
case CBUBR_460800:
|
||||
return "460800";
|
||||
case CBUBR_500000:
|
||||
return "500000";
|
||||
case CBUBR_1000000:
|
||||
return "1000000";
|
||||
case CBUBR_2000000:
|
||||
return "2000000";
|
||||
case CBUBR_3000000:
|
||||
return "3000000";
|
||||
case CBUBR_4000000:
|
||||
return "4000000";
|
||||
case CBUBR_6000000:
|
||||
return "6000000";
|
||||
case CBUBR_8000000:
|
||||
return "8000000";
|
||||
case CBUBR_10000000:
|
||||
return "10000000";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Invalid baudrate";
|
||||
}
|
||||
|
||||
std::string to_string(CbUartParity value) {
|
||||
switch (value) {
|
||||
case CBUP_None:
|
||||
return "N";
|
||||
case CBUP_Odd:
|
||||
return "O";
|
||||
case CBUP_Even:
|
||||
return "E";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Invalid parity";
|
||||
}
|
||||
|
||||
std::string to_string(CbUartStopbits value) {
|
||||
switch (value) {
|
||||
case CBUS_OneStopBit:
|
||||
return "1";
|
||||
case CBUS_TwoStopBits:
|
||||
return "2";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Invalid parity";
|
||||
}
|
||||
|
||||
std::string to_string(CbUartConfig const& value) {
|
||||
std::stringstream data;
|
||||
data << to_string(value.baudrate) << " 8" << to_string(value.parity) << to_string(value.stopbits);
|
||||
return data.str();
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#include <charge_bridge/utilities/string.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace charge_bridge::utilities {
|
||||
bool string_starts_with(std::string_view const& str, std::string_view const& pattern) {
|
||||
return str.rfind(pattern, 0) == 0;
|
||||
}
|
||||
|
||||
bool string_ends_with(std::string const& str, std::string const& pattern) {
|
||||
if (pattern.size() > str.size())
|
||||
return false;
|
||||
return std::equal(pattern.rbegin(), pattern.rend(), str.rbegin());
|
||||
}
|
||||
|
||||
std::string string_after_pattern(std::string_view const& str, std::string_view const& pattern) {
|
||||
if (charge_bridge::utilities::string_starts_with(str, pattern)) {
|
||||
return static_cast<std::string>(str.substr(pattern.length()));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string& replace_all_in_place(std::string& source, std::string const& placeholder, std::string const& substitute) {
|
||||
|
||||
if (placeholder.empty()) {
|
||||
return source;
|
||||
}
|
||||
|
||||
std::size_t start_pos = 0;
|
||||
|
||||
while ((start_pos = source.find(placeholder, start_pos)) != std::string::npos) {
|
||||
source.replace(start_pos, placeholder.length(), substitute);
|
||||
start_pos += substitute.length();
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string replace_all(std::string const& source, std::string const& placeholder, std::string const& substitute) {
|
||||
std::string result = source;
|
||||
return replace_all_in_place(result, placeholder, substitute);
|
||||
}
|
||||
|
||||
std::set<std::string> csv_to_set(std::string const& str) {
|
||||
std::set<std::string> result;
|
||||
std::stringstream ss(str);
|
||||
std::string item;
|
||||
|
||||
while (std::getline(ss, item, ',')) {
|
||||
if (!item.empty()) {
|
||||
result.insert(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace charge_bridge::utilities
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user