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:
42
tools/openocpp/.gitignore
vendored
Normal file
42
tools/openocpp/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# CLion
|
||||
.idea/
|
||||
|
||||
# CMakeLists
|
||||
cmake-build*/
|
||||
build/
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Other
|
||||
metrixpp.db
|
||||
201
tools/openocpp/LICENSE
Normal file
201
tools/openocpp/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.
|
||||
149
tools/openocpp/README.md
Normal file
149
tools/openocpp/README.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# OpenOCPP
|
||||
|
||||
Multi-platform OCPP 1.6/2.0.1 embedded software for charging stations.
|
||||
|
||||
Website: [https://openocpp.com](https://openocpp.com)
|
||||
|
||||
## Certifications
|
||||
This project has been tested and passed core test cases as part of the OCTT 2.0.1 Vendor Declaration of Conformance:
|
||||

|
||||
|
||||
For more information on specific capabilities please contact support@openocpp.com
|
||||
|
||||
## ESP32 Demo
|
||||
### Getting started
|
||||
The ESP32 demo in this project depends on the **ESP-IDF** toolchain version **v5.2.3**. Setup instructions can be found
|
||||
here:
|
||||
|
||||
- https://docs.espressif.com/projects/esp-idf/en/v5.2.5/esp32/get-started/index.html
|
||||
|
||||
An example setup process on an **Ubuntu VM** is provided below:
|
||||
|
||||
```shell
|
||||
# Install Prerequisites
|
||||
sudo apt-get install git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
|
||||
|
||||
# Get ESP-IDF
|
||||
mkdir -p ~/esp
|
||||
cd ~/esp
|
||||
git clone -b v5.2.5 --recursive https://github.com/espressif/esp-idf.git
|
||||
|
||||
# Set up the Tools
|
||||
cd ~/esp/esp-idf
|
||||
./install.sh esp32
|
||||
|
||||
# Activate ESP-IDF
|
||||
. $HOME/esp/esp-idf/export.sh
|
||||
```
|
||||
|
||||
Note: the activation step above must be run every time a new terminal is opened before running any of the `idf.py`
|
||||
commands.
|
||||
|
||||
### Building the project
|
||||
|
||||
Open a terminal in the OpenOCPP project directory and run the following commands:
|
||||
|
||||
```shell
|
||||
# Navigate to the demo-esp32 folder (if necessary)
|
||||
cd demo-esp32
|
||||
|
||||
# Active ESP-IDF (if necessary)
|
||||
. $HOME/esp/esp-idf/export.sh
|
||||
|
||||
# Build the project - note: this step will take a few minutes
|
||||
idf.py build
|
||||
```
|
||||
|
||||
### Running the project
|
||||
|
||||
From a terminal in the OpenOCPP project run the following commands:
|
||||
|
||||
```shell
|
||||
# Navigate to the demo-esp32 folder (if necessary)
|
||||
cd demo-esp32
|
||||
|
||||
# Active ESP-IDF (if necessary)
|
||||
. $HOME/esp/esp-idf/export.sh
|
||||
|
||||
# Flash and run the project
|
||||
idf.py -p /dev/ttyUSB0 flash && idf.py -p /dev/ttyUSB0 monitor
|
||||
```
|
||||
|
||||
Note that the above assumes that the ESP32 module/dev kit is connected to the `/dev/ttyUSB0` interface in the system/VM.
|
||||
|
||||
_For example:_ an ESP32 dev kit plugged into the host machine can generally be exposed to a KVM VM as follows:
|
||||
|
||||
- Press: Add Hardware
|
||||
- Select: USB Host Device
|
||||
- Select: Silicon Labs CP210x UART Bridge
|
||||
- Press: Finish
|
||||
|
||||
Assuming the firmware is flashed successfully onto the ESP32 module the `monitor` command will restart the firmware and
|
||||
begin trailing the log messages. This should look something like the following:
|
||||
|
||||
```text
|
||||
I (31) boot: ESP-IDF v5.2.3 2nd stage bootloader
|
||||
I (31) boot: compile time Feb 24 2025 10:14:06
|
||||
I (31) boot: Multicore bootloader
|
||||
I (35) boot: chip revision: v3.0
|
||||
I (39) boot.esp32: SPI Speed : 80MHz
|
||||
I (44) boot.esp32: SPI Mode : DIO
|
||||
I (48) boot.esp32: SPI Flash Size : 4MB
|
||||
I (53) boot: Enabling RNG early entropy source...
|
||||
I (58) boot: Partition Table:
|
||||
I (62) boot: ## Label Usage Type ST Offset Length
|
||||
I (69) boot: 0 nvs WiFi data 01 02 00009000 00003000
|
||||
I (77) boot: 1 pmjournal Unknown data 01 06 0000c000 00002000
|
||||
I (84) boot: 2 otadata OTA data 01 00 0000e000 00002000
|
||||
I (92) boot: 3 app1 OTA app 00 11 00010000 001e0000
|
||||
I (99) boot: 4 app0 OTA app 00 10 001f0000 001e0000
|
||||
I (106) boot: 5 spiffs Unknown data 01 82 003d0000 0002f000
|
||||
I (114) boot: 6 sernr NVS keys 01 04 003ff000 00001000
|
||||
I (122) boot: End of partition table
|
||||
...
|
||||
```
|
||||
|
||||
### Provisioning to the simulator
|
||||
|
||||
When the firmware finishes initializing it begins broadcasting an open wifi access point for provisioning purposes named
|
||||
**Charger Simulator**. Connect to the access point and perform the following steps to connect the device to an OCPP
|
||||
back-end:
|
||||
|
||||
- Open https://192.168.4.1 in your browser
|
||||
- Note that both an HTTP and HTTPS server (with a self-signed certificate) is run for the purpose of the demo
|
||||
- Accept the self-signed certificate
|
||||
- Submit the provisioning information:
|
||||
- Select the Wifi network (may take a moment to load)
|
||||
- Enter/confirm the Wifi password
|
||||
- Enter the OCPP network URL - for example:
|
||||
- ws://0123456789abcdef.octt.openchargealliance.org:25316
|
||||
- wss://my-ocpp-backend.com
|
||||
- Enter the OCPP ID to use
|
||||
- The value here is appended to the network URL, for example with an OCPP ID of TEST the simulator will connect to
|
||||
the following URLs in the examples above:
|
||||
- ws://0123456789abcdef.octt.openchargealliance.org:25316/TEST
|
||||
- wss://my-ocpp-backend.com/TEST
|
||||
- Select the OCPP protocol to use
|
||||
- Press: Submit
|
||||
|
||||
After the above is completed the simulator will restart and connect to the provided OCPP back-end.
|
||||
|
||||
### Connecting to the running simulator
|
||||
|
||||
The running simulator can be controlled either by connecting to the simulator's IP address on the Wifi network it's
|
||||
connected to, or by re-connecting to the **Charger Simulator** Wifi network. Note that the **Charger Simulator** network
|
||||
will require the same password used to connect to the Wifi network after provisioning is complete.
|
||||
|
||||
When connected to the simulator the state of the device can be controlled via a web interface:
|
||||
|
||||
- Connect to the simulator
|
||||
- Connect to the simulator's IP address on the local Wifi network: https://192.168.1.X
|
||||
- Connect directly to the simulator's access point:
|
||||
- Open https://192.168.4.1 in your browser (if your local network does not use the 192.168 prefix), otherwise
|
||||
- Open https://10.0.0.1 in your browser (if your local network does use the 192.168 prefix)
|
||||
- Select the **Configuration** tab to alter the charger's configuration, such as:
|
||||
- Altering the default networking profile slot/configuration
|
||||
- Select the **Control** tab to control the simulator, such as:
|
||||
- Simulate connecting/disconnecting a vehicle
|
||||
- Simulate charging suspended by EV/EVSE
|
||||
- Simulate an RFID or other local authorization
|
||||
3
tools/openocpp/demo-esp32/.gitignore
vendored
Normal file
3
tools/openocpp/demo-esp32/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build/
|
||||
sdkconfig
|
||||
managed_components/
|
||||
11
tools/openocpp/demo-esp32/CMakeLists.txt
Normal file
11
tools/openocpp/demo-esp32/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if (ESP_PLATFORM)
|
||||
add_compile_definitions(JSON_NO_IO)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(TestFirmware LANGUAGES C CXX)
|
||||
|
||||
include_directories($ENV{IDF_PATH}/components/freertos/include/esp_additions/freertos/)
|
||||
endif()
|
||||
32
tools/openocpp/demo-esp32/dependencies.lock
Normal file
32
tools/openocpp/demo-esp32/dependencies.lock
Normal file
@@ -0,0 +1,32 @@
|
||||
dependencies:
|
||||
espressif/esp_websocket_client:
|
||||
component_hash: f77326f0e1c38da4e9c97e17c5d649b0dd13027f2645e720e48db269638fd622
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.4.0
|
||||
espressif/zlib:
|
||||
component_hash: d901723af51f13fc8e5824f39f32239c847956e8fd951a05266588dc5cfbb9ae
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.3.1
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.2.5
|
||||
direct_dependencies:
|
||||
- espressif/esp_websocket_client
|
||||
- espressif/zlib
|
||||
- idf
|
||||
manifest_hash: 115b87000fdb1fc149cf270df267a604f2c5a46b6d9a0c7b042451668945afa3
|
||||
target: esp32
|
||||
version: 2.0.0
|
||||
48
tools/openocpp/demo-esp32/main/CMakeLists.txt
Normal file
48
tools/openocpp/demo-esp32/main/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
# Custom function to gzip files
|
||||
function(gzip_files)
|
||||
file(GLOB WEB_FILES "${CMAKE_CURRENT_SOURCE_DIR}/web/*.html" "${CMAKE_CURRENT_SOURCE_DIR}/web/*.ico")
|
||||
|
||||
foreach(FILE ${WEB_FILES})
|
||||
get_filename_component(FILENAME ${FILE} NAME)
|
||||
set(GZIPPED_FILE "${CMAKE_CURRENT_SOURCE_DIR}/web/${FILENAME}.gz")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${GZIPPED_FILE}
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Gzipping ${FILENAME}"
|
||||
COMMAND gzip -c ${FILE} > ${GZIPPED_FILE}
|
||||
DEPENDS ${FILE}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
list(APPEND GZIPPED_FILES ${GZIPPED_FILE})
|
||||
endforeach()
|
||||
|
||||
set(GZIPPED_FILES ${GZIPPED_FILES} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Call the function to generate gzipped files
|
||||
gzip_files()
|
||||
|
||||
list(APPEND SOURCES "main.cc")
|
||||
list(APPEND SOURCES "../../include/openocpp/implementation/logging_esp.cc")
|
||||
|
||||
idf_component_register(
|
||||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES app_update esp_websocket_client esp_wifi nvs_flash spiffs driver esp_eth esp_https_server esp_http_client
|
||||
EMBED_TXTFILES "certs/servercert.pem" "certs/prvtkey.pem" "certs/manufacturer.pem"
|
||||
EMBED_FILES
|
||||
"web/web_favicon.ico.gz"
|
||||
"web/web_index.html.gz"
|
||||
"web/web_setup.html.gz"
|
||||
"web/web_config.html.gz"
|
||||
"web/web_control.html.gz"
|
||||
)
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE -mtext-section-literals)
|
||||
|
||||
# add_compile_definitions(LOG_WITH_FILE_AND_LINE)
|
||||
add_compile_definitions(JSON_NO_IO)
|
||||
|
||||
include_directories(../../include)
|
||||
include_directories(../../rapidjson/include)
|
||||
include_directories(${CMAKE_BINARY_DIR})
|
||||
22
tools/openocpp/demo-esp32/main/certs/manufacturer.pem
Normal file
22
tools/openocpp/demo-esp32/main/certs/manufacturer.pem
Normal file
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrzCCApegAwIBAgIUEgpLccj+t8/xH452M0XijiShXkQwDQYJKoZIhvcNAQEL
|
||||
BQAwZzELMAkGA1UEBhMCQ0ExEDAOBgNVBAgMB09udGFyaW8xEDAOBgNVBAcMB1Rv
|
||||
cm9udG8xEjAQBgNVBAoMCUNoYXJnZUxhYjEgMB4GA1UEAwwXVGVzdEZpcm13YXJl
|
||||
U2lnbmluZ0NlcnQwHhcNMjUwMjE0MTYwMjM1WhcNMzUwMjEyMTYwMjM1WjBnMQsw
|
||||
CQYDVQQGEwJDQTEQMA4GA1UECAwHT250YXJpbzEQMA4GA1UEBwwHVG9yb250bzES
|
||||
MBAGA1UECgwJQ2hhcmdlTGFiMSAwHgYDVQQDDBdUZXN0RmlybXdhcmVTaWduaW5n
|
||||
Q2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJi2dU3Jba0V2X9o
|
||||
JMcep3L+2ZqNiKt7fgM9Ey71Ih06wFdAA+hflIwlUJm8WeBTd2HhdezfRrWlhyrE
|
||||
Dcm57BQfUNuI42HtYMhrpgkUtdBfL/DHKJKUCBIpgLDPYPSOEXo8tVxEKZSDiy9Z
|
||||
eTQqHuCUc5pC4nWzWnvRnogOas+evn3RnCoEdo8mIdyDXiicHpuJbqoNa7EEs/VC
|
||||
4NBIj9ErFTmwjJwmbPoKsodjRQHYy36x4rZNlZzyxeWj3wTw5umxMDWFaobWlJ+5
|
||||
ZT5Us+eEnXdfITGHnutKIRe7sm2zw+pObpP7EShuZpnhYsTjCOtDHkhSgOww1UrY
|
||||
QAHVmJMCAwEAAaNTMFEwHQYDVR0OBBYEFC5enSgIvaycENnmHgPnv5fjX/QlMB8G
|
||||
A1UdIwQYMBaAFC5enSgIvaycENnmHgPnv5fjX/QlMA8GA1UdEwEB/wQFMAMBAf8w
|
||||
DQYJKoZIhvcNAQELBQADggEBACLB/MEN9gxfaaut52sFQx1fwdsDmyMm2+j3Sjng
|
||||
lEoeRYxvP4zCNXgV49ZBlrWLP/jf5Hl7IbBBEOfk45b+D2O+cbdXREs9xwT+MV+5
|
||||
atAEvyieRj+7bdqmIrdFnSBETdH7dgy48P0K2ORwdZaFHpn+Ihs13hGXQJGAgBUk
|
||||
1W9zZSblLvOe9NHHIzhyeDY6oS0EKkr0Qhx1sTV/qSsmVr/lBG521NfmsZX5qm/u
|
||||
OgAC/Pj96Hp9R50PwZUvDMcEzx65RjiNZ/+IPgk7Vd3cYNHyzDDElRqVFAgtkJjJ
|
||||
Y+xlHj2La6hL9TnGOkA9DtQdyhjET6qfIh7d5wLsDVk3ilI=
|
||||
-----END CERTIFICATE-----
|
||||
28
tools/openocpp/demo-esp32/main/certs/prvtkey.pem
Normal file
28
tools/openocpp/demo-esp32/main/certs/prvtkey.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH
|
||||
JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw
|
||||
h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT
|
||||
aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al
|
||||
3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg
|
||||
0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB
|
||||
vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui
|
||||
f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9
|
||||
Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y
|
||||
JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX
|
||||
49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc
|
||||
+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6
|
||||
pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D
|
||||
0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG
|
||||
YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV
|
||||
MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL
|
||||
CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin
|
||||
7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1
|
||||
noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8
|
||||
4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g
|
||||
Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/
|
||||
nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3
|
||||
q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2
|
||||
lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB
|
||||
jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr
|
||||
v/t+MeGJP/0Zw8v/X2CFll96
|
||||
-----END PRIVATE KEY-----
|
||||
19
tools/openocpp/demo-esp32/main/certs/servercert.pem
Normal file
19
tools/openocpp/demo-esp32/main/certs/servercert.pem
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL
|
||||
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx
|
||||
MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ
|
||||
UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T
|
||||
sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k
|
||||
qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd
|
||||
GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4
|
||||
sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb
|
||||
jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/
|
||||
ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3
|
||||
emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY
|
||||
W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx
|
||||
bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN
|
||||
ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl
|
||||
hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo=
|
||||
-----END CERTIFICATE-----
|
||||
18
tools/openocpp/demo-esp32/main/idf_component.yml
Normal file
18
tools/openocpp/demo-esp32/main/idf_component.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/zlib: "^1.3.0"
|
||||
espressif/esp_websocket_client: "^1.3.0"
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: ">=4.1.0"
|
||||
# # Put list of dependencies here
|
||||
# # For components maintained by Espressif:
|
||||
# component: "~1.0.0"
|
||||
# # For 3rd party components:
|
||||
# username/component: ">=1.0.0,<2.0.0"
|
||||
# username2/component2:
|
||||
# version: "~1.0.0"
|
||||
# # For transient dependencies `public` flag can be set.
|
||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
||||
# # All dependencies of `main` are public by default.
|
||||
# public: true
|
||||
87
tools/openocpp/demo-esp32/main/main.cc
Normal file
87
tools/openocpp/demo-esp32/main/main.cc
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "portal_demo.h"
|
||||
#include "openocpp/implementation/platform_esp.h"
|
||||
#include "openocpp/implementation/standard_charger.h"
|
||||
#include "openocpp/implementation/station_test_esp32.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
chargelab::logging::SetLogLevel(chargelab::logging::LogLevel::debug);
|
||||
|
||||
auto firstBootAfterFactoryReset = std::make_shared<chargelab::SettingBool> (
|
||||
[]() {
|
||||
return chargelab::SettingMetadata {
|
||||
"FirstBootAfterFactoryReset",
|
||||
chargelab::SettingConfig::rwPolicy(),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
chargelab::SettingBool::kTextTrue
|
||||
};
|
||||
},
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
|
||||
auto platform = std::make_shared<chargelab::PlatformESP>();
|
||||
auto settings = platform->getSettings();
|
||||
settings->registerCustomSetting(firstBootAfterFactoryReset);
|
||||
|
||||
if (firstBootAfterFactoryReset->getValue()) {
|
||||
// Clear certificates and install default certificates
|
||||
platform->removeCertificatesIf([](auto const&) {return true;});
|
||||
|
||||
extern const unsigned char _binary_manufacturer_pem_start[] asm("_binary_manufacturer_pem_start");
|
||||
extern const unsigned char _binary_manufacturer_pem_end[] asm("_binary_manufacturer_pem_end");
|
||||
std::string pem {_binary_manufacturer_pem_start, _binary_manufacturer_pem_end};
|
||||
if (!platform->addCertificate(pem, chargelab::ocpp2_0::GetCertificateIdUseEnumType::kManufacturerRootCertificate)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed adding default manufacturer root CA: " << pem;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Added default manufacturer root CA: " << pem;
|
||||
}
|
||||
|
||||
firstBootAfterFactoryReset->setValue(false);
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Current setting";
|
||||
settings->visitSettings([](auto const& x) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << x.getId() << ": " << x.getValueAsString();
|
||||
});
|
||||
|
||||
auto station_test = std::make_shared<chargelab::StationTestEsp32>(platform);
|
||||
auto charger = std::make_unique<chargelab::StandardCharger>(platform, station_test);
|
||||
charger->addAfter(std::make_shared<chargelab::PortalDemo>(platform, charger->reset_module,
|
||||
charger->connector_status_module,
|
||||
charger->configuration_module,
|
||||
station_test));
|
||||
|
||||
std::optional<chargelab::SteadyPointMillis> held_since;
|
||||
while (true) {
|
||||
station_test->updateMeasurements();
|
||||
charger->runStep();
|
||||
|
||||
auto const now = platform->steadyClockNow();
|
||||
if (gpio_get_level(GPIO_NUM_0) == 0) {
|
||||
if (!held_since.has_value())
|
||||
held_since = platform->steadyClockNow();
|
||||
|
||||
if (now - held_since.value() > 5000) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Factory reset requested - clearing all saved state";
|
||||
for (auto const& name : std::vector<std::string> {"pmjournal", "spiffs"}) {
|
||||
auto partition = platform->getPartition(name);
|
||||
if (partition != nullptr) {
|
||||
if (!partition->erase()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed erasing partition: " << name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
platform->resetHard();
|
||||
}
|
||||
} else {
|
||||
held_since = std::nullopt;
|
||||
}
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
}
|
||||
725
tools/openocpp/demo-esp32/main/portal_demo.h
Normal file
725
tools/openocpp/demo-esp32/main/portal_demo.h
Normal file
@@ -0,0 +1,725 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_PORTAL_DEMO_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_PORTAL_DEMO_H
|
||||
|
||||
#include "openocpp/module/reset_module.h"
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/implementation/platform_esp.h"
|
||||
#include "openocpp/implementation/station_test_esp32.h"
|
||||
#include "openocpp/module/connector_status_module.h"
|
||||
#include "openocpp/module/configuration_module.h"
|
||||
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
|
||||
#include "esp_https_server.h"
|
||||
#include "esp_tls.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include "esp_http_client.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_configuration.h"
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
struct DeviceConfigInfo {
|
||||
std::string centralSystemUrl;
|
||||
std::string chargePointId;
|
||||
std::string ocppProtocol; // OCPP16 or OCPP20
|
||||
int securityProfile;
|
||||
bool clearCertificates;
|
||||
std::string serialNumber;
|
||||
std::string maxCurrentAmps;
|
||||
std::string vendor;
|
||||
std::string model;
|
||||
int numberOfConnectors;
|
||||
std::string firmwareVersion;
|
||||
|
||||
CHARGELAB_JSON_INTRUSIVE(
|
||||
DeviceConfigInfo,
|
||||
centralSystemUrl,
|
||||
chargePointId,
|
||||
ocppProtocol,
|
||||
securityProfile,
|
||||
clearCertificates,
|
||||
serialNumber,
|
||||
maxCurrentAmps,
|
||||
vendor,
|
||||
model,
|
||||
numberOfConnectors,
|
||||
firmwareVersion
|
||||
)
|
||||
};
|
||||
|
||||
struct ConnectorSettings {
|
||||
std::optional<int> connectorId;
|
||||
std::optional<bool> vehicleConnected;
|
||||
std::optional<bool> suspendedByVehicle;
|
||||
std::optional<bool> suspendedByCharger;
|
||||
std::optional<bool> allowWebsocketConnection;
|
||||
CHARGELAB_JSON_INTRUSIVE(ConnectorSettings, connectorId, vehicleConnected, suspendedByVehicle, suspendedByCharger, allowWebsocketConnection)
|
||||
};
|
||||
|
||||
struct ConnectorStatusInfo {
|
||||
chargelab::charger::ConnectorStatus connectorStatus;
|
||||
std::vector<ocpp1_6::SampledValue> meterValues1_6;
|
||||
std::vector<ocpp2_0::SampledValueType> meterValues2_0;
|
||||
ConnectorSettings connectorSettings;
|
||||
ocpp1_6::ChargePointStatus chargePointStatus1_6;
|
||||
CHARGELAB_JSON_INTRUSIVE(ConnectorStatusInfo, connectorStatus, meterValues1_6, meterValues2_0, connectorSettings, chargePointStatus1_6)
|
||||
};
|
||||
|
||||
struct ChargerInfo {
|
||||
std::string serialNumber;
|
||||
std::string firmwareVersion;
|
||||
CHARGELAB_JSON_INTRUSIVE(ChargerInfo, serialNumber, firmwareVersion);
|
||||
};
|
||||
|
||||
struct ChargerInfoResponse {
|
||||
ChargerInfo info;
|
||||
CHARGELAB_JSON_INTRUSIVE(ChargerInfoResponse, info);
|
||||
};
|
||||
|
||||
struct SimulateRfidInteraction {
|
||||
std::string idToken;
|
||||
ocpp2_0::IdTokenEnumType tokenType;
|
||||
CHARGELAB_JSON_INTRUSIVE(SimulateRfidInteraction, idToken, tokenType);
|
||||
};
|
||||
|
||||
struct WifiNetwork {
|
||||
std::string ssid;
|
||||
std::string rssi;
|
||||
std::string encryption;
|
||||
CHARGELAB_JSON_INTRUSIVE(WifiNetwork, ssid, rssi, encryption);
|
||||
};
|
||||
|
||||
struct SetupPayload {
|
||||
std::string ssid;
|
||||
std::string password;
|
||||
std::string networkUrl;
|
||||
std::string ocppId;
|
||||
ocpp2_0::OCPPVersionEnumType ocppProtocol;
|
||||
CHARGELAB_JSON_INTRUSIVE(SetupPayload, ssid, password, networkUrl, ocppId, ocppProtocol);
|
||||
};
|
||||
|
||||
struct OcppParametersInfo {
|
||||
ocpp1_6::GetConfigurationRsp rsp;
|
||||
CHARGELAB_JSON_INTRUSIVE(OcppParametersInfo, rsp)
|
||||
};
|
||||
|
||||
struct OcppParameterSetting {
|
||||
std::string key;
|
||||
std::string value;
|
||||
CHARGELAB_JSON_INTRUSIVE(OcppParameterSetting, key, value)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notes:
|
||||
* Increasing the HTTPD_MAX_REQ_HDR_LEN from 512 (default) to 1024 is recommended. The limit was hit on Chrome
|
||||
* browsers.
|
||||
*/
|
||||
class PortalDemo : public PureService {
|
||||
private:
|
||||
static constexpr int kStartupDelayMillis = 10*1000;// 500;
|
||||
static constexpr int kMaxPostContentLengthBytes = 5*1024;
|
||||
|
||||
public:
|
||||
PortalDemo(
|
||||
std::shared_ptr<PlatformESP> platform,
|
||||
std::shared_ptr<ResetModule> reset,
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_module,
|
||||
std::shared_ptr<ConfigurationModule> configuration_module,
|
||||
std::shared_ptr<StationTestEsp32> station
|
||||
)
|
||||
: platform_(std::move(platform)),
|
||||
reset_(std::move(reset)),
|
||||
connector_status_module_(std::move(connector_status_module)),
|
||||
configuration_module_(std::move(configuration_module)),
|
||||
station_(std::move(station))
|
||||
|
||||
{
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Setting up portal";
|
||||
settings_ = platform_->getSettings();
|
||||
|
||||
for (auto const& method : std::vector<httpd_method_t>{HTTP_GET, HTTP_POST}) {
|
||||
handlers_.resize(handlers_.size()+1);
|
||||
auto& x = handlers_.back();
|
||||
x.uri = "*";
|
||||
x.method = method;
|
||||
x.handler = onRequest;
|
||||
x.user_ctx = (void *)this;
|
||||
}
|
||||
}
|
||||
|
||||
~PortalDemo() {
|
||||
stopServers();
|
||||
}
|
||||
|
||||
private:
|
||||
void runUnconditionally() override {
|
||||
if (!initialized_) {
|
||||
if (!start_ts_.has_value())
|
||||
start_ts_ = platform_->steadyClockNow();
|
||||
|
||||
auto const elapsed = platform_->steadyClockNow() - start_ts_.value();
|
||||
if (elapsed < kStartupDelayMillis)
|
||||
return;
|
||||
|
||||
startServerHttps();
|
||||
startServerHttp();
|
||||
initialized_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void startServerHttps() {
|
||||
extern const unsigned char _binary_servercert_pem_start[] asm("_binary_servercert_pem_start");
|
||||
extern const unsigned char _binary_servercert_pem_end[] asm("_binary_servercert_pem_end");
|
||||
extern const unsigned char _binary_prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
|
||||
extern const unsigned char _binary_prvtkey_pem_end[] asm("_binary_prvtkey_pem_end");
|
||||
|
||||
if (server_https_ != nullptr)
|
||||
return;
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Starting HTTPS server...";
|
||||
httpd_ssl_config_t config = HTTPD_SSL_CONFIG_DEFAULT();
|
||||
{
|
||||
config.servercert = _binary_servercert_pem_start;
|
||||
config.servercert_len = _binary_servercert_pem_end - _binary_servercert_pem_start;
|
||||
}
|
||||
{
|
||||
config.prvtkey_pem = _binary_prvtkey_pem_start;
|
||||
config.prvtkey_len = _binary_prvtkey_pem_end - _binary_prvtkey_pem_start;
|
||||
}
|
||||
|
||||
config.httpd.uri_match_fn = httpd_uri_match_wildcard;
|
||||
config.httpd.lru_purge_enable = true;
|
||||
config.httpd.stack_size += 1024;
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Using stack size: " << config.httpd.stack_size;
|
||||
ESP_ERROR_CHECK(httpd_ssl_start(&server_https_, &config));
|
||||
for (auto& x : handlers_)
|
||||
httpd_register_uri_handler(server_https_, &x);
|
||||
}
|
||||
|
||||
void startServerHttp() {
|
||||
if (server_http_ != nullptr)
|
||||
return;
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Starting HTTP server...";
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||
config.lru_purge_enable = true;
|
||||
config.stack_size += 1024;
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Using stack size: " << config.stack_size;
|
||||
|
||||
ESP_ERROR_CHECK(httpd_start(&server_http_, &config));
|
||||
for (auto& x : handlers_)
|
||||
httpd_register_uri_handler(server_http_, &x);
|
||||
}
|
||||
|
||||
void stopServers() {
|
||||
if (server_http_ != nullptr) {
|
||||
ESP_ERROR_CHECK(httpd_stop(server_http_));
|
||||
server_http_ = nullptr;
|
||||
}
|
||||
if (server_https_ != nullptr) {
|
||||
ESP_ERROR_CHECK(httpd_stop(server_https_));
|
||||
server_https_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> readQueryString(httpd_req_t *req) {
|
||||
std::string query(512, '\0');
|
||||
if (httpd_req_get_url_query_str(req, query.data(), query.size()) != ESP_OK)
|
||||
return std::nullopt;
|
||||
|
||||
query.resize(strlen(query.c_str()));
|
||||
return query;
|
||||
}
|
||||
|
||||
std::optional<std::string> readQueryParameter(std::optional<std::string> const& query, std::string const& key) {
|
||||
if (!query.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
std::string value(query->size(), '\0');
|
||||
if (httpd_query_key_value(query->data(), key.c_str(), value.data(), value.size()) != ESP_OK)
|
||||
return std::nullopt;
|
||||
|
||||
value.resize(strlen(value.c_str()));
|
||||
return uri::decodeUriComponent(value);
|
||||
}
|
||||
|
||||
void onGet(httpd_req_t *req) {
|
||||
// Note: as extern variables the uniqueness of the name/combination is important. These can't be moved into
|
||||
// local variable definitions within the subsequent if blocks with shared names (start/end for
|
||||
// example); leaving these are verbose explicit names here to limit the risk of conflicting
|
||||
// definitions.
|
||||
extern const char _binary_web_favicon_ico_gz_start[] asm("_binary_web_favicon_ico_gz_start");
|
||||
extern const char _binary_web_favicon_ico_gz_end[] asm("_binary_web_favicon_ico_gz_end");
|
||||
extern const char _binary_web_index_html_gz_start[] asm("_binary_web_index_html_gz_start");
|
||||
extern const char _binary_web_index_html_gz_end[] asm("_binary_web_index_html_gz_end");
|
||||
extern const char _binary_web_setup_html_gz_start[] asm("_binary_web_setup_html_gz_start");
|
||||
extern const char _binary_web_setup_html_gz_end[] asm("_binary_web_setup_html_gz_end");
|
||||
extern const char _binary_web_config_html_gz_start[] asm("_binary_web_config_html_gz_start");
|
||||
extern const char _binary_web_config_html_gz_end[] asm("_binary_web_config_html_gz_end");
|
||||
extern const char _binary_web_control_html_gz_start[] asm("_binary_web_control_html_gz_start");
|
||||
extern const char _binary_web_control_html_gz_end[] asm("_binary_web_control_html_gz_end");
|
||||
|
||||
std::string const uri = req->uri;
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Requested URI was: " << uri;
|
||||
|
||||
if (uri == "" || uri == "/" || uri == "/index.html" || uri == "/home") {
|
||||
serveGzipHtml(req, _binary_web_index_html_gz_start, _binary_web_index_html_gz_end);
|
||||
} else if (uri == "/setup") {
|
||||
serveGzipHtml(req, _binary_web_setup_html_gz_start, _binary_web_setup_html_gz_end);
|
||||
} else if (uri == "/config") {
|
||||
serveGzipHtml(req, _binary_web_config_html_gz_start, _binary_web_config_html_gz_end);
|
||||
} else if (uri == "/control") {
|
||||
serveGzipHtml(req, _binary_web_control_html_gz_start, _binary_web_control_html_gz_end);
|
||||
} else if (uri == "/networks") {
|
||||
std::optional<std::vector<detail::WifiNetwork>> networks;
|
||||
for (int i=0; i < 10; i++) {
|
||||
networks = scanNetworks();
|
||||
if (networks.has_value())
|
||||
break;
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
|
||||
if (networks.has_value()) {
|
||||
auto const content = write_json_to_string(networks);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, content.data(), content.size());
|
||||
} else {
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, nullptr);
|
||||
}
|
||||
} else if (uri == "/req.html") {
|
||||
} else if (uri == "/chargerInfo") {
|
||||
detail::ChargerInfoResponse response = {
|
||||
detail::ChargerInfo {
|
||||
settings_->ChargerSerialNumber.getValue(),
|
||||
settings_->ChargerFirmwareVersion.getValue()
|
||||
}
|
||||
};
|
||||
|
||||
auto const text = write_json_to_string(response);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, text.data(), text.size());
|
||||
} else if (uri == "/configure") {
|
||||
} else if (uri == "/logs") {
|
||||
} else if (uri == "/getDeviceConfiguration") {
|
||||
detail::DeviceConfigInfo response {};
|
||||
response.securityProfile = settings_->SecurityProfile.getValue();
|
||||
response.clearCertificates = false;
|
||||
|
||||
auto const& slot = settings_->NetworkConnectionProfiles.getValue(0);
|
||||
if (slot.has_value())
|
||||
response.centralSystemUrl = slot->ocppCsmsUrl.value();
|
||||
|
||||
response.chargePointId = settings_->ChargePointId.getValue();
|
||||
response.ocppProtocol = settings_->OcppProtocol.getValue();
|
||||
response.serialNumber = settings_->ChargerSerialNumber.getValue();
|
||||
response.maxCurrentAmps = "40";
|
||||
response.vendor = settings_->ChargerVendor.getValue();
|
||||
response.model = settings_->ChargerModel.getValue();
|
||||
response.firmwareVersion = settings_->ChargerFirmwareVersion.getValue();
|
||||
response.numberOfConnectors = station_->getConnectorMetadata().size();
|
||||
|
||||
auto const text = write_json_to_string(response);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, text.data(), text.size());
|
||||
} else if (uri == "/logs") {
|
||||
} else if (uri.find("/getConnectorStatus") == 0) {
|
||||
auto const query = readQueryString(req);
|
||||
int connectorId = 1;
|
||||
{
|
||||
auto const text = readQueryParameter(query, "connectorId");
|
||||
if (text.has_value()) {
|
||||
auto const parsed = string::ToInteger(text.value());
|
||||
if (parsed.has_value())
|
||||
connectorId = parsed.value();
|
||||
}
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Connector ID was: " << connectorId;
|
||||
auto const evse = station_->lookupConnectorId1_6(connectorId);
|
||||
if (!evse.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Connector ID doesn't exist";
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const status = station_->pollConnectorStatus(evse.value());
|
||||
if (!status.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Connector status was empty";
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
detail::ConnectorSettings settings {};
|
||||
settings.vehicleConnected = status->vehicle_connected;
|
||||
settings.suspendedByCharger = status->suspended_by_charger;
|
||||
settings.suspendedByVehicle = status->suspended_by_vehicle;
|
||||
|
||||
auto status1_6_map = connector_status_module_->getChargePointStatus1_6();
|
||||
auto it = status1_6_map.find(1); // use default connector 1 for now
|
||||
|
||||
detail::ConnectorStatusInfo response {
|
||||
status.value(),
|
||||
station_->pollMeterValues1_6(evse.value()),
|
||||
station_->pollMeterValues2_0(evse.value()),
|
||||
settings,
|
||||
it != status1_6_map.end() ? it->second : ocpp1_6::ChargePointStatus{ocpp1_6::ChargePointStatus::kValueNotFoundInEnum}
|
||||
};
|
||||
|
||||
auto const text = write_json_to_string(response);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, text.data(), text.size());
|
||||
} else if (uri == "/getOcppParameters") { // ocpp 1.6 configuration parameter
|
||||
detail::OcppParametersInfo response {};
|
||||
|
||||
auto getConfigurationRsp = configuration_module_->getConfiguration(ocpp1_6::GetConfigurationReq{});
|
||||
if (getConfigurationRsp) {
|
||||
if (std::holds_alternative<ocpp1_6::GetConfigurationRsp>(getConfigurationRsp.value())) {
|
||||
response.rsp = std::move(std::get<ocpp1_6::GetConfigurationRsp>(std::move(getConfigurationRsp.value())));
|
||||
}
|
||||
}
|
||||
|
||||
auto const text = write_json_to_string(response);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, text.data(), text.size());
|
||||
} else if (uri == "/favicon.ico") {
|
||||
serveGzipContent(req, "image/vnd.microsoft.icon", _binary_web_favicon_ico_gz_start, _binary_web_favicon_ico_gz_end);
|
||||
} else {
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, "<h1>Not found</h1>", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
}
|
||||
|
||||
void onPost(httpd_req_t *req) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Received POST request on: " << req->uri;
|
||||
std::string payload;
|
||||
if (req->content_len > 0) {
|
||||
if (req->content_len > kMaxPostContentLengthBytes) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
payload.resize(req->content_len);
|
||||
auto ret = httpd_req_recv(req, payload.data(), payload.size());
|
||||
if (ret < 0 || ret != payload.size()) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
return;
|
||||
} else {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Payload was: " << payload;
|
||||
|
||||
std::string const uri = req->uri;
|
||||
if (uri == "/submitSetup") {
|
||||
onPostSetupSubmit(payload, req);
|
||||
} else if (uri == "/updateDeviceConfiguration") {
|
||||
onPostUpdateDeviceConfiguration(payload, req);
|
||||
} else if (uri == "/updateConnectorSettings") {
|
||||
onPostUpdateConnectorSettings(payload, req);
|
||||
} else if (uri == "/simulateRfidInteraction") {
|
||||
onPostSimulateRfidInteraction(payload, req);
|
||||
} else if (uri == "/updateOcppParameter") {
|
||||
onPostUpdateOcppParameter(payload, req);
|
||||
} else if (uri == "/reboot") {
|
||||
onPostReboot(payload, req);
|
||||
}
|
||||
else {
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, "<h1>Hello Secure World - Post!</h1>", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
}
|
||||
|
||||
void onPostSetupSubmit(std::string const& payload, httpd_req_t *req) {
|
||||
auto const parsed = read_json_from_string<detail::SetupPayload>(payload);
|
||||
if (!parsed.has_value()) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& settings = parsed.value();
|
||||
settings_->WifiSSID.forceValue(settings.ssid);
|
||||
settings_->WifiPassword.forceValue(settings.password);
|
||||
settings_->SecurityProfile.setValue(0);
|
||||
settings_->NetworkConnectionProfiles.forceValue(0, ocpp2_0::NetworkConnectionProfileType {
|
||||
// TODO: Need to set OCPP version from a field
|
||||
settings.ocppProtocol,
|
||||
ocpp2_0::OCPPTransportEnumType::kJSON,
|
||||
settings.networkUrl,
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
0,
|
||||
chargelab::ocpp2_0::OCPPInterfaceEnumType::kWireless0
|
||||
});
|
||||
settings_->NetworkConfigurationPriority.forceValue("0");
|
||||
settings_->ChargePointId.forceValue(settings.ocppId);
|
||||
|
||||
reset_->resetImmediately(ocpp2_0::BootReasonEnumType::kLocalReset);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
void onPostUpdateDeviceConfiguration(std::string const& payload, httpd_req_t *req) {
|
||||
auto const parsed = read_json_from_string<detail::DeviceConfigInfo>(payload);
|
||||
if (!parsed.has_value()) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing networking profiles
|
||||
for (int i=0; i < settings_->NetworkConnectionProfiles.size(); i++)
|
||||
settings_->NetworkConnectionProfiles.forceValue(i, std::nullopt);
|
||||
|
||||
auto const& settings = parsed.value();
|
||||
settings_->SecurityProfile.setValue(settings.securityProfile);
|
||||
settings_->NetworkConnectionProfiles.forceValue(0, ocpp2_0::NetworkConnectionProfileType {
|
||||
// TODO: Need to set OCPP version from a field
|
||||
settings.ocppProtocol == "OCPP20" ? ocpp2_0::OCPPVersionEnumType::kOCPP20 :
|
||||
ocpp2_0::OCPPVersionEnumType::kOCPP16,
|
||||
ocpp2_0::OCPPTransportEnumType::kJSON,
|
||||
settings.centralSystemUrl,
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
settings.securityProfile,
|
||||
chargelab::ocpp2_0::OCPPInterfaceEnumType::kWireless0
|
||||
});
|
||||
settings_->NetworkConfigurationPriority.forceValue("0");
|
||||
settings_->ChargePointId.forceValue(settings.chargePointId);
|
||||
settings_->ChargerSerialNumber.setValue(settings.serialNumber);
|
||||
settings_->ChargerVendor.setValue(settings.vendor);
|
||||
settings_->ChargerModel.setValue(settings.model);
|
||||
settings_->ChargerFirmwareVersion.setValue(settings.firmwareVersion);
|
||||
|
||||
if (settings.numberOfConnectors != (int)station_->getConnectorMetadata().size()) {
|
||||
station_->updateState([&](detail::SimulatorState& state) {
|
||||
state.evse_metadata.clear();
|
||||
state.connector_metadata.clear();
|
||||
state.connector_state.clear();
|
||||
|
||||
state.station_metadata = charger::StationMetadata {
|
||||
1
|
||||
};
|
||||
|
||||
for (int i=1; i <= settings.numberOfConnectors; i++) {
|
||||
state.evse_metadata[ocpp2_0::EVSEType {i}] = charger::EvseMetadata {
|
||||
1,
|
||||
10560.0
|
||||
};
|
||||
state.connector_metadata[ocpp2_0::EVSEType {i, 1}] = charger::ConnectorMetadata {
|
||||
1,
|
||||
"cType1",
|
||||
1,
|
||||
10560.0,
|
||||
48.0
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.clearCertificates)
|
||||
platform_->removeCertificatesIf([](detail::SavedCertificate const&) {return true;});
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, "{\"successful\":true}", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
void onPostUpdateConnectorSettings(std::string const& payload, httpd_req_t *req) {
|
||||
auto const parsed = read_json_from_string<detail::ConnectorSettings>(payload);
|
||||
if (!parsed.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed parsing ConnectorSettings from: " << payload;
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& settings = parsed.value();
|
||||
if (settings.allowWebsocketConnection.has_value())
|
||||
platform_->setDisableWebsocketConnection(!settings.allowWebsocketConnection.value());
|
||||
|
||||
station_->updateState([&](detail::SimulatorState& state) {
|
||||
for (auto const& entry : state.connector_metadata) {
|
||||
if (settings.connectorId.has_value() && entry.second.connector_id1_6 != settings.connectorId.value())
|
||||
continue;
|
||||
|
||||
auto& connector_state = state.connector_state[entry.first];
|
||||
if (settings.vehicleConnected.has_value())
|
||||
connector_state.vehicle_connected = settings.vehicleConnected.value();
|
||||
if (settings.suspendedByVehicle.has_value())
|
||||
connector_state.suspended_by_vehicle = settings.suspendedByVehicle.value();
|
||||
if (settings.suspendedByCharger.has_value())
|
||||
connector_state.suspended_by_charger = settings.suspendedByCharger.value();
|
||||
}
|
||||
});
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
void onPostSimulateRfidInteraction(std::string const& payload, httpd_req_t *req) {
|
||||
auto const parsed = read_json_from_string<detail::SimulateRfidInteraction>(payload);
|
||||
if (!parsed.has_value()) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const data = parsed.value();
|
||||
station_->simulateTap(
|
||||
ocpp1_6::IdToken {data.idToken},
|
||||
ocpp2_0::IdTokenType {data.idToken, data.tokenType},
|
||||
1000
|
||||
);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
void onPostUpdateOcppParameter(std::string const& payload, httpd_req_t *req) {
|
||||
auto const parsed = read_json_from_string<detail::OcppParameterSetting>(payload);
|
||||
if (!parsed.has_value()) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const setting = parsed.value();
|
||||
auto changeConfigRsp = configuration_module_->changeConfiguration(ocpp1_6::ChangeConfigurationReq {
|
||||
setting.key, setting.value }, true);
|
||||
|
||||
if (changeConfigRsp) {
|
||||
if (std::holds_alternative<ocpp1_6::ChangeConfigurationRsp>(changeConfigRsp.value())) {
|
||||
auto status = std::get<ocpp1_6::ChangeConfigurationRsp>(std::move(changeConfigRsp.value()));
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "change configuration, key: " << setting.key << ", return: " << status;
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, write_json_to_string(status).data(), HTTPD_RESP_USE_STRLEN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, nullptr);
|
||||
}
|
||||
|
||||
void onPostReboot(std::string const& payload, httpd_req_t *req) {
|
||||
reset_->resetImmediately(ocpp2_0::BootReasonEnumType::kLocalReset);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, "{}", HTTPD_RESP_USE_STRLEN);
|
||||
}
|
||||
|
||||
void serveGzipHtml(httpd_req_t *req, char const* start, char const* end) {
|
||||
auto length = end - start;
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||
httpd_resp_send(req, start, length);
|
||||
}
|
||||
|
||||
void serveGzipContent(httpd_req_t *req, char const* content_type, char const* start, char const* end) {
|
||||
auto length = end - start;
|
||||
httpd_resp_set_type(req, content_type);
|
||||
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||
httpd_resp_send(req, start, length);
|
||||
}
|
||||
|
||||
private:
|
||||
static esp_err_t onRequest(httpd_req_t *req) {
|
||||
if (req == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected null req";
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
auto const this_ptr = (PortalDemo*)req->user_ctx;
|
||||
if (this_ptr == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected null user_ctx";
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
switch (req->method) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected request method: " << req->method;
|
||||
return ESP_FAIL;
|
||||
|
||||
case HTTP_GET:
|
||||
this_ptr->onGet(req);
|
||||
break;
|
||||
|
||||
case HTTP_POST:
|
||||
this_ptr->onPost(req);
|
||||
break;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<std::vector<detail::WifiNetwork>> scanNetworks()
|
||||
{
|
||||
std::vector<detail::WifiNetwork> result;
|
||||
std::vector<wifi_ap_record_t> ap_info;
|
||||
ap_info.resize(20);
|
||||
|
||||
uint16_t number = ap_info.size();
|
||||
auto scan_start_result = esp_wifi_scan_start(nullptr, true);
|
||||
if (scan_start_result != ESP_OK) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "wifi scan start failed, error code:" << scan_start_result;
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "ESP_ERR_WIFI_NOT_INIT = " << ESP_ERR_WIFI_NOT_INIT;
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "ESP_ERR_WIFI_NOT_STARTED = " << ESP_ERR_WIFI_NOT_STARTED;
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "ESP_ERR_WIFI_TIMEOUT = " << ESP_ERR_WIFI_TIMEOUT;
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "ESP_ERR_WIFI_STATE = " << ESP_ERR_WIFI_STATE;
|
||||
return std::nullopt;
|
||||
}
|
||||
auto get_ap_result = esp_wifi_scan_get_ap_records(&number, ap_info.data());
|
||||
if (get_ap_result != ESP_OK) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "wifi scan get ap records failed, error code: " << get_ap_result;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ap_info.size() && i < number; i++) {
|
||||
detail::WifiNetwork network {};
|
||||
network.ssid = (char const*)ap_info[i].ssid;
|
||||
network.rssi = std::to_string(ap_info[i].rssi);
|
||||
network.encryption = getAuthModeText(ap_info[i].authmode);
|
||||
result.push_back(std::move(network));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string getAuthModeText(int authmode)
|
||||
{
|
||||
switch (authmode) {
|
||||
case WIFI_AUTH_OPEN: return "WIFI_AUTH_OPEN";
|
||||
case WIFI_AUTH_WEP: return "WIFI_AUTH_WEP";
|
||||
case WIFI_AUTH_WPA_PSK: return "WIFI_AUTH_WPA_PSK";
|
||||
case WIFI_AUTH_WPA2_PSK: return "WIFI_AUTH_WPA2_PSK";
|
||||
case WIFI_AUTH_WPA_WPA2_PSK: return "WIFI_AUTH_WPA_WPA2_PSK";
|
||||
case WIFI_AUTH_WPA2_ENTERPRISE: return "WIFI_AUTH_WPA2_ENTERPRISE";
|
||||
case WIFI_AUTH_WPA3_PSK: return "WIFI_AUTH_WPA3_PSK";
|
||||
case WIFI_AUTH_WPA2_WPA3_PSK: return "WIFI_AUTH_WPA2_WPA3_PSK";
|
||||
default: return "WIFI_AUTH_UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<PlatformESP> platform_;
|
||||
std::shared_ptr<ResetModule> reset_;
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_module_;
|
||||
std::shared_ptr<ConfigurationModule> configuration_module_;
|
||||
std::shared_ptr<StationTestEsp32> station_;
|
||||
std::shared_ptr<Settings> settings_;
|
||||
|
||||
std::vector<httpd_uri_t> handlers_;
|
||||
std::optional<SteadyPointMillis> start_ts_ = std::nullopt;
|
||||
bool initialized_ = false;
|
||||
|
||||
httpd_handle_t server_https_ = nullptr;
|
||||
httpd_handle_t server_http_ = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_PORTAL_DEMO_H
|
||||
1
tools/openocpp/demo-esp32/main/web/.gitignore
vendored
Normal file
1
tools/openocpp/demo-esp32/main/web/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.gz
|
||||
247
tools/openocpp/demo-esp32/main/web/web_config.html
Normal file
247
tools/openocpp/demo-esp32/main/web/web_config.html
Normal file
@@ -0,0 +1,247 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<style>
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.form-group label {
|
||||
display: inline-block;
|
||||
width: 280px; /* Adjust width as needed */
|
||||
}
|
||||
.form-group input {
|
||||
width: 300px; /* Adjust width as needed */
|
||||
}
|
||||
.form-group input[type="checkbox"] {
|
||||
width: auto; /* Let checkbox have natural width */
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<h2 id="myTitle">Charger Configuration</h2>
|
||||
<div>
|
||||
<h3>Network Settings</h3>
|
||||
<div class="form-group">
|
||||
<label for="networkUrl">Central System URL (Slot 0):</label>
|
||||
<input type="text" id="networkUrl" name="networkUrl" size="60">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="chargePointId">Charge Point ID:</label>
|
||||
<input type="text" id="chargePointId" name="chargePointId" size="60">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ocpp_protocol">OCPP Protocol:</label>
|
||||
<select id="ocpp_protocol" name="ocpp_protocol">
|
||||
<option value="OCPP 1.6" selected>OCPP 1.6</option>
|
||||
<option value="OCPP 2.0.1">OCPP 2.0.1</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="securityProfile">Security Profile:</label>
|
||||
<input type="number" id="securityProfile" name="securityProfile" size="60">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="clearCertificates">Clear certificates:</label>
|
||||
<input type="checkbox" id="clearCertificates" name="clearCertificates">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Device Settings</h3>
|
||||
<div class="form-group">
|
||||
<label for="serialNumber">Serial Number:</label>
|
||||
<input type="text" id="serialNumber" name="serialNumber" size="40">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="maxCurrent">Device Maximum Current (amps):</label>
|
||||
<input type="number" id="maxCurrent" name="maxCurrent" size="40">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="vendor">Vendor:</label>
|
||||
<input type="text" id="vendor" name="vendor" size="40">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="model">Model:</label>
|
||||
<input type="text" id="model" name="model" size="40">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="numberOfConnectors">Number of Connectors:</label>
|
||||
<input type="number" id="numberOfConnectors" name="numberOfConnectors" size="40">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div>
|
||||
<button onclick="submit()">Submit</button><br>
|
||||
<p id="result" style="color:blue"> </p>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div>
|
||||
<h3>OCPP 1.6 Configuration</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ocpp1_6_parameter">Select an OCPP Parameter:</label>
|
||||
<select id="ocpp1_6_parameter" name="ocpp1_6_parameter" onchange="onOcppParameterChanged()"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ocpp1_6_parameter_input">Value:</label>
|
||||
<input type="text" id="ocpp1_6_parameter_input">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>OCPP Parameter Read-Only:</label>
|
||||
<span id="ocpp-parameter-readonly-status">-</span>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div>
|
||||
<button id="ocpp1_6_submit_button" onclick="submitOcpp1_6_ParameterChange()">Submit Parameter Change</button>
|
||||
<p id="ocpp1_6_submit_result" style="color:blue"> </p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.ocppParametersMap = window.ocppParametersMap || new Map();
|
||||
|
||||
fetchAndFillSettings();
|
||||
fetchAndFillOcpp1_6_Parameter();
|
||||
|
||||
function fetchAndFillSettings() {
|
||||
fetch("getDeviceConfiguration")
|
||||
.then(response => response.json())
|
||||
.then(payload => {
|
||||
document.getElementById("networkUrl").value = payload.centralSystemUrl;
|
||||
document.getElementById("chargePointId").value = payload.chargePointId;
|
||||
|
||||
let normalizedOcppProtocol = null;
|
||||
|
||||
if (payload.ocppProtocol != null) {
|
||||
normalizedOcppProtocol = payload.ocppProtocol.replace(/^"(.*)"$/, "$1");
|
||||
}
|
||||
|
||||
if (normalizedOcppProtocol == null || normalizedOcppProtocol === "OCPP20") {
|
||||
document.getElementById("ocpp_protocol").value = "OCPP 2.0.1";
|
||||
} else {
|
||||
document.getElementById("ocpp_protocol").value = "OCPP 1.6";
|
||||
}
|
||||
|
||||
document.getElementById("securityProfile").value = payload.securityProfile;
|
||||
document.getElementById("serialNumber").value = payload.serialNumber;
|
||||
document.getElementById("maxCurrent").value = payload.maxCurrentAmps;
|
||||
document.getElementById("vendor").value = payload.vendor;
|
||||
document.getElementById("model").value = payload.model;
|
||||
document.getElementById("numberOfConnectors").value = payload.numberOfConnectors;
|
||||
|
||||
document.getElementById("myTitle").innerHTML = "Charger Configuration " + "(" + payload.firmwareVersion + ")";
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
var data = new Object();
|
||||
data.centralSystemUrl = document.getElementById("networkUrl").value;
|
||||
data.chargePointId = document.getElementById("chargePointId").value;
|
||||
data.ocppProtocol = document.getElementById("ocpp_protocol").value === "OCPP 2.0.1" ? "OCPP20" : "OCPP16";
|
||||
data.securityProfile = parseInt(document.getElementById("securityProfile").value);
|
||||
data.clearCertificates = document.getElementById("clearCertificates").checked;
|
||||
data.serialNumber = document.getElementById("serialNumber").value;
|
||||
data.maxCurrentAmps = document.getElementById("maxCurrent").value;
|
||||
data.vendor = document.getElementById("vendor").value;
|
||||
data.model = document.getElementById("model").value;
|
||||
data.numberOfConnectors = parseInt(document.getElementById("numberOfConnectors").value, 10);
|
||||
data.firmwareVersion = "";
|
||||
|
||||
const jsonString = JSON.stringify(data);
|
||||
console.log(jsonString);
|
||||
|
||||
fetch("updateDeviceConfiguration", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}).then(response => response.json())
|
||||
.then(json => {
|
||||
if (json.successful === true)
|
||||
document.getElementById("result").innerHTML = "Configuration updated successfully";
|
||||
else
|
||||
document.getElementById("result").innerHTML = "Configuration update failed";
|
||||
})
|
||||
.catch((error) => {
|
||||
document.getElementById("result").innerHTML = "Configuration update failed:" + error;
|
||||
});
|
||||
}
|
||||
|
||||
function fetchAndFillOcpp1_6_Parameter() {
|
||||
fetch("getOcppParameters")
|
||||
.then(response => response.json())
|
||||
.then(payload => {
|
||||
const configKeys = payload?.rsp?.configurationKey;
|
||||
if (!configKeys) {
|
||||
console.warn("No configuration keys found in response.");
|
||||
return;
|
||||
}
|
||||
|
||||
configKeys.forEach(item => {
|
||||
window.ocppParametersMap.set(item.key, { value: item.value, readonly: item.readonly });
|
||||
});
|
||||
|
||||
const select = document.getElementById("ocpp1_6_parameter");
|
||||
select.innerHTML = Array.from(window.ocppParametersMap.keys())
|
||||
.map(key => `<option value="${key}">${key}</option>`)
|
||||
.join('');
|
||||
|
||||
onOcppParameterChanged();
|
||||
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
function onOcppParameterChanged() {
|
||||
const selectedKey = document.getElementById("ocpp1_6_parameter").value;
|
||||
const param = window.ocppParametersMap.get(selectedKey);
|
||||
|
||||
document.getElementById("ocpp1_6_parameter_input").value = param.value || "";
|
||||
document.getElementById("ocpp-parameter-readonly-status").textContent = param.readonly ? "Yes (Read-Only)" : "No (Editable)";
|
||||
}
|
||||
|
||||
async function submitOcpp1_6_ParameterChange() {
|
||||
const selectedKey = document.getElementById("ocpp1_6_parameter").value;
|
||||
const newValue = document.getElementById("ocpp1_6_parameter_input").value;
|
||||
|
||||
// Send data to server
|
||||
try {
|
||||
const response = await fetch('updateOcppParameter', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ key: selectedKey, value: newValue })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const payload = await response.json();
|
||||
console.log(`change ocpp parameter response: ${payload}`);
|
||||
|
||||
if (payload.status === "Accepted" || payload.status === "RebootRequired") {
|
||||
if (ocppParametersMap.has(selectedKey)) {
|
||||
window.ocppParametersMap.get(selectedKey).value = newValue;
|
||||
} else {
|
||||
console.warn(`Key ${selectedKey} not found in ocppParametersMap.`);
|
||||
}
|
||||
}
|
||||
document.getElementById("ocpp1_6_submit_result").innerHTML = `Get response with status: ${payload.status} for ${selectedKey} = ${newValue}`;
|
||||
} else {
|
||||
document.getElementById("ocpp1_6_submit_result").innerHTML = "Failed to update the ocpp variable value on server.";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error submitting value:", error);
|
||||
document.getElementById("ocpp1_6_submit_result").innerHTML = "An error occurred while updating the ocpp variable.";
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
244
tools/openocpp/demo-esp32/main/web/web_control.html
Normal file
244
tools/openocpp/demo-esp32/main/web/web_control.html
Normal file
@@ -0,0 +1,244 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.left {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
left: 40px;
|
||||
width: 800px;
|
||||
border: 0px solid #73AD21;
|
||||
padding: 0px;
|
||||
}
|
||||
.eighteen-point {line-height: 18pt;}
|
||||
.li-position
|
||||
{
|
||||
left: 30px;
|
||||
position: relative;
|
||||
}
|
||||
.flex-container {
|
||||
display: flex;
|
||||
}
|
||||
.flex-child {
|
||||
flex: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h2>
|
||||
<label style="color:green" for="connector_id">Connector Id:</label>
|
||||
<select id="connector_id" name="connector_id" onchange="onConnectorIdChanged()">
|
||||
</select>
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h2>EV connection</h2>
|
||||
<input onchange="updateChanges()" type="radio" id="ev_connected_false" name="ev_connected" checked>EV disconnected</input><br>
|
||||
<input onchange="updateChanges()" type="radio" id="ev_connected_true" name="ev_connected">EV connected</input><br>
|
||||
|
||||
<h2>Suspended by EV</h2>
|
||||
<input onchange="updateChanges()" type="radio" id="suspended_ev_true" name="suspended_ev" checked>Suspended by EV</input><br>
|
||||
<input onchange="updateChanges()" type="radio" id="suspended_ev_false" name="suspended_ev">EV drawing power</input><br>
|
||||
|
||||
<h2>Suspended by charger</h2>
|
||||
<input onchange="updateChanges()" type="radio" id="suspended_charger_true" name="suspended_charger" checked>Suspended by charger</input><br>
|
||||
<input onchange="updateChanges()" type="radio" id="suspended_charger_false" name="suspended_charger">Charger delivering power</input><br>
|
||||
|
||||
<h2>RFID tap</h2>
|
||||
<label for="rfid_token">Token:</label>
|
||||
<input type="text" id="rfid_token" name="rfid_token" size="40"><br>
|
||||
|
||||
<label for="rfid_type">Type:</label>
|
||||
<select name="rfid_type" id="rfid_type" style="margin-top: 10px">
|
||||
<option value="Central">Central</option>
|
||||
<option value="eMAID">eMAID</option>
|
||||
<option value="ISO14443">ISO14443</option>
|
||||
<option value="ISO15693">ISO15693</option>
|
||||
<option value="KeyCode">KeyCode</option>
|
||||
<option value="Local">Local</option>
|
||||
<option value="MacAddress">MacAddress</option>
|
||||
<option value="kNoAuthorization">kNoAuthorization</option>
|
||||
</select>
|
||||
|
||||
<button onclick="onSendRFID()">Simulate RFID interaction</button><br>
|
||||
|
||||
<h2>Websocket connection</h2>
|
||||
<input onchange="updateChanges()" type="radio" id="allow_websocket_connection_true" name="allow_websocket_connection" checked>Allow connection</input><br>
|
||||
<input onchange="updateChanges()" type="radio" id="allow_websocket_connection_false" name="allow_websocket_connection">Disallow connection</input><br>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<div class="flex-container">
|
||||
<div class="flex-child">
|
||||
<h2>Charging status</h2>
|
||||
<label for="charging_status">Charging state:</label>
|
||||
<strong id="charging_status" name="charging_status"></strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div>
|
||||
<br>
|
||||
<p id="message_result" style="color:blue"></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var simulateRfidRequest = null;
|
||||
var updateConnectorSettingsRequest = null;
|
||||
var lastStatusUpdate = null;
|
||||
|
||||
// document.body.addEventListener('change', function(e) {
|
||||
// if (e.target.id === "") {
|
||||
//
|
||||
// }
|
||||
// if (e.target.type === "select-one") {
|
||||
// document.getElementById("message_result").innerHTML = "";
|
||||
// return;
|
||||
// }
|
||||
// updateChanges();
|
||||
// });
|
||||
|
||||
setTimeout(() => {
|
||||
fetch("getDeviceConfiguration", {signal: AbortSignal.timeout(1000)})
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
let obj = JSON.parse(text);
|
||||
let number_of_connectors = (obj || {})['numberOfConnectors'] || 0;
|
||||
let connector_id_select = document.getElementById("connector_id");
|
||||
for (let i = 0; i < number_of_connectors; i++) {
|
||||
connector_id_select.options[i] = new Option(i+1, i+1);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
setTimeout(fetchAndFill, 100);
|
||||
})
|
||||
}, 100);
|
||||
|
||||
function onConnectorIdChanged() {
|
||||
lastStatusUpdate = null;
|
||||
}
|
||||
|
||||
function updateBoolInput(name, value) {
|
||||
if (value === true) {
|
||||
if (!document.getElementById(name + "_true").checked) {
|
||||
console.log("Setting: " + name + "_true");
|
||||
document.getElementById(name + "_true").checked = true;
|
||||
}
|
||||
} else {
|
||||
if (!document.getElementById(name + "_false").checked) {
|
||||
console.log("Setting: " + name + "_false");
|
||||
document.getElementById(name + "_false").checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fetchAndFill() {
|
||||
let future;
|
||||
if (updateConnectorSettingsRequest != null) {
|
||||
future = fetch("updateConnectorSettings", {
|
||||
signal: AbortSignal.timeout(1000),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(updateConnectorSettingsRequest)
|
||||
})
|
||||
.then(()=>{
|
||||
document.getElementById("message_result").innerHTML = "The charger status was updated successfully.";
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById("message_result").innerHTML = "The charger status was not updated due to error:" + error;
|
||||
});
|
||||
|
||||
updateConnectorSettingsRequest = null;
|
||||
} else if (simulateRfidRequest != null) {
|
||||
future = fetch("simulateRfidInteraction", {
|
||||
signal: AbortSignal.timeout(1000),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(simulateRfidRequest)
|
||||
})
|
||||
.then(()=>{
|
||||
document.getElementById("message_result").innerHTML = "RFID request accepted.";
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById("message_result").innerHTML = "RFID request failed with error: " + error;
|
||||
});
|
||||
|
||||
simulateRfidRequest = null;
|
||||
} else if (lastStatusUpdate == null || Math.abs(Date.now() - lastStatusUpdate) > 2000) {
|
||||
lastStatusUpdate = Date.now();
|
||||
|
||||
connector_id = document.getElementById("connector_id").value;
|
||||
if (connector_id === null) {
|
||||
connector_id = 1;
|
||||
}
|
||||
future = fetch("getConnectorStatus" + "?connectorId=" + connector_id, {signal: AbortSignal.timeout(1000)})
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
let obj = JSON.parse(text);
|
||||
|
||||
let settings = (obj || {})['connectorSettings'] || {};
|
||||
|
||||
updateBoolInput("ev_connected", settings['vehicleConnected']);
|
||||
updateBoolInput("suspended_ev", settings['suspendedByVehicle']);
|
||||
updateBoolInput("suspended_charger", settings['suspendedByCharger']);
|
||||
|
||||
let charging_status = (obj || {})['chargePointStatus1_6'] || {};
|
||||
|
||||
const chargingElement = document.getElementById('charging_status');
|
||||
if (charging_status === "ValueNotFoundInEnum") {
|
||||
chargingElement.innerHTML = "";
|
||||
} else {
|
||||
chargingElement.innerHTML = charging_status;
|
||||
|
||||
// Set color based on charging_status
|
||||
switch (charging_status) {
|
||||
case "Charging":
|
||||
chargingElement.style.color = "green";
|
||||
break;
|
||||
case "Faulted":
|
||||
chargingElement.style.color = "red";
|
||||
break;
|
||||
case "Unavailable":
|
||||
chargingElement.style.color = "gray";
|
||||
break;
|
||||
default:
|
||||
chargingElement.style.color = "blue"; // Default color
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
future = Promise.resolve();
|
||||
}
|
||||
|
||||
future.then(() => setTimeout(fetchAndFill, 100))
|
||||
.catch(() => setTimeout(fetchAndFill, 100));
|
||||
}
|
||||
|
||||
function updateChanges() {
|
||||
updateConnectorSettingsRequest = {
|
||||
connectorId: parseInt(document.getElementById("connector_id").value, 10),
|
||||
vehicleConnected: document.getElementById("ev_connected_true").checked,
|
||||
suspendedByVehicle: document.getElementById("suspended_ev_true").checked,
|
||||
suspendedByCharger: document.getElementById("suspended_charger_true").checked,
|
||||
allowWebsocketConnection: document.getElementById("allow_websocket_connection_true").checked
|
||||
};
|
||||
}
|
||||
|
||||
function onSendRFID() {
|
||||
simulateRfidRequest = {
|
||||
idToken: document.getElementById("rfid_token").value,
|
||||
tokenType: document.getElementById("rfid_type").value
|
||||
};
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
tools/openocpp/demo-esp32/main/web/web_favicon.ico
Normal file
BIN
tools/openocpp/demo-esp32/main/web/web_favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
121
tools/openocpp/demo-esp32/main/web/web_index.html
Normal file
121
tools/openocpp/demo-esp32/main/web/web_index.html
Normal file
@@ -0,0 +1,121 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Trebuchet MS, Tahoma, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
overflow: hidden;
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.topnav a {
|
||||
float: left;
|
||||
color: #f2f2f2;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
text-decoration: none;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.topnav a:hover {
|
||||
background-color: #ddd;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.topnav a.active {
|
||||
background-color: #04AA6D;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="topnav">
|
||||
<a id="href_setup" class="mylink" href="#" onclick=loadSetup()>Setup</a>
|
||||
<a id="href_config" class="mylink" href="#" onclick=loadConfig()>Configuration</a>
|
||||
<a id="href_control" class="mylink" href="#" onclick=loadControl()>Control</a>
|
||||
</div>
|
||||
|
||||
<div id="content" style="padding-left:16px">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
addSetLinkActiveEvents();
|
||||
document.getElementById('href_setup').click();
|
||||
|
||||
function addSetLinkActiveEvents() {
|
||||
const links = document.querySelectorAll(".mylink");
|
||||
if (links.length) {
|
||||
links.forEach((link) => {
|
||||
link.addEventListener('click', (e) => {
|
||||
links.forEach((link) => {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
e.preventDefault();
|
||||
link.classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
fetch("/config")
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
document.getElementById("content").innerHTML = text;
|
||||
executeScriptElements(document.getElementById("content"));
|
||||
document.title = "Charger Simulator - Configuration";
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById("content").innerHTML = error;
|
||||
});
|
||||
}
|
||||
function loadControl() {
|
||||
fetch("/control")
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
document.getElementById("content").innerHTML = text;
|
||||
executeScriptElements(document.getElementById("content"));
|
||||
document.title = "Charger Simulator - Control";
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById("content").innerHTML = error;
|
||||
});
|
||||
}
|
||||
function loadSetup() {
|
||||
fetch("/setup")
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
document.getElementById("content").innerHTML = text;
|
||||
executeScriptElements(document.getElementById("content"));
|
||||
document.title = "Charger Simulator - Setup";
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById("content").innerHTML = error;
|
||||
});
|
||||
}
|
||||
|
||||
function executeScriptElements(containerElement) {
|
||||
const scriptElements = containerElement.querySelectorAll("script");
|
||||
|
||||
Array.from(scriptElements).forEach((scriptElement) => {
|
||||
const clonedElement = document.createElement("script");
|
||||
|
||||
Array.from(scriptElement.attributes).forEach((attribute) => {
|
||||
clonedElement.setAttribute(attribute.name, attribute.value);
|
||||
});
|
||||
|
||||
clonedElement.text = scriptElement.text;
|
||||
|
||||
scriptElement.parentNode.replaceChild(clonedElement, scriptElement);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
495
tools/openocpp/demo-esp32/main/web/web_setup.html
Normal file
495
tools/openocpp/demo-esp32/main/web/web_setup.html
Normal file
@@ -0,0 +1,495 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Test Charger</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'; style-src 'unsafe-inline'">
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-family: Trebuchet MS, Tahoma, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
input[type=text],
|
||||
input[type=password] {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
display: inline-block;
|
||||
border: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 14px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.cancelbtn {
|
||||
width: auto;
|
||||
padding: 10px 18px;
|
||||
background-color: #f44336;
|
||||
}
|
||||
|
||||
.imgcontainer {
|
||||
text-align: center;
|
||||
margin: 24px 0 12px 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img.avatar {
|
||||
width: 40%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
span.psw {
|
||||
float: right;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgb(0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 5% auto 15% auto;
|
||||
border: 1px solid #888;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
top: 0;
|
||||
color: #000;
|
||||
font-size: 35px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.animate {
|
||||
-webkit-animation: animatezoom 0.6s;
|
||||
animation: animatezoom 0.6s
|
||||
}
|
||||
|
||||
@-webkit-keyframes animatezoom {
|
||||
from {
|
||||
-webkit-transform: scale(0)
|
||||
}
|
||||
|
||||
to {
|
||||
-webkit-transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animatezoom {
|
||||
from {
|
||||
transform: scale(0)
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 300px) {
|
||||
span.psw {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.cancelbtn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.blue {
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #5DA2E1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #1F1F1F;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
label {
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
color: #1F1F1F;
|
||||
}
|
||||
|
||||
select {
|
||||
height: 48px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
background-color: #F5F6F8;
|
||||
border-radius: 4px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #ADADAD;
|
||||
padding: 12px 20px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+);
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: 97%;
|
||||
background-position-y: 12px;
|
||||
}
|
||||
|
||||
.black {
|
||||
color: #1F1F1F;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.bolden {
|
||||
font-family: "Arial Black"
|
||||
}
|
||||
|
||||
input[type='password'],
|
||||
input[type='text'] {
|
||||
border: none;
|
||||
background-color: #F5F6F8;
|
||||
border-radius: 4px;
|
||||
height: 48px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #ADADAD;
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: 95%;
|
||||
background-position-y: 12px;
|
||||
background-size: 22px;
|
||||
}
|
||||
|
||||
input:not(:placeholder-shown) {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.eye {
|
||||
width: 20px;
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
height: 60px;
|
||||
background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABDUlEQVQ4jd2SvW3DMBBGbwQVKlyo4BGC4FKFS4+TATKCNxAggkeoSpHSRQbwAB7AA7hQoUKFLH6E2qQQHfgHdpo0yQHX8T3exyPR/ytlQ8kOhgV7FvSx9+xglA3lM3DBgh0LPn/onbJhcQ0bv2SHlgVgQa/suFHVkCg7bm5gzB2OyvjlDFdDcoa19etZMN8Qp7oUDPEM2KFV1ZAQO2zPMBERO7Ra4JQNpRa4K4FDS0R0IdneCbQLb4/zh/c7QdH4NL40tPXrovFpjHQr6PJ6yr5hQV80PiUiIm1OKxZ0LICS8TWvpyyOf2DBQQtcXk8Zi3+JcKfNafVsjZ0WfGgJlZZQxZjdwzX+ykf6u/UF0Fwo5Apfcq8AAAAASUVORK5CYII=);
|
||||
filter: brightness(0);
|
||||
background-position: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.eye-hide {
|
||||
width: 20px;
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
height: 65px;
|
||||
background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpjYWVjOWZiYi00ZDU4LTQxMmYtODcwMi0zYWMxYzc4ZTU0MWQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QjNDQTYwNTJFRjM3MTFFOEI1MDlFMjg4NDlCM0IyMjIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QjNDQTYwNTFFRjM3MTFFOEI1MDlFMjg4NDlCM0IyMjIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpkMTgzYmMzOC1mMzlmLTQ2OTQtYjI3ZC0xOWVhZDBjN2RhZWMiIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpjNzAwNzcyYi0yYzg4LWQzNDMtOTMwNi03ZjAyZDE2ZTBjOTgiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz53I3B2AAABVklEQVR42sTTyytFURTH8XOFzMRA3RQDlCITQqSk/AMmkomBx+QWE8XAUIqZZEJedVMkAymPMkCuN3kUEckjUoqJCY7v0u/oZHoGdn3q7NNea6+99jkh13WdICPOCThCf+ZJKEAtqhHGJ26xjllsIgMN2PcH28shBdi5jtGFHc1dJWnGFZ7Q6QXn4QBHuMQ2itCGGzxjHGP4wioqveCwgueRq7LqMapd99CrRHdoRbdiMq0Hw9rNMqZiDVtKZuUnoA6LqszWx2Nax3Q+EFE1aVjBKfpxjQcMKOkJ0rW2SbE/Z7Mys3QrNZhT+TMYwYvm7QpOQQxRr/uH6EGLdr3HIM58N9CHRCRjAefWA6+RZVhSh62iHOSjQ00r0bpynfsChV6wlfWojyWi0mJ6rlJzG7GMV0wh2/8l2plLManmVegai1WujTfsYgIbeP9N8O8/U+AE3wIMAOAeYTJHg1SWAAAAAElFTkSuQmCC);
|
||||
filter: brightness(0);
|
||||
background-position: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
select:focus {
|
||||
border: solid 1px #000 !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type='submit'] {
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
background: #5DA2E1;
|
||||
border-radius: 4px;
|
||||
padding: 16px 20px;
|
||||
border: none;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
font-size: 11px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mb-12 {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.mb-8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mt {
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function _el(s) { return document.getElementById(s); }
|
||||
window.onclick = function (event) {
|
||||
var el = _el('id01');
|
||||
if (event.target == el) {
|
||||
el.style.display = "none";
|
||||
}
|
||||
}
|
||||
var u = 'http://', z = "********", z1 = "", prev_el = null, u1 = 'acharger.ca';
|
||||
function sendReq(r, cb) {
|
||||
var x = new XMLHttpRequest();
|
||||
x.onreadystatechange = function () {
|
||||
if (this.readyState == 4 && (this.status == 200 || this.status == 0)) {
|
||||
if (cb) cb(this.responseText);
|
||||
}
|
||||
};
|
||||
x.open("GET", r, true);
|
||||
x.send();
|
||||
}
|
||||
function liclk(e) {
|
||||
if (e && e.innerText) {
|
||||
var q = e.innerText.split(" "), t = e.style;
|
||||
if (q.length >= 3) {
|
||||
z1 = q[0];
|
||||
var el = _el("uname");
|
||||
el.value = z1;
|
||||
el = _el("m_si");
|
||||
el.value = z;
|
||||
if (t) {
|
||||
t.backgroundColor = "#00FFBF";
|
||||
if (prev_el)
|
||||
prev_el.backgroundColor = "#FFFFFF";
|
||||
prev_el = t;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkFormValidation() {
|
||||
document.getElementById("networkUrlError").style.display = "none";
|
||||
document.getElementById("ocppIdError").style.display = "none";
|
||||
var valid = true;
|
||||
|
||||
let password = document.getElementById("Password").value;
|
||||
let confirmPassword = document.getElementById("Confirmpassword").value;
|
||||
if (password !== "" && password !== confirmPassword) {
|
||||
document.getElementById("PasswordMatchError").style.display = "block";
|
||||
valid = false;
|
||||
} else {
|
||||
document.getElementById("PasswordMatchError").style.display = "none";
|
||||
}
|
||||
|
||||
if (document.getElementById("networkUrl").value == "") {
|
||||
document.getElementById("networkUrlError").style.display = "block";
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (document.getElementById("ocppId").value == "") {
|
||||
document.getElementById("ocppIdError").style.display = "block";
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
function submitData() {
|
||||
if (!checkFormValidation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("submitSetup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ssid: document.getElementById("wifi").value,
|
||||
password: document.getElementById("Password").value,
|
||||
networkUrl: document.getElementById("networkUrl").value,
|
||||
ocppId: document.getElementById("ocppId").value,
|
||||
ocppProtocol: document.getElementById("ocppProtocol").value
|
||||
})
|
||||
})
|
||||
.then(()=>{
|
||||
document.getElementById("step1").style.display = "none";
|
||||
document.getElementById("step2").style.display = "block";
|
||||
})
|
||||
.catch(error => {
|
||||
console.log("Submit failed with error: ", error);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleShowPassword() {
|
||||
if (document.getElementById('PasswordEye').className === "eye-hide") {
|
||||
document.getElementById('Password').type = "text";
|
||||
document.getElementById('Confirmpassword').type = "text";
|
||||
document.getElementById('PasswordEye').className = "eye";
|
||||
document.getElementById('ConfirmpasswordEye').className = "eye";
|
||||
} else {
|
||||
document.getElementById('Password').type = "password";
|
||||
document.getElementById('Confirmpassword').type = "password";
|
||||
document.getElementById('PasswordEye').className = "eye-hide"
|
||||
document.getElementById('ConfirmpasswordEye').className = "eye-hide";
|
||||
}
|
||||
}
|
||||
|
||||
sendReq("networks", function (s) {
|
||||
const networks = JSON.parse(s);
|
||||
console.log(networks);
|
||||
|
||||
if (networks) {
|
||||
var select = document.getElementById('wifi');
|
||||
var opt = document.createElement('option');
|
||||
opt.value = "";
|
||||
opt.innerHTML = "WiFi name";
|
||||
select.appendChild(opt);
|
||||
for (let i = 0; i < networks.length; i++) {
|
||||
const entry = networks[i];
|
||||
var opt = document.createElement('option');
|
||||
opt.value = entry.ssid;
|
||||
opt.innerHTML = entry.ssid;
|
||||
select.appendChild(opt);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="step2" style="display: none;">
|
||||
<h2>Charger successfully updated </h2>
|
||||
<h3>Your charger's settings have been updated. </h3>
|
||||
<h3>You may now close this web page.</h3>
|
||||
</div>
|
||||
<div id="step1">
|
||||
<h2 class="mb-12">Test Charger setup</h2>
|
||||
|
||||
<div class="blue">*Case sensitive</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>WiFi name</label>
|
||||
<div class="form-control">
|
||||
<select onchange="javascript:wifiChanged(this);checkFormValidation('wifi')" id="wifi"
|
||||
name="wifi"></select>
|
||||
<span id="wifiError" class="error">Wifi name is required!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Password</label>
|
||||
<div class="form-control">
|
||||
<input id="Password" onblur="javascript:checkFormValidation()" name="Password" type="password"
|
||||
placeholder="Password">
|
||||
<span class="eye-hide" id="PasswordEye" onclick="javascript:toggleShowPassword()"></span>
|
||||
<span id="PasswordError" class="error">Password is required!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Confirm password</label>
|
||||
<div class="form-control">
|
||||
<input id="Confirmpassword" onblur="javascript:checkFormValidation()"
|
||||
name="Confirmpassword" type="password" placeholder="Confirm password">
|
||||
<span class="eye-hide" id="ConfirmpasswordEye"
|
||||
onclick="javascript:toggleShowPassword()"></span>
|
||||
<span id="ConfirmpasswordError" class="error">Confirm password is required!</span>
|
||||
<span id="PasswordMatchError" class="error">Password and Confirm password does not match!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="divNetworkUrl" style="display: block;">
|
||||
<label>Enter network URL</label>
|
||||
<div class="form-control">
|
||||
<input type="text" onblur="javascript:checkFormValidation()" placeholder="Enter network URL" id="networkUrl" name="networkUrl">
|
||||
<span id="networkUrlError" class="error">Network URL is required!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="divOcppId" style="display: block;">
|
||||
<label>Enter OCPP ID</label>
|
||||
<div class="form-control">
|
||||
<input type="text" onblur="javascript:checkFormValidation()" placeholder="Enter OCPP ID" id="ocppId" name="ocppId">
|
||||
<span id="ocppIdError" class="error">OCPP ID is required!</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="divOcppProtocol" style="display: block;">
|
||||
<label>Select OCPP protocol</label>
|
||||
<div class="form-control">
|
||||
<select id="ocppProtocol" name="ocppProtocol">
|
||||
<option value="OCPP16">OCPP 1.6</option>
|
||||
<option value="OCPP20" selected>OCPP 2.0.1</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mt">
|
||||
<input type="submit" onclick="javascript:submitData()" value="Submit" />
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
9
tools/openocpp/demo-esp32/partitions.csv
Normal file
9
tools/openocpp/demo-esp32/partitions.csv
Normal file
@@ -0,0 +1,9 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x3000,
|
||||
pmjournal,data, undefined, , 0x2000
|
||||
otadata, data, ota, 0xE000, 0x2000,
|
||||
app1, app, ota_1, , 0x1E0000,
|
||||
app0, app, ota_0, , 0x1E0000,
|
||||
spiffs, data, spiffs, , 0x2F000,
|
||||
sernr, data, nvs_keys, ,0x1000,
|
||||
|
||||
|
53
tools/openocpp/demo-esp32/sdkconfig.defaults
Normal file
53
tools/openocpp/demo-esp32/sdkconfig.defaults
Normal file
@@ -0,0 +1,53 @@
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=n
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=10240
|
||||
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=10240
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=2304
|
||||
CONFIG_FREERTOS_MAX_TASK_NAME_LEN=32
|
||||
CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y
|
||||
CONFIG_MB_TIMER_PORT_ENABLED=y
|
||||
|
||||
CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
|
||||
CONFIG_HEAP_USE_HOOKS=y
|
||||
CONFIG_ESP_BROWNOUT_DET=n
|
||||
CONFIG_ESP_SYSTEM_BROWNOUT_INTR=n
|
||||
CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=n
|
||||
CONFIG_SPI_FLASH_BROWNOUT_RESET=n
|
||||
|
||||
CONFIG_ESP_HTTPS_SERVER_ENABLE=y
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
|
||||
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
|
||||
|
||||
# Minimizing wifi buffer usage - using "Minimum" profile:
|
||||
# https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/wifi.html#how-to-configure-parameters
|
||||
CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=4
|
||||
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=8
|
||||
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=8
|
||||
CONFIG_ESP_WIFI_RX_BA_WIN=Disable
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=8
|
||||
TCP_WND_DEFAULT=8
|
||||
CONFIG_ESP_WIFI_IRAM_OPT=ENABLE
|
||||
CONFIG_ESP_WIFI_RX_IRAM_OPT=ENABLE
|
||||
CONFIG_LWIP_IRAM_OPTIMIZATION=ENABLE
|
||||
|
||||
# Minimizing TLS heap usage:
|
||||
# https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/protocols/mbedtls.html#reducing-heap-usage
|
||||
# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH=y
|
||||
CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=n
|
||||
# CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
|
||||
CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT=y
|
||||
|
||||
# Note: disabling dynamic buffers - failing in esp_http_client_read during firmware update
|
||||
CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH=n
|
||||
CONFIG_MBEDTLS_DYNAMIC_BUFFER=n
|
||||
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=1024
|
||||
BIN
tools/openocpp/docs/assets/vdoc-summary.png
Normal file
BIN
tools/openocpp/docs/assets/vdoc-summary.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
50
tools/openocpp/include/openocpp/charging_station.h
Normal file
50
tools/openocpp/include/openocpp/charging_station.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_CHARGING_STATION_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_CHARGING_STATION_H
|
||||
|
||||
#include "openocpp/interface/element/websocket_raw_interface.h"
|
||||
#include "openocpp/interface/message_handler.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/interface/element/storage_interface.h"
|
||||
#include "openocpp/interface/connector_interface.h"
|
||||
#include "openocpp/helpers/optional.h"
|
||||
#include "openocpp/helpers/string.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
namespace chargelab {
|
||||
class ChargingStation {
|
||||
public:
|
||||
ChargingStation(std::shared_ptr<WebsocketInterface> websocket, std::shared_ptr<MessageHandlerInterface> handler)
|
||||
: websocket_(std::move(websocket)), handler_(std::move(handler))
|
||||
{
|
||||
}
|
||||
|
||||
~ChargingStation() {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ChargingStation";
|
||||
}
|
||||
|
||||
public:
|
||||
void runStep() {
|
||||
auto connected = websocket_->isConnected();
|
||||
if (connected) {
|
||||
optional::IfPresent(websocket_->pollTextMessages(), [&](auto&& x) { handler_->onTextMessage(x); });
|
||||
optional::IfPresent(websocket_->pollBinaryMessages(), [&](auto&& x) { handler_->onBinaryMessage(x); });
|
||||
}
|
||||
handler_->runStep();
|
||||
if (connected) {
|
||||
optional::IfPresent(handler_->pollTextMessages(), [&](auto&& x) { websocket_->sendTextMessage(x); });
|
||||
optional::IfPresent(handler_->pollBinaryMessages(), [&](auto&& x) { websocket_->sendBinaryMessage(x); });
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SystemInterface> system_;
|
||||
std::shared_ptr<WebsocketInterface> websocket_;
|
||||
std::shared_ptr<MessageHandlerInterface> handler_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_CHARGING_STATION_H
|
||||
149
tools/openocpp/include/openocpp/common/compressed_journal.h
Normal file
149
tools/openocpp/include/openocpp/common/compressed_journal.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_COMPRESSED_JOURNAL_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_COMPRESSED_JOURNAL_H
|
||||
|
||||
#include "openocpp/common/compressed_queue.h"
|
||||
#include "openocpp/interface/element/flash_block_interface.h"
|
||||
|
||||
namespace chargelab {
|
||||
template <typename T, typename Serializer>
|
||||
class CompressedJournalCustom {
|
||||
public:
|
||||
explicit CompressedJournalCustom(std::unique_ptr<FlashBlockInterface> storage, std::string protocol_version)
|
||||
: storage_(std::move(storage)),
|
||||
protocol_version_(std::move(protocol_version))
|
||||
{
|
||||
}
|
||||
|
||||
bool addUpdate(std::vector<T> const& final_state, T const& delta) {
|
||||
// First try to add the delta to the stream
|
||||
if (addToStream(std::vector<T>{delta}))
|
||||
return true;
|
||||
|
||||
// Otherwise erase flash storage and write the final state
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Available journal storage exhausted - clearing";
|
||||
storage_->erase();
|
||||
writeHeader();
|
||||
return addToStream(final_state);
|
||||
}
|
||||
|
||||
template <typename Visitor>
|
||||
void visit(Visitor&& visitor) {
|
||||
if (!checkHeader())
|
||||
return;
|
||||
|
||||
std::vector<uint8_t> raw_data;
|
||||
raw_data.resize(storage_->size() - protocol_version_.size());
|
||||
storage_->read(protocol_version_.size(), raw_data.data(), raw_data.size());
|
||||
|
||||
std::vector<uint8_t> input_buffer;
|
||||
CompressedInputStreamZLib input(input_buffer, raw_data.data(), raw_data.size(), true);
|
||||
while (true) {
|
||||
auto record = input.nextRecord();
|
||||
if (!record.has_value())
|
||||
break;
|
||||
|
||||
auto result = Serializer::read(record.value());
|
||||
if (result.has_value()) {
|
||||
visitor(record.value(), result.value());
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed deserializing payload: " << record.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t totalBytesWritten() const {
|
||||
return bytes_written_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t storageSize() const {
|
||||
return storage_->size();
|
||||
}
|
||||
|
||||
private:
|
||||
bool checkHeader() {
|
||||
std::string test;
|
||||
test.resize(protocol_version_.size());
|
||||
storage_->read(0, test.data(), test.size());
|
||||
return test == protocol_version_;
|
||||
}
|
||||
|
||||
bool writeHeader() {
|
||||
return storage_->write(0, protocol_version_.data(), protocol_version_.size());
|
||||
}
|
||||
|
||||
bool addToStream(std::vector<T> const& records) {
|
||||
if (!checkHeader())
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> raw_data;
|
||||
raw_data.resize(storage_->size() - protocol_version_.size());
|
||||
storage_->read(protocol_version_.size(), raw_data.data(), raw_data.size());
|
||||
|
||||
std::vector<uint8_t> input_buffer;
|
||||
std::vector<uint8_t> output_buffer;
|
||||
CompressedInputStreamZLib input(input_buffer, raw_data.data(), raw_data.size(), true);
|
||||
CompressedOutputStreamZLib output(output_buffer, Z_SYNC_FLUSH);
|
||||
while (true) {
|
||||
auto record = input.nextRecord();
|
||||
if (!record.has_value())
|
||||
break;
|
||||
|
||||
output.addRecord(record.value());
|
||||
}
|
||||
|
||||
for (auto const& x : records)
|
||||
output.addRecord(Serializer::write(x));
|
||||
|
||||
if (!output.close(false) || output_buffer.size() > raw_data.size())
|
||||
return false;
|
||||
|
||||
total_bytes_ = output_buffer.size();
|
||||
|
||||
std::size_t first_index = 0;
|
||||
while (first_index < output_buffer.size()) {
|
||||
auto const old_byte = raw_data[first_index];
|
||||
auto const new_byte = output_buffer[first_index];
|
||||
|
||||
// Note: assuming write operations can only set bits to zero in between erase operations
|
||||
if ((old_byte & new_byte) != new_byte)
|
||||
return false;
|
||||
|
||||
if (old_byte != new_byte)
|
||||
break;
|
||||
|
||||
first_index++;
|
||||
}
|
||||
|
||||
if (first_index >= output_buffer.size())
|
||||
return true;
|
||||
|
||||
auto last_index = first_index;
|
||||
for (auto i = first_index; i < output_buffer.size(); i++) {
|
||||
auto const old_byte = raw_data[i];
|
||||
auto const new_byte = output_buffer[i];
|
||||
|
||||
// Note: assuming write operations can only set bits to zero in between erase operations
|
||||
if ((old_byte & new_byte) != new_byte)
|
||||
return false;
|
||||
if (old_byte != new_byte)
|
||||
last_index = i;
|
||||
}
|
||||
|
||||
auto const size = last_index - first_index + 1;
|
||||
bytes_written_ += size;
|
||||
return storage_->write(protocol_version_.size() + first_index, &output_buffer[first_index], size);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<FlashBlockInterface> storage_;
|
||||
std::string protocol_version_;
|
||||
std::optional<std::size_t> total_bytes_;
|
||||
|
||||
std::atomic<std::size_t> bytes_written_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using CompressedJournalJson = CompressedJournalCustom<T, detail::JsonSerializer<T>>;
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_COMPRESSED_JOURNAL_H
|
||||
606
tools/openocpp/include/openocpp/common/compressed_queue.h
Normal file
606
tools/openocpp/include/openocpp/common/compressed_queue.h
Normal file
@@ -0,0 +1,606 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_COMPRESSED_QUEUE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_COMPRESSED_QUEUE_H
|
||||
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <variant>
|
||||
#include <string>
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
class CompressedStreamZlibConstants {
|
||||
public:
|
||||
// zlib constants
|
||||
static constexpr int kZlibLevel = Z_BEST_COMPRESSION;
|
||||
// 2KiB history buffer
|
||||
static constexpr int kZlibWindowBits = 11;
|
||||
static constexpr int kZlibMemLevel = 1;
|
||||
|
||||
// Compressed block size threshold - when the size of the compressed records in a block exceeds this
|
||||
// threshold a new block is created to store subsequent records
|
||||
static constexpr std::size_t kCompressedBlockThreshold = 5 * 1024;
|
||||
|
||||
static constexpr std::size_t kInflateBufferInitialSize = 256;
|
||||
static constexpr std::size_t kDeflateBufferStepSize = 256;
|
||||
};
|
||||
}
|
||||
|
||||
class CompressedOutputStreamZLib {
|
||||
public:
|
||||
CompressedOutputStreamZLib(std::vector<uint8_t>& buffer, int record_flush = Z_NO_FLUSH)
|
||||
: buffer_(buffer), record_flush_(record_flush)
|
||||
{
|
||||
resetInternal();
|
||||
}
|
||||
|
||||
~CompressedOutputStreamZLib() {
|
||||
if (initialized_)
|
||||
deflateEnd(&stream_);
|
||||
}
|
||||
|
||||
CompressedOutputStreamZLib(CompressedOutputStreamZLib const&) = delete;
|
||||
CompressedOutputStreamZLib(CompressedOutputStreamZLib&&) = delete;
|
||||
CompressedOutputStreamZLib& operator=(CompressedOutputStreamZLib const&) = delete;
|
||||
CompressedOutputStreamZLib& operator=(CompressedOutputStreamZLib&&) = delete;
|
||||
|
||||
void addRecord(std::string_view const& record) {
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
int size_prefix = (int)record.size();
|
||||
writeBlock(&size_prefix, sizeof(size_prefix), Z_NO_FLUSH);
|
||||
writeBlock(record.data(), record.size(), record_flush_);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
resetInternal();
|
||||
}
|
||||
|
||||
bool close(bool finish_stream = true) {
|
||||
if (!initialized_)
|
||||
return false;
|
||||
|
||||
if (finish_stream) {
|
||||
while(true) {
|
||||
if (stream_.avail_out <= 0) {
|
||||
auto const index = buffer_.size();
|
||||
buffer_.resize(buffer_.size() + detail::CompressedStreamZlibConstants::kDeflateBufferStepSize);
|
||||
stream_.next_out = buffer_.data() + index;
|
||||
stream_.avail_out = buffer_.size() - index;
|
||||
}
|
||||
|
||||
auto const ret = deflate(&stream_, Z_FINISH);
|
||||
if (ret == Z_STREAM_END)
|
||||
break;
|
||||
if (ret != Z_OK) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "deflate failed with error code: " << ret;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buffer_.resize(buffer_.size() - stream_.avail_out);
|
||||
deflateEnd(&stream_);
|
||||
std::memset(&stream_, 0, sizeof(stream_));
|
||||
initialized_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t approximateTotalBytes() const {
|
||||
if (!initialized_)
|
||||
return 0;
|
||||
|
||||
return buffer_.size() - stream_.avail_out;
|
||||
}
|
||||
|
||||
private:
|
||||
void writeBlock(void const* data, std::size_t size, int flush) {
|
||||
stream_.next_in = (uint8_t*)data;
|
||||
stream_.avail_in = size;
|
||||
|
||||
do {
|
||||
if (stream_.avail_out <= 0) {
|
||||
auto const index = buffer_.size() - stream_.avail_out;
|
||||
buffer_.resize(buffer_.size() + detail::CompressedStreamZlibConstants::kDeflateBufferStepSize);
|
||||
stream_.next_out = buffer_.data() + index;
|
||||
stream_.avail_out = buffer_.size() - index;
|
||||
}
|
||||
|
||||
auto const ret = deflate(&stream_, flush);
|
||||
if (ret != Z_OK) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "deflate failed with error code: " << ret;
|
||||
return;
|
||||
}
|
||||
} while(stream_.avail_out == 0);
|
||||
|
||||
stream_.next_in = Z_NULL;
|
||||
stream_.avail_in = 0;
|
||||
}
|
||||
|
||||
void resetInternal() {
|
||||
buffer_.resize(detail::CompressedStreamZlibConstants::kDeflateBufferStepSize);
|
||||
stream_.zalloc = Z_NULL;
|
||||
stream_.zfree = Z_NULL;
|
||||
stream_.opaque = Z_NULL;
|
||||
stream_.next_in = Z_NULL;
|
||||
stream_.avail_in = 0;
|
||||
stream_.next_out = buffer_.data();
|
||||
stream_.avail_out = buffer_.size();
|
||||
auto const ret = deflateInit2(
|
||||
&stream_,
|
||||
detail::CompressedStreamZlibConstants::kZlibLevel,
|
||||
Z_DEFLATED,
|
||||
detail::CompressedStreamZlibConstants::kZlibWindowBits,
|
||||
detail::CompressedStreamZlibConstants::kZlibMemLevel,
|
||||
Z_DEFAULT_STRATEGY
|
||||
);
|
||||
if (ret == Z_OK) {
|
||||
initialized_ = true;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "deflateInit failed with error code: " << ret;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
z_stream stream_ {};
|
||||
std::vector<uint8_t>& buffer_;
|
||||
int record_flush_;
|
||||
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
class CompressedInputStreamZLib {
|
||||
public:
|
||||
CompressedInputStreamZLib(
|
||||
std::vector<uint8_t>& buffer,
|
||||
uint8_t* data,
|
||||
std::size_t size,
|
||||
bool ignore_read_errors = false
|
||||
)
|
||||
: buffer_(buffer),
|
||||
ignore_read_errors_(ignore_read_errors)
|
||||
{
|
||||
buffer_.resize(detail::CompressedStreamZlibConstants::kInflateBufferInitialSize);
|
||||
stream_.zalloc = Z_NULL;
|
||||
stream_.zfree = Z_NULL;
|
||||
stream_.opaque = Z_NULL;
|
||||
stream_.next_in = data;
|
||||
stream_.avail_in = size;
|
||||
stream_.next_out = buffer_.data();
|
||||
stream_.avail_out = buffer_.size();
|
||||
auto const ret = inflateInit2(&stream_, detail::CompressedStreamZlibConstants::kZlibWindowBits);
|
||||
if (ret == Z_OK) {
|
||||
initialized_ = true;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "inflateInit failed with error code: " << ret;
|
||||
}
|
||||
}
|
||||
|
||||
~CompressedInputStreamZLib() {
|
||||
if (initialized_)
|
||||
inflateEnd(&stream_);
|
||||
}
|
||||
|
||||
CompressedInputStreamZLib(CompressedInputStreamZLib const&) = delete;
|
||||
CompressedInputStreamZLib(CompressedInputStreamZLib&&) = delete;
|
||||
CompressedInputStreamZLib& operator=(CompressedInputStreamZLib const&) = delete;
|
||||
CompressedInputStreamZLib& operator=(CompressedInputStreamZLib&&) = delete;
|
||||
|
||||
std::optional<std::string_view> nextRecord() {
|
||||
if (!initialized_)
|
||||
return std::nullopt;
|
||||
|
||||
int size_value {};
|
||||
auto size_ptr = readBlock(sizeof(size_value));
|
||||
if (size_ptr == nullptr)
|
||||
return std::nullopt;
|
||||
|
||||
std::memcpy(&size_value, size_ptr, sizeof(size_value));
|
||||
auto data_ptr = readBlock(size_value);
|
||||
if (data_ptr == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data block of size: " << size_value;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::string_view {(char const*)data_ptr, (std::size_t)size_value};
|
||||
}
|
||||
|
||||
bool close() {
|
||||
if (!initialized_)
|
||||
return false;
|
||||
|
||||
inflateEnd(&stream_);
|
||||
initialized_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void const* readBlock(std::size_t size) {
|
||||
auto bytes_present = buffer_.size() - stream_.avail_out - index_;
|
||||
if (bytes_present >= size) {
|
||||
auto result = &buffer_[index_];
|
||||
index_ += size;
|
||||
return result;
|
||||
}
|
||||
if (buffer_.size() < size)
|
||||
buffer_.resize(size);
|
||||
|
||||
std::memmove(buffer_.data(), buffer_.data()+index_, bytes_present);
|
||||
stream_.next_out = buffer_.data() + bytes_present;
|
||||
stream_.avail_out = buffer_.size() - bytes_present;
|
||||
index_ = 0;
|
||||
|
||||
while (true) {
|
||||
auto const avail_begin = stream_.avail_in;
|
||||
auto const ret = inflate(&stream_, Z_BLOCK);
|
||||
auto const avail_end = stream_.avail_in;
|
||||
if (ret != Z_OK && ret != Z_STREAM_END) {
|
||||
if (!ignore_read_errors_) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "inflate failed with error code " << ret << ": "
|
||||
<< (char const *) stream_.msg;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bytes_present = buffer_.size() - stream_.avail_out - index_;
|
||||
if (bytes_present >= size)
|
||||
break;
|
||||
if (avail_begin == avail_end)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
index_ += size;
|
||||
return buffer_.data();
|
||||
}
|
||||
|
||||
private:
|
||||
z_stream stream_ {};
|
||||
std::vector<uint8_t>& buffer_;
|
||||
bool ignore_read_errors_;
|
||||
|
||||
bool initialized_ = false;
|
||||
std::size_t index_ = 0;
|
||||
};
|
||||
|
||||
class CompressedQueueRawZLib {
|
||||
public:
|
||||
CompressedQueueRawZLib() {
|
||||
}
|
||||
|
||||
// Use poll first and pop first instead, and just remove when the attempts time-out
|
||||
std::optional<std::string> pollFront() {
|
||||
if (blocks_.empty())
|
||||
return std::nullopt;
|
||||
|
||||
CompressedInputStreamZLib input {inflate_buffer_, blocks_.front().data(), blocks_.front().size()};
|
||||
auto const record = input.nextRecord();
|
||||
if (!record.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected state - no records present in chunk";
|
||||
blocks_.erase(blocks_.begin());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::string {record.value()};
|
||||
}
|
||||
|
||||
std::optional<std::string> popFront() {
|
||||
if (blocks_.empty())
|
||||
return std::nullopt;
|
||||
|
||||
CompressedInputStreamZLib input {inflate_buffer_, blocks_.front().data(), blocks_.front().size()};
|
||||
auto const record = input.nextRecord();
|
||||
if (!record.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected state - no records present in chunk";
|
||||
blocks_.erase(blocks_.begin());
|
||||
return std::nullopt;
|
||||
}
|
||||
auto result = std::string {record.value()};
|
||||
|
||||
bool empty = true;
|
||||
CompressedOutputStreamZLib output {deflate_buffer_};
|
||||
while (true) {
|
||||
auto const& x = input.nextRecord();
|
||||
if (!x.has_value())
|
||||
break;
|
||||
|
||||
output.addRecord(x.value());
|
||||
empty = false;
|
||||
}
|
||||
|
||||
output.close();
|
||||
if (empty) {
|
||||
blocks_.erase(blocks_.begin());
|
||||
} else {
|
||||
blocks_.front() = deflate_buffer_;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void updateFront(std::string const& update) {
|
||||
if (blocks_.empty())
|
||||
return;
|
||||
|
||||
CompressedInputStreamZLib input {inflate_buffer_, blocks_.front().data(), blocks_.front().size()};
|
||||
auto const record = input.nextRecord();
|
||||
if (!record.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected state - no records present in chunk";
|
||||
blocks_.erase(blocks_.begin());
|
||||
return;
|
||||
}
|
||||
|
||||
CompressedOutputStreamZLib output {deflate_buffer_};
|
||||
output.addRecord(update);
|
||||
|
||||
while (true) {
|
||||
auto const& x = input.nextRecord();
|
||||
if (!x.has_value())
|
||||
break;
|
||||
|
||||
output.addRecord(x.value());
|
||||
}
|
||||
|
||||
if (!output.close()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed closing output stream - offline messages were dropped";
|
||||
blocks_.erase(blocks_.begin());
|
||||
} else {
|
||||
blocks_.front() = deflate_buffer_;
|
||||
}
|
||||
}
|
||||
|
||||
void pushBack(std::string const& value) {
|
||||
if (!blocks_.empty() && blocks_.back().size() < detail::CompressedStreamZlibConstants::kCompressedBlockThreshold) {
|
||||
// Try to add the record to the final block
|
||||
{
|
||||
CompressedOutputStreamZLib writer{deflate_buffer_};
|
||||
CompressedInputStreamZLib reader{inflate_buffer_, blocks_.back().data(), blocks_.back().size()};
|
||||
while (true) {
|
||||
auto record = reader.nextRecord();
|
||||
if (!record.has_value())
|
||||
break;
|
||||
|
||||
writer.addRecord(record.value());
|
||||
}
|
||||
|
||||
if (!reader.close()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed reading historic data - dropping record";
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
writer.addRecord(value);
|
||||
if (!writer.close()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - dropping record" ;
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: freeing the reader and writer before resizing buffer
|
||||
blocks_.back() = deflate_buffer_;
|
||||
} else {
|
||||
// Otherwise add a new block
|
||||
{
|
||||
CompressedOutputStreamZLib writer{deflate_buffer_};
|
||||
writer.addRecord(value);
|
||||
if (!writer.close()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - dropping record";
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: freeing the writer before adding new buffer
|
||||
blocks_.push_back(deflate_buffer_);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Visitor>
|
||||
void visit(Visitor&& visitor) {
|
||||
for (auto& block : blocks_) {
|
||||
CompressedInputStreamZLib reader{inflate_buffer_, block.data(), block.size()};
|
||||
while (true) {
|
||||
auto const& record = reader.nextRecord();
|
||||
if (!record.has_value())
|
||||
break;
|
||||
|
||||
visitor(record.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Predicate>
|
||||
void removeIf(Predicate&& predicate) {
|
||||
bool empty = true;
|
||||
CompressedOutputStreamZLib writer{deflate_buffer_};
|
||||
|
||||
std::vector<std::vector<uint8_t>> original_blocks;
|
||||
std::swap(blocks_, original_blocks);
|
||||
|
||||
for (auto& block : original_blocks) {
|
||||
CompressedInputStreamZLib reader{inflate_buffer_, block.data(), block.size()};
|
||||
while (true) {
|
||||
auto const& record = reader.nextRecord();
|
||||
if (!record.has_value())
|
||||
break;
|
||||
if (predicate(record.value()))
|
||||
continue;
|
||||
|
||||
empty = false;
|
||||
writer.addRecord(record.value());
|
||||
|
||||
if (writer.approximateTotalBytes() > detail::CompressedStreamZlibConstants::kCompressedBlockThreshold) {
|
||||
if (!writer.close()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - blocks were dropped";
|
||||
} else {
|
||||
blocks_.push_back(deflate_buffer_);
|
||||
}
|
||||
|
||||
empty = true;
|
||||
writer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> empty_buffer{};
|
||||
std::swap(empty_buffer, block);
|
||||
|
||||
// This may be a long-running operation, so sleep briefly between individual blocks to yield to other
|
||||
// threads
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
|
||||
if (!empty) {
|
||||
if (!writer.close()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - blocks were dropped";
|
||||
} else {
|
||||
blocks_.push_back(deflate_buffer_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
blocks_.clear();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t totalBytes() const {
|
||||
std::size_t result = 0;
|
||||
for (auto const& block : blocks_)
|
||||
result += block.size();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return blocks_.empty();
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void write(Visitor&& visitor) {
|
||||
for (auto const& block : blocks_)
|
||||
visitor((void*)block.data(), block.size());
|
||||
}
|
||||
|
||||
template<typename Supplier>
|
||||
void read(Supplier&& supplier) {
|
||||
blocks_.clear();
|
||||
while (true) {
|
||||
auto const next = supplier();
|
||||
if (!next.has_value())
|
||||
break;
|
||||
|
||||
blocks_.push_back(next.value());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Note: using a shared deflate and inflate buffer here to reduce memory fragmentation
|
||||
std::vector<std::vector<uint8_t>> blocks_;
|
||||
std::vector<uint8_t> deflate_buffer_;
|
||||
std::vector<uint8_t> inflate_buffer_;
|
||||
};
|
||||
|
||||
template <typename T, typename Serializer>
|
||||
class CompressedQueueCustom {
|
||||
public:
|
||||
std::optional<T> pollFront() {
|
||||
auto text = queue_.pollFront();
|
||||
if (!text.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return Serializer::read(text.value());
|
||||
}
|
||||
|
||||
std::optional<T> popFront() {
|
||||
auto text = queue_.popFront();
|
||||
if (!text.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return Serializer::read(text.value());
|
||||
}
|
||||
|
||||
void updateFront(T const& update) {
|
||||
queue_.updateFront(Serializer::write(update));
|
||||
}
|
||||
|
||||
void pushBack(T const& value) {
|
||||
queue_.pushBack(Serializer::write(value));
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void visit(Visitor&& visitor) {
|
||||
queue_.visit([&](auto const& text) {
|
||||
auto result = Serializer::read(text);
|
||||
if (result.has_value()) {
|
||||
visitor(text, result.value());
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed deserializing payload: " << text;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Predicate>
|
||||
void removeIf(Predicate&& predicate) {
|
||||
queue_.template removeIf([&](auto const& text) {
|
||||
auto result = Serializer::read(text);
|
||||
if (result.has_value()) {
|
||||
return predicate(text, result.value());
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed deserializing payload";
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void clear() {
|
||||
queue_.clear();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::size_t totalBytes() const {
|
||||
return queue_.totalBytes();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return queue_.empty();
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void write(Visitor&& visitor) {
|
||||
queue_.template write(std::forward<Visitor>(visitor));
|
||||
}
|
||||
|
||||
template<typename Supplier>
|
||||
void read(Supplier&& supplier) {
|
||||
queue_.template read(std::forward<Supplier>(supplier));
|
||||
}
|
||||
|
||||
private:
|
||||
CompressedQueueRawZLib queue_;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
//payload_to_string(request)
|
||||
template <typename T>
|
||||
struct JsonSerializer {
|
||||
static std::optional<T> read(std::string_view const& text) {
|
||||
return read_json_from_string<T>(text);
|
||||
}
|
||||
|
||||
static std::string write(T const& value) {
|
||||
return write_json_to_string(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using CompressedQueueJson = CompressedQueueCustom<T, detail::JsonSerializer<T>>;
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_COMPRESSED_QUEUE_H
|
||||
73
tools/openocpp/include/openocpp/common/json_file.h
Normal file
73
tools/openocpp/include/openocpp/common/json_file.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_JSON_FILE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_JSON_FILE_H
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include "openocpp/helpers/json.h"
|
||||
#include "logging.h"
|
||||
|
||||
namespace chargelab {
|
||||
class JsonFile {
|
||||
static constexpr int kMaxFileDumpSize = 10*1000;
|
||||
public:
|
||||
template<class T>
|
||||
static std::optional<T> loadFile(std::string const& file_name) {
|
||||
auto file_content = readTextFile(file_name.c_str());
|
||||
if (file_content) {
|
||||
return read_json_from_string<T>(file_content);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static bool saveProfiles(std::string const& file_name, T const & content) {
|
||||
FILE* file = fopen(file_name.c_str(), "w");
|
||||
if (file == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed to open file for writing: " << file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const json_string = write_json_to_string(content);
|
||||
if (!json_string.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed serializing JSON content";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fwrite(json_string->data(), 1, json_string->size(), file) < json_string.size()) {
|
||||
fclose(file);
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed to write file: " << file_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::optional<std::string> readTextFile(const char* file_name) {
|
||||
FILE* file = fopen(file_name, "r");
|
||||
if (file == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "file doesn't exist:" << file_name;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t file_size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
std::string text;
|
||||
text.resize(file_size);
|
||||
if (fread(text.data(), 1, file_size, file) != file_size) {
|
||||
fclose(file);
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed reading file:" << file_name << " file size:" << file_size;
|
||||
return std::nullopt;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
return text;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_JSON_FILE_H
|
||||
195
tools/openocpp/include/openocpp/common/logging.h
Normal file
195
tools/openocpp/include/openocpp/common/logging.h
Normal file
@@ -0,0 +1,195 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_LOGGING_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_LOGGING_H
|
||||
|
||||
#include "openocpp/model/system_types.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <variant>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::logging {
|
||||
enum class LogLevel {
|
||||
trace,
|
||||
debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
fatal
|
||||
};
|
||||
|
||||
struct LogMetadata {
|
||||
LogLevel level;
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
std::string_view file;
|
||||
int line;
|
||||
#endif
|
||||
std::string_view function;
|
||||
};
|
||||
|
||||
void SetLogLevel(LogLevel level);
|
||||
bool IsLoggingEnabled(LogLevel level);
|
||||
void PrintLogMessage(LogMetadata const& metadata, std::string_view const& message);
|
||||
void PrintLogMessage(LogMetadata const& metadata, std::string const& message);
|
||||
|
||||
using LoggingListenerFunction = std::function<void(LogMetadata const& metadata, std::string_view const& message)>;
|
||||
void RegisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback);
|
||||
void UnregisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback);
|
||||
|
||||
template <typename T, typename U=void>
|
||||
struct LogWriter;
|
||||
|
||||
template <>
|
||||
struct LogWriter<std::string, void> {
|
||||
static void write(std::string& accumulator, std::string const& value) {
|
||||
accumulator += value;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LogWriter<std::string_view, void> {
|
||||
static void write(std::string& accumulator, std::string_view const& value) {
|
||||
accumulator += value;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LogWriter<char const*, void> {
|
||||
static void write(std::string& accumulator, char const* value) {
|
||||
accumulator += value;
|
||||
}
|
||||
};
|
||||
|
||||
template <int N>
|
||||
struct LogWriter<char const[N], void> {
|
||||
static void write(std::string& accumulator, char const* value) {
|
||||
accumulator += value;
|
||||
}
|
||||
};
|
||||
|
||||
template <int N>
|
||||
struct LogWriter<char[N], void> {
|
||||
static void write(std::string& accumulator, char const* value) {
|
||||
accumulator += value;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct LogWriter<T, typename std::enable_if<std::is_arithmetic<T>::value>::type> {
|
||||
static void write(std::string& accumulator, T const& value) {
|
||||
accumulator += std::to_string(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LogWriter<SystemTimeMillis, void> {
|
||||
static void write(std::string& accumulator, SystemTimeMillis value) {
|
||||
accumulator += std::to_string(static_cast<int64_t>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct LogWriter<SteadyPointMillis, void> {
|
||||
static void write(std::string& accumulator, SteadyPointMillis value) {
|
||||
accumulator += std::to_string(static_cast<int64_t>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct LogWriter<T, typename std::enable_if_t<std::is_enum<T>::value>> {
|
||||
static void write(std::string& accumulator, T const& value) {
|
||||
using IntType = typename std::underlying_type<T>::type;
|
||||
accumulator += std::to_string(static_cast<IntType>(value));
|
||||
}
|
||||
};
|
||||
|
||||
class LogAccumulator {
|
||||
public:
|
||||
explicit LogAccumulator(LogLevel level,
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
std::string_view file, int line,
|
||||
#endif
|
||||
std::string_view function)
|
||||
: metadata_ {level,
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
file, line,
|
||||
#endif
|
||||
function},
|
||||
enabled_(IsLoggingEnabled(level))
|
||||
{
|
||||
}
|
||||
|
||||
~LogAccumulator() {
|
||||
PrintLogMessage(metadata_, accumulator_);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool getDone() const {
|
||||
return !enabled_ || done_;
|
||||
}
|
||||
|
||||
void setDone(bool done) {
|
||||
done_ = done;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
friend LogAccumulator& operator<<(LogAccumulator& os, T const& value) {
|
||||
LogWriter<T>::write(os.accumulator_, value);
|
||||
return os;
|
||||
}
|
||||
|
||||
private:
|
||||
LogMetadata metadata_;
|
||||
bool enabled_;
|
||||
|
||||
bool done_ = false;
|
||||
std::string accumulator_;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
constexpr char const* FileName(char const* path) {
|
||||
char const* it = path;
|
||||
char const* result = it;
|
||||
while (true) {
|
||||
auto ch = *(it++);
|
||||
if (ch == '\0')
|
||||
break;
|
||||
if (ch == '/')
|
||||
result = it;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class NoOpAccumulator {
|
||||
template <typename T>
|
||||
friend NoOpAccumulator& operator<<(NoOpAccumulator& os, T const&) {
|
||||
return os;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#ifndef CHARGELAB_DISABLE_LOGGING
|
||||
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
#define CHARGELAB_LOG_MESSAGE(level) \
|
||||
for (::chargelab::LogAccumulator accumulator{::chargelab::LogLevel::level, ::chargelab::detail::FileName(__FILE__), __LINE__, __func__}; !accumulator.getDone(); accumulator.setDone(true)) \
|
||||
accumulator
|
||||
#else
|
||||
#define CHARGELAB_LOG_MESSAGE(level) \
|
||||
for (::chargelab::logging::LogAccumulator accumulator{::chargelab::logging::LogLevel::level, __func__}; !accumulator.getDone(); accumulator.setDone(true)) \
|
||||
accumulator
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define CHARGELAB_LOG_MESSAGE(level) \
|
||||
for (::chargelab::NoOpAccumulator accumulator{}; false;) \
|
||||
accumulator
|
||||
#endif
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_LOGGING_H
|
||||
94
tools/openocpp/include/openocpp/common/macro.h
Normal file
94
tools/openocpp/include/openocpp/common/macro.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_MACRO_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_MACRO_H
|
||||
|
||||
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
|
||||
#define CHARGELAB_THROW(exception) throw exception
|
||||
#define CHARGELAB_TRY try
|
||||
#define CHARGELAB_CATCH catch(std::exception const& e)
|
||||
#define CHARGELAB_RETHROW throw
|
||||
#else
|
||||
#include <cstdlib>
|
||||
#define CHARGELAB_THROW(exception) std::abort()
|
||||
#define CHARGELAB_TRY if(true)
|
||||
#define CHARGELAB_CATCH for(std::exception e; false;)
|
||||
#define CHARGELAB_RETHROW
|
||||
#endif
|
||||
|
||||
#define CHARGELAB_STR(x) #x
|
||||
#define CHARGELAB_XSTR(x) CHARGELAB_STR(x)
|
||||
|
||||
// echo -n "#define CHARGELAB_NUM_ARGS_IMPL(x1"; for x in `seq 2 64`; do echo -n ",x$x"; done; echo ",N,...) N"; echo -n "#define CHARGELAB_NUM_ARGS(...) CHARGELAB_NUM_ARGS_IMPL(__VA_ARGS__ __VA_OPT__(,)64"; for x in `seq 63 -1 0`; do echo -n ",$x"; done; echo ")";
|
||||
#define CHARGELAB_NUM_ARGS_IMPL(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63,x64,N,...) N
|
||||
#define CHARGELAB_NUM_ARGS(...) CHARGELAB_NUM_ARGS_IMPL(__VA_ARGS__ __VA_OPT__(,)64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
|
||||
|
||||
// echo "#define CHARGELAB_EXPAND(x) x"; for x in `seq 0 64`; do echo -n "#define CHARGELAB_EXPAND_$x(f"; for y in `seq 1 $x`; do echo -n ",x$y"; done; echo -n ")"; for y in `seq 1 $x`; do echo -n " f(x$y)"; done; echo; done; echo -n "#define CHARGELAB_GET_MACRO(f"; for x in `seq 1 64`; do echo -n ",x$x"; done; echo -n ", TARGET, ...) TARGET"; echo; echo -n "#define CHARGELAB_PASTE(...) CHARGELAB_EXPAND(CHARGELAB_GET_MACRO(__VA_ARGS__"; for x in `seq 64 -1 0`; do echo -n ",CHARGELAB_EXPAND_$x"; done; echo "))";
|
||||
#define CHARGELAB_EXPAND(x) x
|
||||
#define CHARGELAB_EXPAND_0(f)
|
||||
#define CHARGELAB_EXPAND_1(f,x1) f(x1)
|
||||
#define CHARGELAB_EXPAND_2(f,x1,x2) f(x1) f(x2)
|
||||
#define CHARGELAB_EXPAND_3(f,x1,x2,x3) f(x1) f(x2) f(x3)
|
||||
#define CHARGELAB_EXPAND_4(f,x1,x2,x3,x4) f(x1) f(x2) f(x3) f(x4)
|
||||
#define CHARGELAB_EXPAND_5(f,x1,x2,x3,x4,x5) f(x1) f(x2) f(x3) f(x4) f(x5)
|
||||
#define CHARGELAB_EXPAND_6(f,x1,x2,x3,x4,x5,x6) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6)
|
||||
#define CHARGELAB_EXPAND_7(f,x1,x2,x3,x4,x5,x6,x7) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7)
|
||||
#define CHARGELAB_EXPAND_8(f,x1,x2,x3,x4,x5,x6,x7,x8) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8)
|
||||
#define CHARGELAB_EXPAND_9(f,x1,x2,x3,x4,x5,x6,x7,x8,x9) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9)
|
||||
#define CHARGELAB_EXPAND_10(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10)
|
||||
#define CHARGELAB_EXPAND_11(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11)
|
||||
#define CHARGELAB_EXPAND_12(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12)
|
||||
#define CHARGELAB_EXPAND_13(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13)
|
||||
#define CHARGELAB_EXPAND_14(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14)
|
||||
#define CHARGELAB_EXPAND_15(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15)
|
||||
#define CHARGELAB_EXPAND_16(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16)
|
||||
#define CHARGELAB_EXPAND_17(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17)
|
||||
#define CHARGELAB_EXPAND_18(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18)
|
||||
#define CHARGELAB_EXPAND_19(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19)
|
||||
#define CHARGELAB_EXPAND_20(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20)
|
||||
#define CHARGELAB_EXPAND_21(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21)
|
||||
#define CHARGELAB_EXPAND_22(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22)
|
||||
#define CHARGELAB_EXPAND_23(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23)
|
||||
#define CHARGELAB_EXPAND_24(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24)
|
||||
#define CHARGELAB_EXPAND_25(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25)
|
||||
#define CHARGELAB_EXPAND_26(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26)
|
||||
#define CHARGELAB_EXPAND_27(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27)
|
||||
#define CHARGELAB_EXPAND_28(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28)
|
||||
#define CHARGELAB_EXPAND_29(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29)
|
||||
#define CHARGELAB_EXPAND_30(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30)
|
||||
#define CHARGELAB_EXPAND_31(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31)
|
||||
#define CHARGELAB_EXPAND_32(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32)
|
||||
#define CHARGELAB_EXPAND_33(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33)
|
||||
#define CHARGELAB_EXPAND_34(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34)
|
||||
#define CHARGELAB_EXPAND_35(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35)
|
||||
#define CHARGELAB_EXPAND_36(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36)
|
||||
#define CHARGELAB_EXPAND_37(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37)
|
||||
#define CHARGELAB_EXPAND_38(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38)
|
||||
#define CHARGELAB_EXPAND_39(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39)
|
||||
#define CHARGELAB_EXPAND_40(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40)
|
||||
#define CHARGELAB_EXPAND_41(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41)
|
||||
#define CHARGELAB_EXPAND_42(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42)
|
||||
#define CHARGELAB_EXPAND_43(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43)
|
||||
#define CHARGELAB_EXPAND_44(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44)
|
||||
#define CHARGELAB_EXPAND_45(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45)
|
||||
#define CHARGELAB_EXPAND_46(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46)
|
||||
#define CHARGELAB_EXPAND_47(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47)
|
||||
#define CHARGELAB_EXPAND_48(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48)
|
||||
#define CHARGELAB_EXPAND_49(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49)
|
||||
#define CHARGELAB_EXPAND_50(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50)
|
||||
#define CHARGELAB_EXPAND_51(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51)
|
||||
#define CHARGELAB_EXPAND_52(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52)
|
||||
#define CHARGELAB_EXPAND_53(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53)
|
||||
#define CHARGELAB_EXPAND_54(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54)
|
||||
#define CHARGELAB_EXPAND_55(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55)
|
||||
#define CHARGELAB_EXPAND_56(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56)
|
||||
#define CHARGELAB_EXPAND_57(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57)
|
||||
#define CHARGELAB_EXPAND_58(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58)
|
||||
#define CHARGELAB_EXPAND_59(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59)
|
||||
#define CHARGELAB_EXPAND_60(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60)
|
||||
#define CHARGELAB_EXPAND_61(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61)
|
||||
#define CHARGELAB_EXPAND_62(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61) f(x62)
|
||||
#define CHARGELAB_EXPAND_63(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61) f(x62) f(x63)
|
||||
#define CHARGELAB_EXPAND_64(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63,x64) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61) f(x62) f(x63) f(x64)
|
||||
#define CHARGELAB_GET_MACRO(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63,x64, TARGET, ...) TARGET
|
||||
#define CHARGELAB_PASTE(...) CHARGELAB_EXPAND(CHARGELAB_GET_MACRO(__VA_ARGS__,CHARGELAB_EXPAND_64,CHARGELAB_EXPAND_63,CHARGELAB_EXPAND_62,CHARGELAB_EXPAND_61,CHARGELAB_EXPAND_60,CHARGELAB_EXPAND_59,CHARGELAB_EXPAND_58,CHARGELAB_EXPAND_57,CHARGELAB_EXPAND_56,CHARGELAB_EXPAND_55,CHARGELAB_EXPAND_54,CHARGELAB_EXPAND_53,CHARGELAB_EXPAND_52,CHARGELAB_EXPAND_51,CHARGELAB_EXPAND_50,CHARGELAB_EXPAND_49,CHARGELAB_EXPAND_48,CHARGELAB_EXPAND_47,CHARGELAB_EXPAND_46,CHARGELAB_EXPAND_45,CHARGELAB_EXPAND_44,CHARGELAB_EXPAND_43,CHARGELAB_EXPAND_42,CHARGELAB_EXPAND_41,CHARGELAB_EXPAND_40,CHARGELAB_EXPAND_39,CHARGELAB_EXPAND_38,CHARGELAB_EXPAND_37,CHARGELAB_EXPAND_36,CHARGELAB_EXPAND_35,CHARGELAB_EXPAND_34,CHARGELAB_EXPAND_33,CHARGELAB_EXPAND_32,CHARGELAB_EXPAND_31,CHARGELAB_EXPAND_30,CHARGELAB_EXPAND_29,CHARGELAB_EXPAND_28,CHARGELAB_EXPAND_27,CHARGELAB_EXPAND_26,CHARGELAB_EXPAND_25,CHARGELAB_EXPAND_24,CHARGELAB_EXPAND_23,CHARGELAB_EXPAND_22,CHARGELAB_EXPAND_21,CHARGELAB_EXPAND_20,CHARGELAB_EXPAND_19,CHARGELAB_EXPAND_18,CHARGELAB_EXPAND_17,CHARGELAB_EXPAND_16,CHARGELAB_EXPAND_15,CHARGELAB_EXPAND_14,CHARGELAB_EXPAND_13,CHARGELAB_EXPAND_12,CHARGELAB_EXPAND_11,CHARGELAB_EXPAND_10,CHARGELAB_EXPAND_9,CHARGELAB_EXPAND_8,CHARGELAB_EXPAND_7,CHARGELAB_EXPAND_6,CHARGELAB_EXPAND_5,CHARGELAB_EXPAND_4,CHARGELAB_EXPAND_3,CHARGELAB_EXPAND_2,CHARGELAB_EXPAND_1,CHARGELAB_EXPAND_0)(__VA_ARGS__))
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_MACRO_H
|
||||
129
tools/openocpp/include/openocpp/common/operation_holder.h
Normal file
129
tools/openocpp/include/openocpp/common/operation_holder.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_OPERATION_HOLDER_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_OPERATION_HOLDER_H
|
||||
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
|
||||
namespace chargelab {
|
||||
struct NoOperation {};
|
||||
inline constexpr NoOperation kNoOperation;
|
||||
|
||||
template <typename T>
|
||||
class OperationHolder {
|
||||
public:
|
||||
explicit OperationHolder(std::shared_ptr<SystemInterface> system)
|
||||
: system_(std::move(system))
|
||||
{
|
||||
assert(system_ != nullptr);
|
||||
}
|
||||
|
||||
OperationHolder& operator=(NoOperation const&) {
|
||||
state_ = std::nullopt;
|
||||
consecutive_failures_ = std::nullopt;
|
||||
idle_since_ = system_->steadyClockNow();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(NoOperation const&) const {
|
||||
return !operationInProgress();
|
||||
}
|
||||
|
||||
bool operator==(T const& rhs) const {
|
||||
if (!state_.has_value())
|
||||
return false;
|
||||
|
||||
return state_->first == rhs && operationInProgress();
|
||||
}
|
||||
|
||||
bool operator!=(NoOperation const& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
bool operator!=(T const& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] int consecutiveFailures() const {
|
||||
int consecutive_failures = 0;
|
||||
if (consecutive_failures_.has_value()) {
|
||||
consecutive_failures = consecutive_failures_.value();
|
||||
}
|
||||
if (currentOperationTimedOut()) {
|
||||
consecutive_failures++;
|
||||
}
|
||||
|
||||
return consecutive_failures;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool currentOperationTimedOut() const {
|
||||
return state_.has_value() && !operationInProgress();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operationInProgress() const {
|
||||
if (!state_.has_value())
|
||||
return false;
|
||||
|
||||
auto const delta = system_->steadyClockNow() - state_->second;
|
||||
if (delta < 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Invalid steady clock delta: " << delta;
|
||||
return false;
|
||||
} else if (delta >= timeout_seconds_*1000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] int getIdleDurationSeconds() const {
|
||||
if (!state_.has_value()) {
|
||||
if (idle_since_.has_value()) {
|
||||
return (system_->steadyClockNow() - idle_since_.value())/1000;
|
||||
} else {
|
||||
return std::numeric_limits<int>::max();
|
||||
}
|
||||
}
|
||||
|
||||
auto const delta = (system_->steadyClockNow() - state_->second)/1000 - timeout_seconds_;
|
||||
if (delta < 0)
|
||||
return 0;
|
||||
if (delta >= std::numeric_limits<int>::max())
|
||||
return std::numeric_limits<int>::max();
|
||||
|
||||
return delta;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool wasIdleFor(int seconds) {
|
||||
return !operationInProgress() && getIdleDurationSeconds() >= seconds;
|
||||
}
|
||||
|
||||
void setWithTimeout(int timeout_seconds, std::optional<T> const& id) {
|
||||
timeout_seconds_ = timeout_seconds;
|
||||
|
||||
if (id.has_value()) {
|
||||
state_ = std::make_pair(id.value(), system_->steadyClockNow());
|
||||
|
||||
if (consecutive_failures_.has_value()) {
|
||||
consecutive_failures_ = consecutive_failures_.value() + 1;
|
||||
} else {
|
||||
consecutive_failures_ = 0;
|
||||
}
|
||||
} else {
|
||||
state_ = std::nullopt;
|
||||
consecutive_failures_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SystemInterface> system_;
|
||||
int timeout_seconds_;
|
||||
|
||||
std::optional<std::pair<T, SteadyPointMillis>> state_ = std::nullopt;
|
||||
std::optional<int> consecutive_failures_ = std::nullopt;
|
||||
std::optional<SteadyPointMillis> idle_since_ = std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_OPERATION_HOLDER_H
|
||||
81
tools/openocpp/include/openocpp/common/ring_buffer.h
Normal file
81
tools/openocpp/include/openocpp/common/ring_buffer.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_RING_BUFFER_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_RING_BUFFER_H
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <cassert>
|
||||
|
||||
namespace chargelab {
|
||||
template <typename T, int N>
|
||||
class RingBuffer {
|
||||
public:
|
||||
void pushBack(T value) {
|
||||
int index;
|
||||
if (size_ < (int)buffer_.size()) {
|
||||
index = (begin_index_ + size_++) % buffer_.size();
|
||||
} else {
|
||||
index = begin_index_;
|
||||
begin_index_ = (begin_index_ + 1) % buffer_.size();
|
||||
}
|
||||
|
||||
buffer_[index] = std::move(value);
|
||||
}
|
||||
|
||||
std::optional<T> popFront() {
|
||||
if (size_ <= 0)
|
||||
return std::nullopt;
|
||||
|
||||
T result = removeAt(begin_index_);
|
||||
begin_index_ = (begin_index_ + 1) % buffer_.size();
|
||||
size_--;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<T> popBack() {
|
||||
if (size_ <= 0)
|
||||
return std::nullopt;
|
||||
|
||||
T result = removeAt((begin_index_ + size_ - 1) % buffer_.size());
|
||||
size_--;
|
||||
return result;
|
||||
}
|
||||
|
||||
T& front() {
|
||||
return buffer_[begin_index_];
|
||||
}
|
||||
|
||||
T& back() {
|
||||
return buffer_[(begin_index_ + size_ - 1) % buffer_.size()];
|
||||
}
|
||||
|
||||
T operator[] (int i) const {
|
||||
assert(i >= 0 && i < size_);
|
||||
return buffer_[(begin_index_ + i) % buffer_.size()];
|
||||
}
|
||||
|
||||
T& operator[] (int i) {
|
||||
assert(i >= 0 && i < size_);
|
||||
return buffer_[(begin_index_ + i) % buffer_.size()];
|
||||
}
|
||||
|
||||
[[nodiscard]] bool empty() const {
|
||||
return size_ <= 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] int size() const {
|
||||
return size_;
|
||||
}
|
||||
private:
|
||||
T removeAt(int index) {
|
||||
T result {};
|
||||
std::swap(result, buffer_[index]);
|
||||
return result;
|
||||
}
|
||||
private:
|
||||
std::array<T, N> buffer_;
|
||||
int begin_index_ = 0;
|
||||
int size_ = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_RING_BUFFER_H
|
||||
108
tools/openocpp/include/openocpp/common/serialization.h
Normal file
108
tools/openocpp/include/openocpp/common/serialization.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_SERIALIZATION_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_SERIALIZATION_H
|
||||
|
||||
#include <type_traits>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
namespace chargelab {
|
||||
// Note: this file provides a number of very simple binary serialization wrappers that are used in certain contexts
|
||||
// where JSON was too expensive (such as maintaining a large number of historic messages as a compressed stream).
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_trivial<T>::value, std::optional<std::size_t>>::type
|
||||
readPrimitive(std::string_view const& text, std::optional<std::size_t> index, T& out) {
|
||||
if (!index.has_value())
|
||||
return std::nullopt;
|
||||
if (index.value() + sizeof(out) > text.size())
|
||||
return std::nullopt;
|
||||
|
||||
std::memcpy(&out, text.data() + index.value(), sizeof(out));
|
||||
return index.value() + sizeof(out);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_trivial<typename T::Value>::value, std::optional<std::size_t>>::type
|
||||
readPrimitive(std::string_view const& text, std::optional<std::size_t> index, T& out) {
|
||||
using raw_type = typename T::Value;
|
||||
if (!index.has_value())
|
||||
return std::nullopt;
|
||||
if (index.value() + sizeof(raw_type{}) > text.size())
|
||||
return std::nullopt;
|
||||
|
||||
raw_type raw;
|
||||
std::memcpy(&raw, text.data() + index.value(), sizeof(raw));
|
||||
out = raw;
|
||||
return index.value() + sizeof(raw);
|
||||
}
|
||||
|
||||
inline std::optional<std::size_t> readPrimitive(std::string_view const& text, std::optional<std::size_t> index, std::string& out) {
|
||||
int32_t size = 0;
|
||||
index = readPrimitive(text, index, size);
|
||||
if (!index.has_value() || size < 0)
|
||||
return std::nullopt;
|
||||
if (index.value() + size > text.size())
|
||||
return std::nullopt;
|
||||
|
||||
out.resize(size);
|
||||
std::memcpy(out.data(), text.data() + index.value(), size);
|
||||
return index.value() + size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<std::size_t> readPrimitive(std::string_view const& text, std::optional<std::size_t> index, std::optional<T>& out) {
|
||||
bool present = false;
|
||||
index = readPrimitive(text, index, present);
|
||||
if (!index.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
if (present) {
|
||||
T value;
|
||||
auto result = readPrimitive(text, index, value);
|
||||
out = std::move(value);
|
||||
return result;
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_trivial<T>::value>::type
|
||||
writePrimitive(std::string& text, T const& in) {
|
||||
auto index = text.size();
|
||||
text.resize(index + sizeof(in));
|
||||
std::memcpy(text.data() + index, &in, sizeof(in));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_trivial<typename T::Value>::value>::type
|
||||
writePrimitive(std::string& text, T const& in) {
|
||||
using raw_type = typename T::Value;
|
||||
raw_type raw = in;
|
||||
|
||||
auto index = text.size();
|
||||
text.resize(index + sizeof(raw));
|
||||
std::memcpy(text.data() + index, &raw, sizeof(raw));
|
||||
}
|
||||
|
||||
inline void writePrimitive(std::string& text, std::string const& in) {
|
||||
writePrimitive(text, (int32_t)in.size());
|
||||
|
||||
auto index = text.size();
|
||||
text.resize(index + in.size());
|
||||
std::memcpy(text.data() + index, in.data(), in.size());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void writePrimitive(std::string& text, std::optional<T> const& in) {
|
||||
if (in.has_value()) {
|
||||
writePrimitive(text, true);
|
||||
writePrimitive(text, in.value());
|
||||
} else {
|
||||
writePrimitive(text, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_SERIALIZATION_H
|
||||
3617
tools/openocpp/include/openocpp/common/settings.h
Normal file
3617
tools/openocpp/include/openocpp/common/settings.h
Normal file
File diff suppressed because it is too large
Load Diff
67
tools/openocpp/include/openocpp/common/storage.h
Normal file
67
tools/openocpp/include/openocpp/common/storage.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STORAGE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STORAGE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "openocpp/interface/element/storage_interface.h"
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
class CloseFileWrapper {
|
||||
public:
|
||||
CloseFileWrapper(std::FILE* file)
|
||||
: file_(file)
|
||||
{
|
||||
}
|
||||
|
||||
~CloseFileWrapper() {
|
||||
std::fclose(file_);
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* file_;
|
||||
};
|
||||
}
|
||||
|
||||
class StorageFile : public StorageInterface {
|
||||
public:
|
||||
explicit StorageFile(std::string filename) : filename_(std::move(filename))
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
bool read(std::function<bool(FILE *)> const& function) override {
|
||||
auto file = std::fopen(filename_.c_str(), "r");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
detail::CloseFileWrapper wrapper{file};
|
||||
return function(file);
|
||||
}
|
||||
|
||||
bool write(std::function<bool(FILE *)> const& function) override {
|
||||
auto file = std::fopen(filename_.c_str(), "w");
|
||||
if (file == nullptr)
|
||||
return false;
|
||||
|
||||
detail::CloseFileWrapper wrapper{file};
|
||||
return function(file);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string filename_;
|
||||
};
|
||||
|
||||
class StorageNull : public StorageInterface {
|
||||
public:
|
||||
bool read(std::function<bool(FILE *)> const&) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool write(std::function<bool(FILE *)> const&) override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STORAGE_H
|
||||
150
tools/openocpp/include/openocpp/common/stream.h
Normal file
150
tools/openocpp/include/openocpp/common/stream.h
Normal file
@@ -0,0 +1,150 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STREAM_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STREAM_H
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#include "openocpp/interface/element/byte_reader_interface.h"
|
||||
#include "openocpp/interface/element/byte_writer_interface.h"
|
||||
|
||||
namespace chargelab::stream {
|
||||
class BufferedByteWriter {
|
||||
public:
|
||||
BufferedByteWriter(ByteWriterInterface& output)
|
||||
: output_(output)
|
||||
{
|
||||
}
|
||||
|
||||
void put(char ch) {
|
||||
if (index_ >= buffer_.size())
|
||||
flush();
|
||||
|
||||
buffer_[index_++] = ch;
|
||||
}
|
||||
|
||||
void write(char const* s, std::size_t n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
put(s[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void write(char const* s) {
|
||||
write(s, std::strlen(s));
|
||||
}
|
||||
|
||||
void write(std::string const &s) {
|
||||
write(s.c_str(), s.size());
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if (index_ == 0)
|
||||
return;
|
||||
|
||||
output_.write(buffer_.data(), index_);
|
||||
index_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
ByteWriterInterface& output_;
|
||||
std::array<char, 128> buffer_ {};
|
||||
std::size_t index_ = 0;
|
||||
};
|
||||
|
||||
class BufferedByteReader {
|
||||
public:
|
||||
BufferedByteReader(ByteReaderInterface& input)
|
||||
: input_(input)
|
||||
{
|
||||
next();
|
||||
}
|
||||
|
||||
int peek() const {
|
||||
if (index_ >= count_)
|
||||
return EOF;
|
||||
|
||||
return buffer_[index_];
|
||||
}
|
||||
|
||||
int take() {
|
||||
if (index_ >= count_)
|
||||
return EOF;
|
||||
|
||||
auto const result = buffer_[index_++];
|
||||
if (index_ >= count_ && count_ == buffer_.size())
|
||||
next();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::size_t tellg() const {
|
||||
return offset_ + index_;
|
||||
}
|
||||
|
||||
private:
|
||||
void next() {
|
||||
count_ = input_.read(buffer_.data(), buffer_.size());
|
||||
index_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
ByteReaderInterface& input_;
|
||||
std::array<char, 128> buffer_ {};
|
||||
std::size_t index_ = 0;
|
||||
std::size_t count_ = 0;
|
||||
std::size_t offset_ = 0;
|
||||
};
|
||||
|
||||
class StringReader : public ByteReaderInterface {
|
||||
public:
|
||||
explicit StringReader(std::string text) : text_(std::move(text))
|
||||
{
|
||||
}
|
||||
|
||||
size_t read(char *s, std::size_t n) override {
|
||||
if (index_ >= text_.size())
|
||||
return 0;
|
||||
|
||||
std::size_t count = std::min(n, text_.size() - index_);
|
||||
std::memcpy(s, &text_[index_], count);
|
||||
index_ += count;
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
std::size_t index_ = 0;
|
||||
};
|
||||
|
||||
class StringWriter : public ByteWriterInterface {
|
||||
public:
|
||||
void write(char const *s, std::size_t count) override {
|
||||
auto const index = text_.size();
|
||||
text_.resize(text_.size() + count);
|
||||
std::memcpy(&text_[index], s, count);
|
||||
}
|
||||
|
||||
std::string const& str() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string text_;
|
||||
};
|
||||
|
||||
class SizeCalculator : public ByteWriterInterface {
|
||||
public:
|
||||
[[nodiscard]] std::size_t getTotalBytes() const {
|
||||
return total_bytes_;
|
||||
}
|
||||
|
||||
private:
|
||||
void write(const char *, std::size_t count) override {
|
||||
total_bytes_ += count;
|
||||
}
|
||||
|
||||
private:
|
||||
std::size_t total_bytes_ = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STREAM_H
|
||||
82
tools/openocpp/include/openocpp/helpers/chrono.h
Normal file
82
tools/openocpp/include/openocpp/helpers/chrono.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_CHRONO_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_CHRONO_H
|
||||
|
||||
#include "openocpp/model/system_types.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
|
||||
|
||||
namespace chargelab::chrono {
|
||||
namespace detail {
|
||||
inline bool isLeapYear(unsigned int year) {
|
||||
return (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0));
|
||||
}
|
||||
// month: 1-based
|
||||
inline int getDaysOfMonth(unsigned int year, int month) {
|
||||
switch (month) {
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
case 7:
|
||||
case 8:
|
||||
case 10:
|
||||
case 12:
|
||||
return 31;
|
||||
case 4:
|
||||
case 6:
|
||||
case 9:
|
||||
case 11:
|
||||
return 30;
|
||||
case 2:
|
||||
return isLeapYear(year) ? 29 : 28;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
inline int totalLeapYears(int year) {
|
||||
return year/4 - year/100 + year/400;
|
||||
}
|
||||
}
|
||||
|
||||
inline std::optional<std::string> ToString(SystemTimeMillis const& timestamp) {
|
||||
return ocpp1_6::DateTime::timestampToText(timestamp);
|
||||
}
|
||||
|
||||
inline std::optional<SystemTimeMillis> FromString(std::string const& text) {
|
||||
return ocpp1_6::DateTime::textToTimestamp(text);
|
||||
}
|
||||
|
||||
inline std::time_t timegm2(std::tm const *tm) {
|
||||
if (tm == nullptr) return -1;
|
||||
if (tm->tm_mon >= 12 || tm->tm_hour >= 24 || tm->tm_min >= 60 || tm->tm_sec >= 61) {
|
||||
return -1;
|
||||
}
|
||||
if (detail::getDaysOfMonth(tm->tm_year + 1900, tm->tm_mon + 1) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// get the day to the tm date since 1900-01-01 00:00:00 +0000, UTC instead of the Epoch (1970-01-01 00:00:00 +0000, UTC)
|
||||
std::int64_t total_days = 0; //
|
||||
int year = tm->tm_year + 1900;
|
||||
|
||||
auto const year_since_epoch = year - 1970;
|
||||
int leap_years = detail::totalLeapYears(year - 1) - detail::totalLeapYears(1970);
|
||||
|
||||
total_days = year_since_epoch * 365 + leap_years;
|
||||
|
||||
for (int i = 0; i < tm->tm_mon; ++i) {
|
||||
total_days += detail::getDaysOfMonth(year, i+1);
|
||||
}
|
||||
total_days += tm->tm_mday - 1; // tm_mday is 1-31
|
||||
std::int64_t total_hours = total_days*24 + tm->tm_hour;
|
||||
std::time_t total_seconds = (total_hours*60 + tm->tm_min)*60 + tm->tm_sec;
|
||||
|
||||
return total_seconds /*+ kSecondsFrom1990*/;
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_CHRONO_H
|
||||
25
tools/openocpp/include/openocpp/helpers/container.h
Normal file
25
tools/openocpp/include/openocpp/helpers/container.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_MAP_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_MAP_H
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace chargelab {
|
||||
namespace container {
|
||||
template <typename T, typename V>
|
||||
bool contains(T&& container, V const& value) {
|
||||
return container.find(value) != container.end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool containsAny(T&&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T, typename V, typename... Tail>
|
||||
bool containsAny(T&& container, V const& head, Tail... tail) {
|
||||
return contains(container, head) || containsAny(container, std::forward<Tail>(tail)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_MAP_H
|
||||
76
tools/openocpp/include/openocpp/helpers/file.h
Normal file
76
tools/openocpp/include/openocpp/helpers/file.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_FILE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_FILE_H
|
||||
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cctype>
|
||||
|
||||
namespace chargelab::file {
|
||||
namespace detail {
|
||||
template<typename CharType=char>
|
||||
class FileByteWriter : public ByteWriterInterface {
|
||||
public:
|
||||
FileByteWriter(FILE* file) : file_(file) {
|
||||
assert(file_ != nullptr);
|
||||
}
|
||||
|
||||
~FileByteWriter() {
|
||||
}
|
||||
|
||||
void write(const char *s, std::size_t length) override {
|
||||
std::fwrite(s, sizeof(CharType), length, file_);
|
||||
}
|
||||
|
||||
private:
|
||||
FILE* file_;
|
||||
std::array<CharType, 128> buffer_ {};
|
||||
std::size_t index_ = 0;
|
||||
};
|
||||
}
|
||||
|
||||
inline bool is_eof_ignore_whitespace(FILE* file) {
|
||||
while (true) {
|
||||
auto ch = std::fgetc(file);
|
||||
if (ch == EOF) {
|
||||
return true;
|
||||
}
|
||||
if (!std::isspace(ch)) {
|
||||
std::ungetc(ch, file);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> json_read_object_from_file(FILE* file) {
|
||||
if (is_eof_ignore_whitespace(file))
|
||||
return std::nullopt;
|
||||
|
||||
int ch;
|
||||
std::string line;
|
||||
while ((ch = std::fgetc(file)) != EOF) {
|
||||
if (ch == '\r' || ch == '\n')
|
||||
break;
|
||||
|
||||
line += (char)ch;
|
||||
}
|
||||
|
||||
auto const result = read_json_from_string<T>(line);
|
||||
if (!result.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing line as JSON: " << line;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void json_write_object_to_file(FILE* file, T&& value) {
|
||||
write_json(detail::FileByteWriter<char> {file}, value);
|
||||
std::fputc('\n', file);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_FILE_H
|
||||
1558
tools/openocpp/include/openocpp/helpers/json.h
Normal file
1558
tools/openocpp/include/openocpp/helpers/json.h
Normal file
File diff suppressed because it is too large
Load Diff
24
tools/openocpp/include/openocpp/helpers/optional.h
Normal file
24
tools/openocpp/include/openocpp/helpers/optional.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_OPTIONAL_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_OPTIONAL_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace chargelab::optional {
|
||||
template <typename T, typename C>
|
||||
inline void IfPresent(std::optional<T> const& container, C&& consumer) {
|
||||
if (container.has_value()) {
|
||||
consumer(container.value());
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T GetOrDefault(std::optional<T> const& currentValue, T const& defaultValue) {
|
||||
if (currentValue.has_value()) {
|
||||
return currentValue.value();
|
||||
} else {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_OPTIONAL_H
|
||||
40
tools/openocpp/include/openocpp/helpers/set.h
Normal file
40
tools/openocpp/include/openocpp/helpers/set.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_SET_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_SET_H
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace chargelab {
|
||||
namespace set {
|
||||
template <typename T, typename V>
|
||||
bool contains(std::vector<T> const& container, V const& value) {
|
||||
return std::find(container.begin(), container.end(), value) != container.end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool containsAny(std::vector<T> const&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T, typename V, typename... Tail>
|
||||
bool containsAny(std::vector<T> const& container, V const& head, Tail... tail) {
|
||||
return contains(container, head) || containsAny(container, std::forward<Tail>(tail)...);
|
||||
}
|
||||
|
||||
template <typename T, typename V>
|
||||
bool contains(std::set<T> const& container, V const& value) {
|
||||
return container.find(value) != container.end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool containsAny(std::set<T> const&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T, typename V, typename... Tail>
|
||||
bool containsAny(std::set<T> const& container, V const& head, Tail... tail) {
|
||||
return contains(container, head) || containsAny(container, std::forward<Tail>(tail)...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_SET_H
|
||||
161
tools/openocpp/include/openocpp/helpers/string.h
Normal file
161
tools/openocpp/include/openocpp/helpers/string.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STRING_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STRING_H
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <limits.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace chargelab::string {
|
||||
namespace detail {
|
||||
char const kHexCharacters[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D' ,'E', 'F'};
|
||||
}
|
||||
|
||||
inline bool BeginsWithIgnoreCaseAscii(std::string const& text, std::string const& prefix) {
|
||||
if (text.length() < prefix.length())
|
||||
return false;
|
||||
|
||||
auto text_it = text.begin();
|
||||
auto prefix_it = prefix.begin();
|
||||
while (prefix_it != prefix.end()) {
|
||||
auto text_ch = *(text_it++);
|
||||
auto prefix_ch = *(prefix_it++);
|
||||
if (std::tolower(text_ch) != std::tolower(prefix_ch)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool EqualsIgnoreCaseAscii(std::string const& lhs, std::string const& rhs) {
|
||||
return lhs.size() == rhs.size() && BeginsWithIgnoreCaseAscii(lhs, rhs);
|
||||
}
|
||||
|
||||
inline std::optional<int> ToInteger(std::string const& text) {
|
||||
if (text.empty())
|
||||
return std::nullopt;
|
||||
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
auto const result = strtol(text.c_str(), &end, 10);
|
||||
auto const parse_error = errno;
|
||||
if (end != text.c_str() + text.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (parse_error != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::optional<std::int64_t> ToInteger64(std::string const& text) {
|
||||
if (text.empty())
|
||||
return std::nullopt;
|
||||
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
auto const result = strtoll(text.c_str(), &end, 10);
|
||||
auto const parse_error = errno;
|
||||
if (end != text.c_str() + text.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (parse_error != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::optional<double> ToDouble(std::string const& text) {
|
||||
if (text.empty())
|
||||
return std::nullopt;
|
||||
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
auto const result = strtod(text.c_str(), &end);
|
||||
auto const parse_error = errno;
|
||||
if (end != text.c_str() + text.size()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (parse_error != 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value,
|
||||
std::string
|
||||
>::type ToHexValue(T value) {
|
||||
if (value == 0)
|
||||
return "0x0";
|
||||
|
||||
std::string result;
|
||||
if (value < 0) {
|
||||
value = -value;
|
||||
result = "-0x";
|
||||
} else {
|
||||
result = "0x";
|
||||
}
|
||||
|
||||
for (int i=0; i < (int)sizeof(T); i++) {
|
||||
auto const offset = (sizeof(T) - i - 1) * CHAR_BIT;
|
||||
auto const byte = (value >> offset) & 0xFF;
|
||||
result += detail::kHexCharacters[byte >> 4];
|
||||
result += detail::kHexCharacters[byte & 0xF];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string ToHexString(std::uint8_t const* begin, std::uint8_t const* end, char const* separator = " ") {
|
||||
std::string result;
|
||||
char const* ifs = "";
|
||||
for (auto it = begin; it != end; it++) {
|
||||
auto const byte = *it;
|
||||
result += ifs;
|
||||
result += detail::kHexCharacters[byte >> 4];
|
||||
result += detail::kHexCharacters[byte & 0xF];
|
||||
ifs = separator;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string ToHexString(std::uint8_t const* begin, std::size_t length, char const* separator = " ") {
|
||||
return ToHexString(begin, begin+length, separator);
|
||||
}
|
||||
|
||||
template <std::size_t N>
|
||||
inline std::string ToHexString(std::array<std::uint8_t, N> const& array, char const* separator = " ") {
|
||||
return ToHexString(array.data(), array.size(), separator);
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
inline void SplitVisitor(std::string const& text, std::string const& delimiter, F&& visitor) {
|
||||
std::size_t begin = 0;
|
||||
while (true) {
|
||||
auto it = text.find(delimiter, begin);
|
||||
if (it == std::string::npos) {
|
||||
visitor(text.substr(begin));
|
||||
break;
|
||||
}
|
||||
|
||||
visitor(text.substr(begin, it-begin));
|
||||
begin = it + delimiter.size();
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string zeroPad(std::string const& text, int length) {
|
||||
std::string result;
|
||||
result.resize(std::max(length - text.size(), (std::size_t)0), '0');
|
||||
return result + text;
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STRING_H
|
||||
251
tools/openocpp/include/openocpp/helpers/uri.h
Normal file
251
tools/openocpp/include/openocpp/helpers/uri.h
Normal file
@@ -0,0 +1,251 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_URI_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_URI_H
|
||||
|
||||
#include "openocpp/helpers/string.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
#include "openocpp/helpers/optional.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <regex>
|
||||
|
||||
namespace chargelab::uri {
|
||||
namespace detail {
|
||||
inline std::pair<std::string, std::optional<std::string>> SplitLeft(std::string const& text, std::string const& delimiter) {
|
||||
auto it = text.find_first_of(delimiter);
|
||||
if (it == std::string::npos)
|
||||
return std::make_pair(text, std::nullopt);
|
||||
|
||||
return std::make_pair(
|
||||
text.substr(0, it),
|
||||
std::make_optional(text.substr(it + delimiter.size()))
|
||||
);
|
||||
}
|
||||
|
||||
inline std::pair<std::optional<std::string>, std::string> SplitRight(std::string const& text, std::string const& delimiter) {
|
||||
auto result = SplitLeft(text, delimiter);
|
||||
if (result.second.has_value()) {
|
||||
return std::make_pair(std::make_optional(result.first), result.second.value());
|
||||
} else {
|
||||
return std::make_pair(std::nullopt, result.first);
|
||||
}
|
||||
}
|
||||
|
||||
int GetHexValue(char ch) {
|
||||
if (ch >= '0' && ch <= '9')
|
||||
return ch-'0';
|
||||
if (ch >= 'a' && ch <= 'f')
|
||||
return (ch-'a')+10;
|
||||
if (ch >= 'A' && ch <= 'F')
|
||||
return (ch-'A')+10;
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Update to ws/wss
|
||||
CHARGELAB_JSON_ENUM(WebsocketScheme,
|
||||
WS,
|
||||
WSS,
|
||||
)
|
||||
|
||||
struct WebsocketParts {
|
||||
WebsocketScheme scheme = WebsocketScheme::kValueNotFoundInEnum;
|
||||
std::string host {};
|
||||
int port = -1;
|
||||
std::string path {};
|
||||
CHARGELAB_JSON_INTRUSIVE(WebsocketParts, scheme, host, port, path)
|
||||
};
|
||||
|
||||
inline std::optional<WebsocketParts> ParseWebsocketUri(std::string const& uri) {
|
||||
WebsocketParts result {};
|
||||
|
||||
auto scheme_fragment = detail::SplitLeft(uri, "://");
|
||||
if (!scheme_fragment.second.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
auto scheme = scheme_fragment.first;
|
||||
if (string::EqualsIgnoreCaseAscii(scheme, "ws")) {
|
||||
result.scheme = WebsocketScheme::kWS;
|
||||
} else if (string::EqualsIgnoreCaseAscii(scheme, "wss")) {
|
||||
result.scheme = WebsocketScheme::kWSS;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Bad scheme in: " << uri;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto target_fragment = detail::SplitLeft(scheme_fragment.second.value(), "/");
|
||||
auto host_port_fragment = detail::SplitLeft(target_fragment.first, ":");
|
||||
if (host_port_fragment.second.has_value()) {
|
||||
auto port = string::ToInteger(host_port_fragment.second.value());
|
||||
if (!port.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing port text in: " << uri;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.port = port.value();
|
||||
} else {
|
||||
switch (result.scheme) {
|
||||
default:
|
||||
assert(false && "Unexpected websocket protocol");
|
||||
|
||||
case WebsocketScheme::kWS:
|
||||
result.port = 80;
|
||||
break;
|
||||
|
||||
case WebsocketScheme::kWSS:
|
||||
result.port = 443;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.host = host_port_fragment.first;
|
||||
result.path = "/" + optional::GetOrDefault<std::string>(target_fragment.second, "");
|
||||
CHARGELAB_LOG_MESSAGE(trace) << "Parsed websocket URI: " << result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CHARGELAB_JSON_ENUM(FtpScheme,
|
||||
FTP,
|
||||
FTPS,
|
||||
)
|
||||
|
||||
struct FtpParts {
|
||||
FtpScheme scheme = FtpScheme::kValueNotFoundInEnum;
|
||||
std::optional<std::string> username = std::nullopt;
|
||||
std::optional<std::string> password = std::nullopt;
|
||||
std::string host {};
|
||||
int port = -1;
|
||||
std::string path {};
|
||||
CHARGELAB_JSON_INTRUSIVE(FtpParts, scheme, username, password, host, port, path)
|
||||
};
|
||||
|
||||
inline std::optional<FtpParts> ParseFtpUri(std::string const& uri) {
|
||||
FtpParts result {};
|
||||
|
||||
auto scheme_fragment = detail::SplitLeft(uri, "://");
|
||||
if (!scheme_fragment.second.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
auto scheme = scheme_fragment.first;
|
||||
if (string::EqualsIgnoreCaseAscii(scheme, "ftp")) {
|
||||
result.scheme = FtpScheme::kFTP;
|
||||
} else if (string::EqualsIgnoreCaseAscii(scheme, "ftps")) {
|
||||
result.scheme = FtpScheme::kFTPS;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Bad scheme in: " << uri;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto target_fragment = detail::SplitLeft(scheme_fragment.second.value(), "/");
|
||||
auto credentials_fragment = detail::SplitRight(target_fragment.first, "@");
|
||||
auto host_port_fragment = detail::SplitLeft(credentials_fragment.second, ":");
|
||||
if (host_port_fragment.second.has_value()) {
|
||||
auto port = string::ToInteger(host_port_fragment.second.value());
|
||||
if (!port.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing port text in: " << uri;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.port = port.value();
|
||||
} else {
|
||||
result.port = 21;
|
||||
}
|
||||
|
||||
result.host = host_port_fragment.first;
|
||||
result.path = "/" + optional::GetOrDefault<std::string>(target_fragment.second, "");
|
||||
|
||||
if (credentials_fragment.first.has_value()) {
|
||||
auto username_password_fragment = detail::SplitLeft(credentials_fragment.first.value(), ":");
|
||||
result.username = username_password_fragment.first;
|
||||
if (username_password_fragment.second.has_value())
|
||||
result.password = username_password_fragment.second.value();
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(trace) << "Parsed FTP URI: " << result;
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Update to http/https
|
||||
CHARGELAB_JSON_ENUM(HttpScheme,
|
||||
Http,
|
||||
Https,
|
||||
)
|
||||
|
||||
struct HttpParts {
|
||||
HttpScheme scheme = HttpScheme::kValueNotFoundInEnum;
|
||||
std::string host {};
|
||||
int port = -1;
|
||||
std::string path {};
|
||||
};
|
||||
|
||||
inline std::optional<HttpParts> parseHttpUri(std::string const& uri) {
|
||||
HttpParts result {};
|
||||
|
||||
auto scheme_fragment = detail::SplitLeft(uri, "://");
|
||||
if (!scheme_fragment.second.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
auto scheme = scheme_fragment.first;
|
||||
if (string::EqualsIgnoreCaseAscii(scheme, "http")) {
|
||||
result.scheme = HttpScheme::kHttp;
|
||||
} else if (string::EqualsIgnoreCaseAscii(scheme, "https")) {
|
||||
result.scheme = HttpScheme::kHttps;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Bad scheme in: " << uri;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto target_fragment = detail::SplitLeft(scheme_fragment.second.value(), "/");
|
||||
auto host_port_fragment = detail::SplitLeft(target_fragment.first, ":");
|
||||
if (host_port_fragment.second.has_value()) {
|
||||
auto port = string::ToInteger(host_port_fragment.second.value());
|
||||
if (!port.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing port text in: " << uri;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
result.port = port.value();
|
||||
} else {
|
||||
switch (result.scheme) {
|
||||
default:
|
||||
assert(false && "Unexpected websocket protocol");
|
||||
|
||||
case HttpScheme::kHttp:
|
||||
result.port = 80;
|
||||
break;
|
||||
|
||||
case HttpScheme::kHttps:
|
||||
result.port = 443;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result.host = host_port_fragment.first;
|
||||
result.path = "/" + optional::GetOrDefault<std::string>(target_fragment.second, "");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string decodeUriComponent(std::string const& text) {
|
||||
std::string result;
|
||||
for (std::size_t i=0; i < text.size(); i++) {
|
||||
// Note: allowing for deviations here; % prefixes not representing a valid octet and ignored
|
||||
if (text[i] == '%' && i+2 < text.size()) {
|
||||
auto const ch1 = detail::GetHexValue(text[i+1]);
|
||||
auto const ch2 = detail::GetHexValue(text[i+2]);
|
||||
if (ch1 >= 0 && ch2 >= 0) {
|
||||
result += (char)((ch1 << 8) | ch2);
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result += text[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_URI_H
|
||||
@@ -0,0 +1,171 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_HASH_METHODS_MBEDTLS_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_HASH_METHODS_MBEDTLS_H
|
||||
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/helpers/string.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
#include "mbedtls/ssl.h"
|
||||
#include "mbedtls/base64.h"
|
||||
|
||||
namespace chargelab {
|
||||
class HashMethodsMbedTLS;
|
||||
|
||||
namespace detail {
|
||||
class HashCalculatorTypeMbedTLS {
|
||||
private:
|
||||
friend class ::chargelab::HashMethodsMbedTLS;
|
||||
|
||||
HashCalculatorTypeMbedTLS() {
|
||||
mbedtls_md_init(&context_);
|
||||
}
|
||||
|
||||
public:
|
||||
~HashCalculatorTypeMbedTLS() {
|
||||
mbedtls_md_free(&context_);
|
||||
}
|
||||
|
||||
bool update(unsigned char const* input, std::size_t ilen) {
|
||||
int err = mbedtls_md_update(&context_, input, ilen);
|
||||
if (err != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Update failed with error: " << err;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> finishBinary() {
|
||||
auto md_info = mbedtls_md_info_from_ctx(&context_);
|
||||
if (md_info == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - missing md_info in context";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> result;
|
||||
result.resize(mbedtls_md_get_size(md_info));
|
||||
|
||||
int err = mbedtls_md_finish(&context_, (unsigned char*)result.data());
|
||||
if (err != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Finish failed with error: " << err;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
err = mbedtls_md_starts(&context_);
|
||||
if (err != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - Failed resetting MbedTLS MD context";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<std::string> finishHex() {
|
||||
auto binary = finishBinary();
|
||||
if (!binary.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return string::ToHexString((unsigned char*)binary->data(), binary->size(), "");
|
||||
}
|
||||
|
||||
void reset() {
|
||||
finishBinary();
|
||||
}
|
||||
|
||||
private:
|
||||
mbedtls_md_context_t context_;
|
||||
};
|
||||
}
|
||||
|
||||
class HashMethodsMbedTLS {
|
||||
private:
|
||||
HashMethodsMbedTLS() {}
|
||||
|
||||
public:
|
||||
using hash_enum_type = mbedtls_md_type_t;
|
||||
using hash_calculator_type = detail::HashCalculatorTypeMbedTLS;
|
||||
|
||||
static constexpr hash_enum_type kHashTypeSHA256 = MBEDTLS_MD_SHA256;
|
||||
static constexpr hash_enum_type kHashTypeSHA512 = MBEDTLS_MD_SHA512;
|
||||
|
||||
static std::unique_ptr<hash_calculator_type> createCalculator(hash_enum_type hash_type) {
|
||||
auto md_info = mbedtls_md_info_from_type(hash_type);
|
||||
if (md_info == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Missing message digest info";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto result = std::unique_ptr<detail::HashCalculatorTypeMbedTLS> (new detail::HashCalculatorTypeMbedTLS());
|
||||
int err = mbedtls_md_setup(&result->context_, md_info, 0);
|
||||
if (err != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed setting up context - error code: " << err;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
err = mbedtls_md_starts(&result->context_);
|
||||
if (err != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed starting context - error code: " << err;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::optional<std::vector<uint8_t>> calculateHashBinary(
|
||||
hash_enum_type hash_type,
|
||||
unsigned char const* input,
|
||||
std::size_t ilen
|
||||
) {
|
||||
auto md_info = mbedtls_md_info_from_type(hash_type);
|
||||
if (md_info == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Missing message digest info";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> digest_buffer;
|
||||
digest_buffer.resize(mbedtls_md_get_size(md_info));
|
||||
auto ret = mbedtls_md(md_info, input, ilen, (unsigned char*)digest_buffer.data());
|
||||
if (ret != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed calculating message digest - error: " << ret;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return digest_buffer;
|
||||
}
|
||||
|
||||
static std::optional<std::string> calculateHashHex(
|
||||
hash_enum_type hash_type,
|
||||
unsigned char const* input,
|
||||
std::size_t ilen
|
||||
) {
|
||||
auto binary = calculateHashBinary(hash_type, input, ilen);
|
||||
if (!binary.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return string::ToHexString((unsigned char*)binary->data(), binary->size(), "");
|
||||
}
|
||||
|
||||
static std::optional<std::vector<uint8_t>> decodeBase64(std::string const& base64_text) {
|
||||
std::size_t olen = 0;
|
||||
auto ret = mbedtls_base64_decode(nullptr, 0, &olen, (const unsigned char*)base64_text.data(), base64_text.size());
|
||||
if (ret != 0 && ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed decoding base64 text/calculating size - error: " << ret;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> result;
|
||||
result.resize(olen);
|
||||
|
||||
ret = mbedtls_base64_decode(result.data(), result.size(), &olen, (const unsigned char*)base64_text.data(), base64_text.size());
|
||||
if (ret != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed decoding base64 text - error: " << ret;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_HASH_METHODS_MBEDTLS_H
|
||||
111
tools/openocpp/include/openocpp/implementation/logging_esp.cc
Normal file
111
tools/openocpp/include/openocpp/implementation/logging_esp.cc
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
|
||||
#include "esp_log.h"
|
||||
|
||||
namespace chargelab::logging {
|
||||
namespace {
|
||||
std::atomic<LogLevel> gLogLevel {LogLevel::trace};
|
||||
constexpr char const* kTag = "main";
|
||||
|
||||
std::recursive_mutex gMutex {};
|
||||
std::vector<std::shared_ptr<LoggingListenerFunction>> gListeners {};
|
||||
}
|
||||
|
||||
void SetLogLevel(LogLevel level) {
|
||||
gLogLevel = level;
|
||||
|
||||
switch (level) {
|
||||
case LogLevel::trace: esp_log_level_set(kTag, ESP_LOG_VERBOSE); break;
|
||||
case LogLevel::debug: esp_log_level_set(kTag, ESP_LOG_DEBUG); break;
|
||||
|
||||
default:
|
||||
case LogLevel::info: esp_log_level_set(kTag, ESP_LOG_INFO); break;
|
||||
|
||||
case LogLevel::warning: esp_log_level_set(kTag, ESP_LOG_WARN); break;
|
||||
case LogLevel::error: esp_log_level_set(kTag, ESP_LOG_ERROR); break;
|
||||
case LogLevel::fatal: esp_log_level_set(kTag, ESP_LOG_ERROR); break;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsLoggingEnabled(LogLevel level) {
|
||||
LogLevel const limit = gLogLevel;
|
||||
return static_cast<int>(level) >= static_cast<int>(limit);
|
||||
}
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
#define CHARGELAB_ESP_LOGGING_TEMPLATE(esp_macro) esp_macro(kTag, "[%s:%d(%s)] %s", metadata.file.data(), metadata.line, metadata.function.data(), message)
|
||||
#else
|
||||
#define CHARGELAB_ESP_LOGGING_TEMPLATE(esp_macro) esp_macro(kTag, "[%s] %s", metadata.function.data(), message)
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
void PrintLogMessage(LogMetadata const& metadata, char const* message) {
|
||||
switch (metadata.level) {
|
||||
case LogLevel::trace: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGV); break;
|
||||
case LogLevel::debug: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGD); break;
|
||||
case LogLevel::info: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGI); break;
|
||||
case LogLevel::warning: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGW); break;
|
||||
case LogLevel::error: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGE); break;
|
||||
|
||||
default:
|
||||
case LogLevel::fatal: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGE); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef CHARGELAB_ESP_LOGGING_TEMPLATE
|
||||
|
||||
void PrintLogMessage(LogMetadata const& metadata, std::string const& message) {
|
||||
detail::PrintLogMessage(metadata, message.data());
|
||||
|
||||
std::lock_guard lock {gMutex};
|
||||
if (!gListeners.empty()) {
|
||||
for (auto const& x : gListeners) {
|
||||
if (x != nullptr) {
|
||||
(*x)(metadata, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintLogMessage(LogMetadata const& metadata, std::string_view const& message) {
|
||||
detail::PrintLogMessage(metadata, message.data());
|
||||
|
||||
std::lock_guard lock {gMutex};
|
||||
if (!gListeners.empty()) {
|
||||
for (auto const& x : gListeners) {
|
||||
if (x != nullptr) {
|
||||
(*x)(metadata, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
|
||||
std::lock_guard lock {gMutex};
|
||||
for (auto const& x : gListeners) {
|
||||
if (x == callback) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gListeners.push_back(callback);
|
||||
}
|
||||
|
||||
void UnregisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
|
||||
std::lock_guard lock {gMutex};
|
||||
|
||||
auto initial_size = gListeners.size();
|
||||
gListeners.erase(
|
||||
std::remove_if(gListeners.begin(), gListeners.end(), [&](auto& x) {return x == callback;}),
|
||||
gListeners.end()
|
||||
);
|
||||
|
||||
if (initial_size - gListeners.size() > 1)
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
|
||||
}
|
||||
}
|
||||
138
tools/openocpp/include/openocpp/implementation/logging_stdout.cc
Normal file
138
tools/openocpp/include/openocpp/implementation/logging_stdout.cc
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/helpers/chrono.h"
|
||||
#include "openocpp/helpers/optional.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
|
||||
namespace chargelab::logging {
|
||||
namespace {
|
||||
std::atomic<LogLevel> gLogLevel {LogLevel::trace};
|
||||
|
||||
std::atomic<int> gRecursiveCounter {};
|
||||
std::recursive_mutex gMutex {};
|
||||
std::vector<std::shared_ptr<LoggingListenerFunction>> gListeners {};
|
||||
|
||||
class RaiiCounter {
|
||||
public:
|
||||
explicit RaiiCounter(std::atomic<int>& counter) : counter_{counter} {
|
||||
counter_++;
|
||||
}
|
||||
|
||||
~RaiiCounter() {
|
||||
counter_--;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<int>& counter_;
|
||||
};
|
||||
}
|
||||
|
||||
std::string_view ToString(LogLevel const& level) {
|
||||
switch (level) {
|
||||
case LogLevel::trace: return std::string_view {"trace"};
|
||||
case LogLevel::debug: return std::string_view {"debug"};
|
||||
case LogLevel::info: return std::string_view {"info"};
|
||||
case LogLevel::warning: return std::string_view {"warning"};
|
||||
case LogLevel::error: return std::string_view {"error"};
|
||||
|
||||
default:
|
||||
case LogLevel::fatal: return std::string_view {"fatal"};
|
||||
}
|
||||
}
|
||||
|
||||
void SetLogLevel(LogLevel level) {
|
||||
gLogLevel = level;
|
||||
}
|
||||
|
||||
bool IsLoggingEnabled(LogLevel level) {
|
||||
LogLevel const limit = gLogLevel;
|
||||
return static_cast<int>(level) >= static_cast<int>(limit);
|
||||
}
|
||||
|
||||
std::ostream& LogPrefix(LogMetadata const& metadata) {
|
||||
auto const now = std::chrono::system_clock::now();
|
||||
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
|
||||
auto const ts = chrono::ToString(static_cast<chargelab::SystemTimeMillis>(millis));
|
||||
|
||||
std::ios_base::fmtflags original_flags(std::cout.flags());
|
||||
|
||||
|
||||
std::cout << "[" << optional::GetOrDefault<std::string>(ts, "<bad timestamp>")
|
||||
<< "] [" << std::hex << std::this_thread::get_id()
|
||||
<< "] [" << ToString(metadata.level)
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
<< "] [" << metadata.file << ":" << metadata.line << "(" << metadata.function << ")] ";
|
||||
#else
|
||||
<< "] [" << "(" << metadata.function << ")] ";
|
||||
#endif
|
||||
|
||||
std::cout.flags(original_flags);
|
||||
return std::cout;
|
||||
}
|
||||
|
||||
void PrintLogMessage(LogMetadata const& metadata, std::string const& message) {
|
||||
LogPrefix(metadata) << message << std::endl;
|
||||
|
||||
std::lock_guard lock {gMutex};
|
||||
RaiiCounter counter {gRecursiveCounter};
|
||||
if (gRecursiveCounter > 2) {
|
||||
LogPrefix(metadata) << "Recursive PrintLogMessage call - suppressing messages" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gListeners.empty()) {
|
||||
for (auto const& x : gListeners) {
|
||||
if (x != nullptr) {
|
||||
(*x)(metadata, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrintLogMessage(LogMetadata const& metadata, std::string_view const& message) {
|
||||
LogPrefix(metadata) << message << std::endl;
|
||||
|
||||
std::lock_guard lock {gMutex};
|
||||
RaiiCounter counter {gRecursiveCounter};
|
||||
if (gRecursiveCounter > 2) {
|
||||
LogPrefix(metadata) << "Recursive PrintLogMessage call - suppressing messages" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!gListeners.empty()) {
|
||||
for (auto const& x : gListeners) {
|
||||
if (x != nullptr) {
|
||||
(*x)(metadata, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
|
||||
std::lock_guard lock {gMutex};
|
||||
for (auto const& x : gListeners) {
|
||||
if (x == callback) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
gListeners.push_back(callback);
|
||||
}
|
||||
|
||||
void UnregisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
|
||||
std::lock_guard lock {gMutex};
|
||||
|
||||
auto initial_size = gListeners.size();
|
||||
gListeners.erase(
|
||||
std::remove_if(gListeners.begin(), gListeners.end(), [&](auto& x) {return x == callback;}),
|
||||
gListeners.end()
|
||||
);
|
||||
|
||||
if (initial_size - gListeners.size() > 1)
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
|
||||
}
|
||||
}
|
||||
2162
tools/openocpp/include/openocpp/implementation/platform_esp.h
Normal file
2162
tools/openocpp/include/openocpp/implementation/platform_esp.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,200 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STANDARD_CHARGER_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STANDARD_CHARGER_H
|
||||
|
||||
#include "openocpp/interface/platform_interface.h"
|
||||
#include "openocpp/interface/station_interface.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/module/pending_messages_module.h"
|
||||
#include "openocpp/module/connector_status_module.h"
|
||||
#include "openocpp/module/get_logs_module.h"
|
||||
#include "openocpp/module/boot_notification_module.h"
|
||||
#include "openocpp/module/heartbeat_module.h"
|
||||
#include "openocpp/module/configuration_module.h"
|
||||
#include "openocpp/module/fallback_module.h"
|
||||
#include "openocpp/module/reset_module.h"
|
||||
#include "openocpp/module/firmware_update_module.h"
|
||||
#include "openocpp/module/power_management_module1_6.h"
|
||||
#include "openocpp/module/transaction_module1_6.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_message_handler.h"
|
||||
#include "openocpp/module/power_management_module2_0.h"
|
||||
#include "openocpp/module/transaction_module2_0.h"
|
||||
#include "openocpp/protocol/ocpp2_0/handlers/ocpp_message_handler.h"
|
||||
|
||||
#include "openocpp/implementation/hash_methods_mbedtls.h"
|
||||
|
||||
namespace chargelab {
|
||||
class StandardCharger {
|
||||
public:
|
||||
std::shared_ptr<PlatformInterface> platform;
|
||||
std::shared_ptr<StationInterface> station;
|
||||
std::shared_ptr<Settings> settings;
|
||||
|
||||
std::shared_ptr<PendingMessagesModule> pending_messages_module;
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_module;
|
||||
std::shared_ptr<GetLogsModule> get_logs_module;
|
||||
std::shared_ptr<BootNotificationModule> boot_notification_module;
|
||||
std::shared_ptr<HeartbeatModule> heartbeat_module;
|
||||
std::shared_ptr<ConfigurationModule> configuration_module;
|
||||
std::shared_ptr<FallbackModule> fallback_module;
|
||||
std::shared_ptr<ResetModule> reset_module;
|
||||
std::shared_ptr<FirmwareUpdateModule<HashMethodsMbedTLS>> firmware_update_module;
|
||||
|
||||
std::shared_ptr<PowerManagementModule1_6> power_management_module1_6;
|
||||
std::shared_ptr<TransactionModule1_6> transaction_module1_6;
|
||||
std::shared_ptr<ocpp1_6::OcppMessageHandler> message_handler1_6;
|
||||
|
||||
std::shared_ptr<PowerManagementModule2_0> power_management_module2_0;
|
||||
std::shared_ptr<TransactionModule2_0> transaction_module2_0;
|
||||
std::shared_ptr<ocpp2_0::OcppMessageHandler> message_handler2_0;
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
T notNull(T value) {
|
||||
assert(value != nullptr);
|
||||
return value;
|
||||
}
|
||||
|
||||
StandardCharger(
|
||||
std::shared_ptr<PlatformInterface> platform_interface,
|
||||
std::shared_ptr<StationInterface> station_interface
|
||||
)
|
||||
: platform(std::move(platform_interface)),
|
||||
station(std::move(station_interface))
|
||||
{
|
||||
settings = platform->getSettings();
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Initializing common modules...";
|
||||
pending_messages_module = std::make_shared<PendingMessagesModule>(
|
||||
notNull(settings),
|
||||
notNull(platform),
|
||||
platform->getStorage("transactions.dat")
|
||||
);
|
||||
connector_status_module = std::make_shared<ConnectorStatusModule>(
|
||||
notNull(settings),
|
||||
notNull(platform),
|
||||
notNull(station)
|
||||
);
|
||||
|
||||
get_logs_module = std::make_shared<GetLogsModule>(notNull(platform), notNull(pending_messages_module));
|
||||
boot_notification_module = std::make_shared<BootNotificationModule>(notNull(settings), notNull(platform));
|
||||
heartbeat_module = std::make_shared<HeartbeatModule>(notNull(settings), notNull(platform));
|
||||
configuration_module = std::make_shared<ConfigurationModule>(notNull(settings), notNull(platform));
|
||||
fallback_module = std::make_shared<FallbackModule>(notNull(platform));
|
||||
reset_module = std::make_shared<ResetModule>(notNull(settings), notNull(platform), notNull(connector_status_module));
|
||||
|
||||
// TODO: Maybe hash methods should move into platform?
|
||||
firmware_update_module = std::make_shared<FirmwareUpdateModule<HashMethodsMbedTLS>>(
|
||||
notNull(platform),
|
||||
notNull(reset_module),
|
||||
notNull(pending_messages_module),
|
||||
notNull(connector_status_module),
|
||||
notNull(station)
|
||||
);
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Initializing OCPP 1.6 modules...";
|
||||
power_management_module1_6 = std::make_shared<PowerManagementModule1_6>(
|
||||
notNull(settings),
|
||||
notNull(platform),
|
||||
notNull(station),
|
||||
platform->getPartition("pmjournal")
|
||||
);
|
||||
|
||||
transaction_module1_6 = std::make_shared<TransactionModule1_6>(
|
||||
notNull(platform),
|
||||
notNull(boot_notification_module),
|
||||
notNull(power_management_module1_6),
|
||||
notNull(pending_messages_module),
|
||||
notNull(connector_status_module),
|
||||
notNull(station)
|
||||
);
|
||||
|
||||
message_handler1_6 = std::make_shared<ocpp1_6::OcppMessageHandler>(
|
||||
notNull(settings),
|
||||
notNull(platform),
|
||||
std::vector<std::shared_ptr<AbstractModuleInterface>> {
|
||||
notNull(platform),
|
||||
notNull(boot_notification_module),
|
||||
notNull(heartbeat_module),
|
||||
notNull(configuration_module),
|
||||
notNull(reset_module),
|
||||
notNull(connector_status_module),
|
||||
notNull(pending_messages_module),
|
||||
notNull(firmware_update_module),
|
||||
notNull(get_logs_module),
|
||||
notNull(power_management_module1_6),
|
||||
notNull(transaction_module1_6),
|
||||
notNull(fallback_module)
|
||||
},
|
||||
// TODO: Get rid of this lambda; more expensive than a pointer? Also less clear what's being invoked at the call-site.
|
||||
[this]() { return boot_notification_module->registrationComplete(); }
|
||||
);
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Initializing OCPP 2.0 modules...";
|
||||
power_management_module2_0 = std::make_shared<PowerManagementModule2_0>(
|
||||
notNull(settings),
|
||||
notNull(platform),
|
||||
notNull(station),
|
||||
platform->getPartition("pmjournal")
|
||||
);
|
||||
|
||||
transaction_module2_0 = std::make_shared<TransactionModule2_0>(
|
||||
notNull(platform),
|
||||
notNull(boot_notification_module),
|
||||
notNull(power_management_module2_0),
|
||||
notNull(pending_messages_module),
|
||||
notNull(connector_status_module),
|
||||
notNull(station)
|
||||
);
|
||||
|
||||
message_handler2_0 = std::make_shared<ocpp2_0::OcppMessageHandler>(
|
||||
notNull(settings),
|
||||
notNull(platform),
|
||||
std::vector<std::shared_ptr<AbstractModuleInterface>> {
|
||||
platform,
|
||||
boot_notification_module,
|
||||
heartbeat_module,
|
||||
configuration_module,
|
||||
reset_module,
|
||||
connector_status_module,
|
||||
pending_messages_module,
|
||||
firmware_update_module,
|
||||
get_logs_module,
|
||||
power_management_module2_0,
|
||||
transaction_module2_0,
|
||||
fallback_module
|
||||
},
|
||||
// TODO: As above; remove this?
|
||||
[this]() { return boot_notification_module->registrationComplete(); }
|
||||
);
|
||||
}
|
||||
|
||||
void runStep() {
|
||||
message_handler1_6->runStep(platform->ocppConnection());
|
||||
message_handler2_0->runStep(platform->ocppConnection());
|
||||
}
|
||||
|
||||
void runLoop() {
|
||||
while (true) {
|
||||
message_handler1_6->runStep(platform->ocppConnection());
|
||||
message_handler2_0->runStep(platform->ocppConnection());
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
void addAfter(std::shared_ptr<T> module, Args&&... args) {
|
||||
message_handler1_6->addAfter(module, std::forward<Args>(args)...);
|
||||
message_handler2_0->addAfter(module, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
void addBefore(std::shared_ptr<T> module, Args&&... args) {
|
||||
message_handler1_6->addBefore(module, std::forward<Args>(args)...);
|
||||
message_handler2_0->addBefore(module, std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STANDARD_CHARGER_H
|
||||
109
tools/openocpp/include/openocpp/implementation/station_esp32.h
Normal file
109
tools/openocpp/include/openocpp/implementation/station_esp32.h
Normal file
@@ -0,0 +1,109 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STATION_ESP32_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STATION_ESP32_H
|
||||
|
||||
#include "openocpp/interface/station_interface.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/helpers/string.h"
|
||||
|
||||
#include "esp_ota_ops.h"
|
||||
|
||||
namespace chargelab {
|
||||
class StationEsp32 : public StationInterface {
|
||||
public:
|
||||
StationEsp32()
|
||||
: running_partition_(esp_ota_get_running_partition()),
|
||||
update_partition_(esp_ota_get_next_update_partition(nullptr))
|
||||
{
|
||||
assert(running_partition_ != nullptr);
|
||||
assert(update_partition_ != nullptr);
|
||||
ESP_ERROR_CHECK(esp_ota_mark_app_valid_cancel_rollback());
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Running partition: " << running_partition_->label;
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Update partition: " << update_partition_->label;
|
||||
}
|
||||
|
||||
std::string getActiveSlotId() override {
|
||||
std::array<uint8_t, sizeof(uint32_t)> buffer {};
|
||||
std::memcpy(buffer.data(), &running_partition_->address, buffer.size());
|
||||
return string::ToHexString(buffer, "");
|
||||
}
|
||||
|
||||
std::string getUpdateSlotId() override {
|
||||
std::array<uint8_t, sizeof(uint32_t)> buffer {};
|
||||
std::memcpy(buffer.data(), &update_partition_->address, buffer.size());
|
||||
return string::ToHexString(buffer, "");
|
||||
}
|
||||
|
||||
Result startUpdateProcess(std::size_t update_size) override {
|
||||
if (update_handle_ != 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Another firmware update operation was in progress";
|
||||
return Result::kFailed;
|
||||
}
|
||||
|
||||
if (update_size > update_partition_->size) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Update size exceeded partition size: " << update_size << " > " << update_partition_->size;
|
||||
return Result::kFailed;
|
||||
}
|
||||
|
||||
if (esp_err_t esp_status = esp_ota_begin(update_partition_, OTA_SIZE_UNKNOWN, &update_handle_) != ESP_OK) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed to start firmware update - error " << esp_status;
|
||||
return Result::kFailed;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Starting firmware update for partition: " << update_partition_->label;
|
||||
update_size_ = update_size;
|
||||
update_offset_ = 0;
|
||||
return Result::kSucceeded;
|
||||
}
|
||||
|
||||
Result processFirmwareChunk(const std::uint8_t *block, std::size_t size) override {
|
||||
if (update_handle_ == 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Process chunk called outside of a firmware update operation";
|
||||
return Result::kFailed;
|
||||
}
|
||||
|
||||
if (update_offset_ + size > update_size_) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Firmware update chunks exceeded update size";
|
||||
return Result::kFailed;
|
||||
}
|
||||
|
||||
if (esp_err_t esp_status = esp_ota_write(update_handle_, (const void*)block, size) != ESP_OK) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Firmware update write failed - error " << esp_status;
|
||||
esp_ota_abort(update_handle_);
|
||||
update_handle_ = 0;
|
||||
return Result::kFailed;
|
||||
}
|
||||
|
||||
update_size_ += size;
|
||||
return Result::kSucceeded;
|
||||
}
|
||||
|
||||
Result finishUpdateProcess(bool succeeded) override {
|
||||
if (update_handle_ == 0) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Finish update called outside of a firmware update operation";
|
||||
return Result::kFailed;
|
||||
}
|
||||
|
||||
if (succeeded) {
|
||||
esp_ota_end(update_handle_);
|
||||
esp_ota_set_boot_partition(update_partition_);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Update applied - the next boot will attempt to boot into the updated partition";
|
||||
} else {
|
||||
esp_ota_abort(update_handle_);
|
||||
}
|
||||
|
||||
update_handle_ = 0;
|
||||
return Result::kSucceeded;
|
||||
}
|
||||
|
||||
private:
|
||||
esp_partition_t const* running_partition_;
|
||||
esp_partition_t const* update_partition_;
|
||||
|
||||
esp_ota_handle_t update_handle_ = 0;
|
||||
std::size_t update_size_ = 0;
|
||||
std::size_t update_offset_ = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STATION_ESP32_H
|
||||
@@ -0,0 +1,452 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STATION_TEST_ESP32_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STATION_TEST_ESP32_H
|
||||
|
||||
#include "openocpp/implementation/station_esp32.h"
|
||||
#include "openocpp/interface/platform_interface.h"
|
||||
#include "openocpp/helpers/string.h"
|
||||
#include "openocpp/version.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
struct SimulatorConnectorState {
|
||||
bool connector_available {true};
|
||||
bool vehicle_connected {false};
|
||||
bool suspended_by_vehicle {false};
|
||||
bool suspended_by_charger {false};
|
||||
|
||||
std::optional<SteadyPointMillis> last_update = std::nullopt;
|
||||
double amps {48.0};
|
||||
double volts {220.0};
|
||||
double watt_hours {0.0};
|
||||
|
||||
// Note: not saved
|
||||
std::optional<double> limit = std::nullopt;
|
||||
bool charging_enabled {false};
|
||||
|
||||
CHARGELAB_JSON_INTRUSIVE(
|
||||
SimulatorConnectorState,
|
||||
connector_available,
|
||||
vehicle_connected,
|
||||
suspended_by_vehicle,
|
||||
suspended_by_charger,
|
||||
last_update,
|
||||
amps,
|
||||
volts,
|
||||
watt_hours
|
||||
)
|
||||
};
|
||||
|
||||
struct SimulatorState {
|
||||
charger::StationMetadata station_metadata {};
|
||||
std::map<ocpp2_0::EVSEType, charger::EvseMetadata> evse_metadata {};
|
||||
std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> connector_metadata {};
|
||||
std::map<ocpp2_0::EVSEType, SimulatorConnectorState> connector_state {};
|
||||
|
||||
// Note: connector state is not saved intentionally
|
||||
CHARGELAB_JSON_INTRUSIVE(SimulatorState, station_metadata, evse_metadata, connector_metadata, connector_state)
|
||||
};
|
||||
}
|
||||
|
||||
class StationTestEsp32 : public StationEsp32 {
|
||||
public:
|
||||
StationTestEsp32(std::shared_ptr<PlatformInterface> platform)
|
||||
: platform_(std::move(platform)),
|
||||
random_engine_ {std::random_device{}()}
|
||||
{
|
||||
assert(platform_ != nullptr);
|
||||
settings_ = platform_->getSettings();
|
||||
assert(settings_ != nullptr);
|
||||
|
||||
// TODO: Move these into SimulatorState so that they're persisted and can be configured?
|
||||
settings_->ChargerVendor.setValue("ChargeLab");
|
||||
settings_->ChargerModel.setValue("Simulator");
|
||||
settings_->ChargerSerialNumber.setValue("0000");
|
||||
settings_->ChargerFirmwareVersion.setValue(OPENOCPP_VERSION_TEXT);
|
||||
settings_->ChargerAccessPointSSID.setValue("Charger Simulator");
|
||||
|
||||
settings_->registerCustomSetting(saved_state_ = std::make_shared<SettingString>(
|
||||
[]() {
|
||||
detail::SimulatorState default_state;
|
||||
default_state.station_metadata = charger::StationMetadata {
|
||||
1
|
||||
};
|
||||
default_state.evse_metadata[ocpp2_0::EVSEType {1}] = charger::EvseMetadata {
|
||||
1,
|
||||
10560.0
|
||||
};
|
||||
default_state.connector_metadata[ocpp2_0::EVSEType {1, 1}] = charger::ConnectorMetadata {
|
||||
1,
|
||||
"cType1",
|
||||
1,
|
||||
10560.0,
|
||||
48
|
||||
};
|
||||
|
||||
return SettingMetadata {
|
||||
"SimulatorState",
|
||||
SettingConfig::rwPolicy(),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
write_json_to_string(default_state)
|
||||
};
|
||||
},
|
||||
[](auto const&) {return true;}
|
||||
));
|
||||
}
|
||||
|
||||
public:
|
||||
void simulateTap(ocpp1_6::IdToken token1_6, ocpp2_0::IdTokenType token2_0, int timeout_millis) {
|
||||
std::lock_guard lock {mutex_};
|
||||
rfid_action_ = std::make_pair(
|
||||
static_cast<SteadyPointMillis>(platform_->steadyClockNow() + timeout_millis),
|
||||
std::make_pair(token1_6, token2_0)
|
||||
);
|
||||
}
|
||||
|
||||
void updateMeasurements() {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
std::uniform_real_distribution<double> distribution(0.95, 1.05);
|
||||
for (auto& x : state_.connector_state) {
|
||||
if (x.first.id == 0 || (x.first.connectorId && x.first.connectorId.value() == 0))
|
||||
continue;
|
||||
|
||||
auto amps = 48.0;
|
||||
if (x.second.limit.has_value())
|
||||
amps = std::min(amps, x.second.limit.value());
|
||||
if (station_limit_.has_value())
|
||||
amps = std::min(amps, station_limit_.value());
|
||||
|
||||
x.second.amps = std::round((amps*distribution(random_engine_))*100.0)/100.0;
|
||||
x.second.volts = std::round((220.0*distribution(random_engine_))*100.0)/100.0;
|
||||
|
||||
auto const now = platform_->steadyClockNow();
|
||||
if (x.second.last_update.has_value()) {
|
||||
auto const delta = now - x.second.last_update.value();
|
||||
auto const delta_hours = delta/(1000.0 * 3600.0);
|
||||
x.second.watt_hours += x.second.amps*x.second.volts*delta_hours;
|
||||
}
|
||||
|
||||
x.second.last_update = now;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void updateState(Visitor&& visitor) {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
visitor(state_);
|
||||
saveIfModified();
|
||||
}
|
||||
|
||||
public:
|
||||
charger::StationMetadata getStationMetadata() override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
return state_.station_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::map<ocpp2_0::EVSEType, charger::EvseMetadata> getEvseMetadata() override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
return state_.evse_metadata;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> getConnectorMetadata() override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
return state_.connector_metadata;
|
||||
}
|
||||
|
||||
std::optional<charger::ConnectorStatus> pollConnectorStatus(const ocpp2_0::EVSEType &evse) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
if (state_.connector_metadata.find(evse) == state_.connector_metadata.end())
|
||||
return std::nullopt;
|
||||
|
||||
auto const& state = state_.connector_state[evse];
|
||||
return charger::ConnectorStatus {
|
||||
state.connector_available,
|
||||
state.vehicle_connected,
|
||||
state.charging_enabled,
|
||||
state.suspended_by_vehicle,
|
||||
state.suspended_by_charger,
|
||||
state.watt_hours,
|
||||
std::nullopt
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<ocpp1_6::SampledValue> pollMeterValues1_6(const std::optional<ocpp2_0::EVSEType> &evse) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
// Note: not supporting station level meter values here
|
||||
if (!evse.has_value())
|
||||
return {};
|
||||
if (state_.connector_metadata.find(evse.value()) == state_.connector_metadata.end())
|
||||
return {};
|
||||
|
||||
auto const& state = state_.connector_state[evse.value()];
|
||||
return {
|
||||
ocpp1_6::SampledValue {
|
||||
std::to_string(roundTo(state.watt_hours, 1)),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp1_6::Measurand::kEnergyActiveImportRegister,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp1_6::UnitOfMeasure::kWattHours
|
||||
},
|
||||
ocpp1_6::SampledValue {
|
||||
std::to_string(roundTo(state.amps, 1)),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp1_6::Measurand::kCurrentImport,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp1_6::UnitOfMeasure::kAmps
|
||||
},
|
||||
ocpp1_6::SampledValue {
|
||||
std::to_string(roundTo(state.volts, 1)),
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp1_6::Measurand::kVoltage,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp1_6::UnitOfMeasure::kVolts
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<ocpp2_0::SampledValueType>
|
||||
pollMeterValues2_0(const std::optional<ocpp2_0::EVSEType> &evse) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
// Note: not supporting station level meter values here
|
||||
if (!evse.has_value())
|
||||
return {};
|
||||
if (state_.connector_metadata.find(evse.value()) == state_.connector_metadata.end())
|
||||
return {};
|
||||
|
||||
auto const& state = state_.connector_state[evse.value()];
|
||||
return {
|
||||
ocpp2_0::SampledValueType {
|
||||
roundTo(state.watt_hours, 1),
|
||||
std::nullopt,
|
||||
ocpp2_0::MeasurandEnumType::kEnergy_Active_Import_Register,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp2_0::UnitOfMeasureType {"Wh"}
|
||||
},
|
||||
ocpp2_0::SampledValueType {
|
||||
roundTo(state.amps, 1),
|
||||
std::nullopt,
|
||||
ocpp2_0::MeasurandEnumType::kCurrent_Import,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp2_0::UnitOfMeasureType {"A"}
|
||||
},
|
||||
ocpp2_0::SampledValueType {
|
||||
roundTo(state.volts, 1),
|
||||
std::nullopt,
|
||||
ocpp2_0::MeasurandEnumType::kVoltage,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
ocpp2_0::UnitOfMeasureType {"V"}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void setChargingEnabled(const ocpp2_0::EVSEType &evse, bool value) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
if (state_.connector_metadata.find(evse) == state_.connector_metadata.end())
|
||||
return;
|
||||
|
||||
state_.connector_state[evse].charging_enabled = value;
|
||||
}
|
||||
|
||||
void setActiveChargePointMaxProfiles(std::vector<schedule_type1_6> const& active_schedules) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
// Note: ignoring phases
|
||||
std::optional<double> limit;
|
||||
for (auto const& x : active_schedules) {
|
||||
// Note: this should be handled here or the station should only accept a specific rate type
|
||||
if (x.first.csChargingProfiles.chargingSchedule.chargingRateUnit != ocpp1_6::ChargingRateUnitType::kA)
|
||||
continue;
|
||||
|
||||
if (!limit.has_value()) {
|
||||
limit = x.second.limit;
|
||||
} else {
|
||||
limit = std::min(limit.value(), x.second.limit);
|
||||
}
|
||||
}
|
||||
|
||||
station_limit_ = limit;
|
||||
}
|
||||
|
||||
void setActiveChargePointMaxProfiles(std::vector<schedule_type2_0> const& active_schedules) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
// Note: ignoring phases
|
||||
std::optional<double> limit;
|
||||
for (auto const& x : active_schedules) {
|
||||
// Note: profiles with multiple schedules are for ISO 15118 and aren't supported at the moment
|
||||
if (x.first.chargingProfile.chargingSchedule.size() != 1)
|
||||
continue;
|
||||
|
||||
auto const& schedule = x.first.chargingProfile.chargingSchedule.front();
|
||||
if (schedule.chargingRateUnit != ocpp2_0::ChargingRateUnitEnumType::kA)
|
||||
continue;
|
||||
|
||||
if (!limit.has_value()) {
|
||||
limit = x.second.limit;
|
||||
} else {
|
||||
limit = std::min(limit.value(), x.second.limit);
|
||||
}
|
||||
}
|
||||
|
||||
station_limit_ = limit;
|
||||
}
|
||||
|
||||
void setActiveEvseProfiles(int evse_id, std::vector<schedule_type1_6> const& active_schedules) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
// Note: ignoring phases
|
||||
std::optional<double> limit;
|
||||
for (auto const& x : active_schedules) {
|
||||
// Note: this should be handled here or the station should only accept a specific rate type
|
||||
if (x.first.csChargingProfiles.chargingSchedule.chargingRateUnit != ocpp1_6::ChargingRateUnitType::kA)
|
||||
continue;
|
||||
|
||||
if (!limit.has_value()) {
|
||||
limit = x.second.limit;
|
||||
} else {
|
||||
limit = std::min(limit.value(), x.second.limit);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& x : state_.connector_state) {
|
||||
if (x.first.id != evse_id)
|
||||
continue;
|
||||
|
||||
x.second.limit = limit;
|
||||
}
|
||||
}
|
||||
|
||||
void setActiveEvseProfiles(int evse_id, std::vector<schedule_type2_0> const& active_schedules) override {
|
||||
std::lock_guard lock {mutex_};
|
||||
loadIfModified();
|
||||
|
||||
// Note: ignoring phases
|
||||
std::optional<double> limit;
|
||||
for (auto const& x : active_schedules) {
|
||||
// Note: profiles with multiple schedules are for ISO 15118 and aren't supported at the moment
|
||||
if (x.first.chargingProfile.chargingSchedule.size() != 1)
|
||||
continue;
|
||||
|
||||
auto const& schedule = x.first.chargingProfile.chargingSchedule.front();
|
||||
if (schedule.chargingRateUnit != ocpp2_0::ChargingRateUnitEnumType::kA)
|
||||
continue;
|
||||
|
||||
if (!limit.has_value()) {
|
||||
limit = x.second.limit;
|
||||
} else {
|
||||
limit = std::min(limit.value(), x.second.limit);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& x : state_.connector_state) {
|
||||
if (x.first.id != evse_id)
|
||||
continue;
|
||||
|
||||
x.second.limit = limit;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<ocpp1_6::IdToken> readToken1_6() override {
|
||||
std::lock_guard lock {mutex_};
|
||||
if (!rfid_action_.has_value())
|
||||
return std::nullopt;
|
||||
if (platform_->steadyClockNow() - rfid_action_->first >= 0) {
|
||||
rfid_action_ = std::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return rfid_action_->second.first;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<ocpp2_0::IdTokenType> readToken2_0() override {
|
||||
std::lock_guard lock {mutex_};
|
||||
if (!rfid_action_.has_value())
|
||||
return std::nullopt;
|
||||
if (platform_->steadyClockNow() - rfid_action_->first >= 0) {
|
||||
rfid_action_ = std::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return rfid_action_->second.second;
|
||||
}
|
||||
|
||||
private:
|
||||
void loadIfModified() {
|
||||
auto const next_saved_state = saved_state_->getValue();
|
||||
if (last_saved_state_ == next_saved_state)
|
||||
return;
|
||||
|
||||
auto const data = read_json_from_string<detail::SimulatorState>(next_saved_state);
|
||||
if (!data.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading saved state: " << next_saved_state;
|
||||
} else {
|
||||
state_ = data.value();
|
||||
}
|
||||
|
||||
last_saved_state_ = next_saved_state;
|
||||
}
|
||||
|
||||
void saveIfModified() {
|
||||
auto const next_saved_state = write_json_to_string(state_);
|
||||
if (last_saved_state_ == next_saved_state)
|
||||
return;
|
||||
|
||||
saved_state_->setValue(next_saved_state);
|
||||
last_saved_state_ = next_saved_state;
|
||||
}
|
||||
|
||||
static double roundTo(double value, int decimal_places) {
|
||||
auto const factor = (double)std::pow(10.0, decimal_places);
|
||||
return std::round(value/factor) * factor;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<PlatformInterface> platform_;
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::default_random_engine random_engine_;
|
||||
|
||||
std::mutex mutex_ {};
|
||||
std::optional<double> station_limit_ = std::nullopt;
|
||||
std::optional<std::pair<SteadyPointMillis, std::pair<ocpp1_6::IdToken, ocpp2_0::IdTokenType>>> rfid_action_;
|
||||
|
||||
detail::SimulatorState state_;
|
||||
std::shared_ptr<SettingString> saved_state_;
|
||||
std::string last_saved_state_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STATION_TEST_ESP32_H
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_CHARGING_PROFILE_PERSISTENCE_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_CHARGING_PROFILE_PERSISTENCE_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include "openocpp/protocol/ocpp1_6/types/charging_profile.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
namespace chargelab {
|
||||
struct ActiveProfileIdsPersistenceInfo {
|
||||
std::unordered_set<int> activeRestartProtectionProfileIds;
|
||||
std::optional<SystemTimeMillis> lastUpdated;
|
||||
};
|
||||
|
||||
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(
|
||||
ActiveProfileIdsPersistenceInfo,
|
||||
activeRestartProtectionProfileIds,
|
||||
lastUpdated
|
||||
)
|
||||
|
||||
struct ChargingProfilePersistenceInfo {
|
||||
std::vector<pair<int, ocpp1_6::ChargingProfile>> profiles;
|
||||
int criticalWriteCredits;
|
||||
};
|
||||
|
||||
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(
|
||||
ChargingProfilePersistenceInfo,
|
||||
profiles,
|
||||
criticalWriteCredits
|
||||
)
|
||||
|
||||
class ChargingProfilePersistenceInterface {
|
||||
public:
|
||||
virtual ~ChargingProfilePersistenceInterface() = default;
|
||||
|
||||
virtual std::optional<ChargingProfilePersistenceInfo> loadProfilePersistenceInfo() = 0;
|
||||
virtual bool saveProfilePersistenceInfo(ChargingProfilePersistenceInfo const & profiles) = 0;
|
||||
|
||||
virtual std::optional<ActiveProfileIdsPersistenceInfo> loadActiveProfileIdsInfo() = 0;
|
||||
virtual bool saveActiveProfileIdsInfo(ActiveProfileIdsPersistenceInfo const & persistence_info) = 0;
|
||||
|
||||
virtual bool saveWriteCredits(int write_credits) = 0;
|
||||
|
||||
virtual int getCreditIncreaseIntervalSeconds() = 0; // this is for making unit test easier
|
||||
virtual int getActiveIdsUpdateIntervalSeconds() = 0; // for making unit test easier
|
||||
virtual int getCheckProfileExpiryIntervalSeconds() = 0; // for making unit test easier
|
||||
virtual int getFlashCreditInterval() = 0; // how many difference between the memory value and disk value, for making unit test easier
|
||||
virtual int getFlashCreditMaximumLimit() = 0; // the biggest credit value allowed to write to disk, for making unit test easier
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_CHARGING_PROFILE_PERSISTENCE_INTERFACE_H
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_FETCH_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_FETCH_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <functional>
|
||||
|
||||
namespace chargelab {
|
||||
class FetchInterface {
|
||||
public:
|
||||
enum class Result {
|
||||
kSucceeded,
|
||||
kFailed
|
||||
};
|
||||
|
||||
public:
|
||||
virtual ~FetchInterface() = default;
|
||||
virtual Result fetch(std::string const& uri, std::function<Result(std::uint8_t const*, std::size_t)> const& process_chunk) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_FETCH_INTERFACE_H
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_SYSTEM_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_SYSTEM_INTERFACE_H
|
||||
|
||||
#include "openocpp/model/system_types.h"
|
||||
#include "openocpp/interface/element/storage_interface.h"
|
||||
#include "openocpp/interface/element/flash_block_interface.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace chargelab {
|
||||
class SystemInterface {
|
||||
public:
|
||||
virtual ~SystemInterface() = default;
|
||||
|
||||
virtual SystemTimeMillis systemClockNow() = 0;
|
||||
virtual SteadyPointMillis steadyClockNow() = 0;
|
||||
|
||||
virtual void setSystemClock(SystemTimeMillis now) = 0;
|
||||
virtual void resetSoft() = 0;
|
||||
virtual void resetHard() = 0;
|
||||
|
||||
virtual bool isClockOutOfSync() = 0;
|
||||
|
||||
virtual std::unique_ptr<chargelab::StorageInterface> getStorage(std::string const& file_name) = 0;
|
||||
virtual std::unique_ptr<chargelab::FlashBlockInterface> getPartition(std::string const& label) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_SYSTEM_INTERFACE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_UPLOAD_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_UPLOAD_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace chargelab {
|
||||
class UploadInterface {
|
||||
public:
|
||||
enum class Result {
|
||||
kSucceeded,
|
||||
kFailed
|
||||
};
|
||||
|
||||
public:
|
||||
virtual ~UploadInterface() = default;
|
||||
virtual Result upload(std::string const& uri, std::vector<uint8_t> const& content, bool append, std::function<void(std::size_t)> const& report_progress) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_UPLOAD_INTERFACE_H
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_BYTE_READER_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_BYTE_READER_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
namespace chargelab {
|
||||
class ByteReaderInterface {
|
||||
public:
|
||||
virtual std::size_t read(char* s, std::size_t n) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_BYTE_READER_INTERFACE_H
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_BYTE_WRITER_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_BYTE_WRITER_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
|
||||
namespace chargelab {
|
||||
class ByteWriterInterface {
|
||||
public:
|
||||
virtual void write(char const *s, std::size_t count) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_BYTE_WRITER_INTERFACE_H
|
||||
@@ -0,0 +1,18 @@
|
||||
#ifndef ESP32_FIRMWARE_TEST_FLASH_BLOCK_INTERFACE_H
|
||||
#define ESP32_FIRMWARE_TEST_FLASH_BLOCK_INTERFACE_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace chargelab {
|
||||
class FlashBlockInterface {
|
||||
public:
|
||||
virtual ~FlashBlockInterface() = default;
|
||||
|
||||
virtual bool read(std::size_t src_offset, void *dst, std::size_t size) const = 0;
|
||||
virtual bool write(std::size_t dst_offset, void *src, std::size_t size) = 0;
|
||||
virtual bool erase() = 0;
|
||||
[[nodiscard]] virtual std::size_t size() const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //ESP32_FIRMWARE_TEST_FLASH_BLOCK_INTERFACE_H
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_REST_CONNECTION_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_REST_CONNECTION_INTERFACE_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace chargelab {
|
||||
class RestConnectionInterface {
|
||||
public:
|
||||
virtual ~RestConnectionInterface() = default;
|
||||
|
||||
virtual int getStatusCode() = 0;
|
||||
virtual std::size_t getContentLength() = 0;
|
||||
|
||||
/**
|
||||
* Sets a custom header. Must be called before open().
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
virtual void setHeader(std::string const& key, std::string const& value) = 0;
|
||||
|
||||
/**
|
||||
* Opens the connection to the server. Must be called after setting any headers for this request.
|
||||
* @param content_length length of data that will be written to the server
|
||||
* @return true on success, otherwise false
|
||||
*/
|
||||
virtual bool open(std::size_t content_length) = 0;
|
||||
|
||||
/**
|
||||
* Completes the REST request and reads the status code/content length from the server. This must be called
|
||||
* after any POST payload and headers are written to the connection.
|
||||
* @return true on success, otherwise false
|
||||
*/
|
||||
virtual bool send() = 0;
|
||||
|
||||
/**
|
||||
* @return -1 on error, otherwise number bytes read
|
||||
*/
|
||||
virtual int read(char* buffer, int len) = 0;
|
||||
|
||||
/**
|
||||
* @return a negative status code on error, otherwise number bytes read
|
||||
*/
|
||||
virtual int write(char const* buffer, int len) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_REST_CONNECTION_INTERFACE_H
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STORAGE_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STORAGE_INTERFACE_H
|
||||
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
|
||||
namespace chargelab {
|
||||
class StorageInterface {
|
||||
public:
|
||||
virtual ~StorageInterface() = default;
|
||||
|
||||
virtual bool read(std::function<bool(FILE*)> const& function) = 0;
|
||||
virtual bool write(std::function<bool(FILE*)> const& function) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STORAGE_INTERFACE_H
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_WEBSOCKET_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_WEBSOCKET_INTERFACE_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "openocpp/helpers/json.h"
|
||||
#include "openocpp/interface/element/byte_writer_interface.h"
|
||||
|
||||
namespace chargelab {
|
||||
class WebsocketInterface {
|
||||
public:
|
||||
virtual ~WebsocketInterface() = default;
|
||||
|
||||
virtual bool isConnected() = 0;
|
||||
virtual std::optional<std::string> getSubprotocol() = 0;
|
||||
|
||||
virtual std::size_t pendingMessages() = 0;
|
||||
virtual std::optional<std::string> pollMessages() = 0;
|
||||
virtual void sendCustom(std::function<void(ByteWriterInterface&)> payload) = 0;
|
||||
|
||||
template <typename T>
|
||||
void sendJson(T const& value) {
|
||||
sendCustom([&](ByteWriterInterface& stream) {
|
||||
write_json(stream, value);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_WEBSOCKET_INTERFACE_H
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_PLATFORM_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_PLATFORM_INTERFACE_H
|
||||
|
||||
#include "openocpp/model/system_types.h"
|
||||
#include "openocpp/interface/element/rest_connection_interface.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/interface/component/upload_interface.h"
|
||||
#include "openocpp/interface/element/websocket_interface.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/module/common_templates.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace chargelab {
|
||||
enum class RestMethod {
|
||||
kGet,
|
||||
kPost,
|
||||
kPut,
|
||||
kPatch,
|
||||
kDelete,
|
||||
kHead,
|
||||
kOptions
|
||||
};
|
||||
|
||||
struct SignatureAndHash {
|
||||
const unsigned char *hash = nullptr;
|
||||
std::size_t hash_len = 0;
|
||||
|
||||
const unsigned char *sig = nullptr;
|
||||
std::size_t sig_len = 0;
|
||||
};
|
||||
|
||||
class PlatformInterface :
|
||||
public SystemInterface,
|
||||
public ServiceStatefulGeneral
|
||||
{
|
||||
public:
|
||||
virtual std::shared_ptr<Settings> getSettings() = 0;
|
||||
virtual std::shared_ptr<WebsocketInterface> ocppConnection() = 0;
|
||||
virtual std::shared_ptr<RestConnectionInterface> restRequest(RestMethod method, std::string const& uri) = 0;
|
||||
|
||||
virtual bool verifyManufacturerCertificate(
|
||||
std::string const& pem,
|
||||
std::optional<SignatureAndHash> const& check_sha256
|
||||
) = 0;
|
||||
|
||||
virtual std::shared_ptr<RestConnectionInterface> getRequest(std::string const& uri) {
|
||||
return restRequest(RestMethod::kGet, uri);
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<RestConnectionInterface> putRequest(std::string const& uri) {
|
||||
return restRequest(RestMethod::kPut, uri);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_PLATFORM_INTERFACE_H
|
||||
274
tools/openocpp/include/openocpp/interface/station_interface.h
Normal file
274
tools/openocpp/include/openocpp/interface/station_interface.h
Normal file
@@ -0,0 +1,274 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_STATION_INTERFACE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_STATION_INTERFACE_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/charge_point_error_code.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/sampled_value.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/charging_schedule_period.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
|
||||
#include "openocpp/protocol/ocpp2_0/types/sampled_value_type.h"
|
||||
#include "openocpp/protocol/ocpp2_0/types/evse_type.h"
|
||||
#include "openocpp/protocol/ocpp2_0/messages/set_charging_profile.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace chargelab {
|
||||
namespace charger {
|
||||
// TODO: Move these into nested classes under StationInterface?
|
||||
struct FaultedStatus1_6 {
|
||||
ocpp1_6::ChargePointErrorCode errorCode = ocpp1_6::ChargePointErrorCode::kValueNotFoundInEnum;
|
||||
std::optional<ocpp1_6::CiString50Type> info;
|
||||
std::optional<ocpp1_6::CiString255Type> vendorId;
|
||||
std::optional<ocpp1_6::CiString50Type> vendorErrorCode;
|
||||
CHARGELAB_JSON_INTRUSIVE(FaultedStatus1_6, errorCode, info, vendorId, vendorErrorCode)
|
||||
};
|
||||
|
||||
struct FaultedStatus2_0 {
|
||||
// TODO
|
||||
CHARGELAB_JSON_INTRUSIVE_EMPTY(FaultedStatus2_0)
|
||||
};
|
||||
|
||||
struct FaultedStatus {
|
||||
FaultedStatus1_6 status1_6;
|
||||
FaultedStatus2_0 status2_0;
|
||||
CHARGELAB_JSON_INTRUSIVE(FaultedStatus, status1_6, status2_0)
|
||||
};
|
||||
|
||||
// TODO: Need to add type information and flags to enable an implementation to enable/disable connectors (if,
|
||||
// for example, multiple physical connectors are present but only one can be used at a time).
|
||||
struct ConnectorStatus {
|
||||
bool connector_available;
|
||||
bool vehicle_connected;
|
||||
bool charging_enabled;
|
||||
bool suspended_by_vehicle;
|
||||
bool suspended_by_charger;
|
||||
double meter_watt_hours;
|
||||
std::optional<FaultedStatus> faulted_status = std::nullopt;
|
||||
CHARGELAB_JSON_INTRUSIVE(ConnectorStatus, connector_available, vehicle_connected, charging_enabled, suspended_by_vehicle,
|
||||
suspended_by_charger, faulted_status)
|
||||
};
|
||||
|
||||
struct ConnectorMetadata {
|
||||
/**
|
||||
* OCPP 1.6 connector ID
|
||||
*/
|
||||
int connector_id1_6;
|
||||
std::string connector_type;
|
||||
int supply_phases;
|
||||
double power_max_watts;
|
||||
double power_max_amps;
|
||||
CHARGELAB_JSON_INTRUSIVE(ConnectorMetadata, connector_id1_6, connector_type, supply_phases, power_max_watts, power_max_amps)
|
||||
};
|
||||
|
||||
struct EvseMetadata {
|
||||
int supply_phases;
|
||||
double power_max_watts;
|
||||
CHARGELAB_JSON_INTRUSIVE(EvseMetadata, supply_phases, power_max_watts)
|
||||
};
|
||||
|
||||
struct StationMetadata {
|
||||
int supply_phases;
|
||||
CHARGELAB_JSON_INTRUSIVE(StationMetadata, supply_phases)
|
||||
};
|
||||
}
|
||||
|
||||
class StationInterface {
|
||||
public:
|
||||
using schedule_type1_6 = std::pair<ocpp1_6::SetChargingProfileReq, ocpp1_6::ChargingSchedulePeriod>;
|
||||
using schedule_type2_0 = std::pair<ocpp2_0::SetChargingProfileRequest, ocpp2_0::ChargingSchedulePeriodType>;
|
||||
|
||||
enum class Result {
|
||||
kSucceeded,
|
||||
kFailed
|
||||
};
|
||||
|
||||
public:
|
||||
virtual ~StationInterface() = default;
|
||||
|
||||
/**
|
||||
* Returns the station metadata required by OCPP 2.0.1.
|
||||
*
|
||||
* @return station metadata
|
||||
*/
|
||||
virtual charger::StationMetadata getStationMetadata() = 0;
|
||||
|
||||
/**
|
||||
* Returns the EVSE metadata required by OCPP 2.0.1. The connectorId field should not be set for all entries in
|
||||
* the returned map.
|
||||
*
|
||||
* @return EVSE metadata
|
||||
*/
|
||||
virtual std::map<ocpp2_0::EVSEType, charger::EvseMetadata> getEvseMetadata() = 0;
|
||||
|
||||
/**
|
||||
* Returns the connector metadata containing the OCPP 1.6 connectorId mappings and the information required by
|
||||
* OCPP 2.0.1. The connectorId field should be set for all entries in the returned map.
|
||||
*
|
||||
* @return connector metadata
|
||||
*/
|
||||
virtual std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> getConnectorMetadata() = 0;
|
||||
|
||||
/**
|
||||
* Polls the status of a particular connector. Note that this call is expected to be provided with a connector
|
||||
* ID - when absent this call should always return an empty optional.
|
||||
*
|
||||
* @param evse
|
||||
* @return a non-empty optional with the connector status if the connector exists, otherwise an empty optional
|
||||
*/
|
||||
virtual std::optional<charger::ConnectorStatus> pollConnectorStatus(ocpp2_0::EVSEType const& evse) = 0;
|
||||
|
||||
/**
|
||||
* Polls the meter values for the station, EVSE, or connector if they're available.
|
||||
*
|
||||
* @param evse an empty optional for station meter values, an EVSE with no connector for EVSE meter values, and
|
||||
* an EVSE/connector for connector meter values.
|
||||
* @return the meter values data if available, otherwise an empty vector
|
||||
*/
|
||||
virtual std::vector<ocpp1_6::SampledValue> pollMeterValues1_6(std::optional<ocpp2_0::EVSEType> const& evse) = 0;
|
||||
|
||||
/**
|
||||
* Polls the meter values for the station, EVSE, or connector if they're available.
|
||||
*
|
||||
* @param evse an empty optional for station meter values, an EVSE with no connector for EVSE meter values, and
|
||||
* an EVSE/connector for connector meter values.
|
||||
* @return the meter values data if available, otherwise an empty vector
|
||||
*/
|
||||
virtual std::vector<ocpp2_0::SampledValueType> pollMeterValues2_0(std::optional<ocpp2_0::EVSEType> const& evse) = 0;
|
||||
|
||||
/**
|
||||
* Enables/disables charging on a particular connector.
|
||||
*
|
||||
* @param evse
|
||||
* @param value true to enable charging, otherwise false
|
||||
*/
|
||||
virtual void setChargingEnabled(ocpp2_0::EVSEType const& evse, bool value) = 0;
|
||||
|
||||
// Note: providing the charger with the active profile and period explicitly for the following reasons:
|
||||
// - If a TxProfile limits based on Amps and a ChargePointMaxProfile limits based on Watts the interpretation
|
||||
// will need to be left to the vendor. Do they apply the limit based on the maximum voltage or a live reading?
|
||||
// If the reading fluctuates does the implementation switch from one limit to another?
|
||||
// - A vendor *may* want to communicate more details to a user. For example, they may want to display the
|
||||
// current profile limits (or other associated metadata) on screen.
|
||||
// This method will be called whenever the active schedule changes.
|
||||
|
||||
/**
|
||||
* Sets the active OCPP 1.6 charging schedules that were assigned to the charger.
|
||||
*
|
||||
* @param active_schedules the active schedule periods and their associated charging profiles
|
||||
*/
|
||||
virtual void setActiveChargePointMaxProfiles(std::vector<schedule_type1_6> const& active_schedules) = 0;
|
||||
|
||||
/**
|
||||
* Sets the active OCPP 2.0 charging schedules that were assigned to the charger.
|
||||
*
|
||||
* @param active_schedules the active schedule periods and their associated charging profiles
|
||||
*/
|
||||
virtual void setActiveChargePointMaxProfiles(std::vector<schedule_type2_0> const& active_schedules) = 0;
|
||||
|
||||
/**
|
||||
* Sets the active OCPP 1.6 charging schedules that were assigned to a specific EVSE ID.
|
||||
*
|
||||
* @param active_schedules the active schedule periods and their associated charging profiles
|
||||
*/
|
||||
virtual void setActiveEvseProfiles(int evse_id, std::vector<schedule_type1_6> const& active_schedules) = 0;
|
||||
|
||||
/**
|
||||
* Sets the active OCPP 2.0 charging schedules that were assigned to a specific EVSE ID.
|
||||
*
|
||||
* @param active_schedules the active schedule periods and their associated charging profiles
|
||||
*/
|
||||
virtual void setActiveEvseProfiles(int evse_id, std::vector<schedule_type2_0> const& active_schedules) = 0;
|
||||
|
||||
/**
|
||||
* Reads an OCPP 1.6 ID token that was presented to the station. This method may either return the token once
|
||||
* every interaction, or it may return the IdToken for as long as the RFID tag is detected by the RFID reader.
|
||||
* If the latter approach is used the running transaction module is expected to only respond to changes in
|
||||
* output.
|
||||
*
|
||||
* @return the IdToken value associated with an RFID (or other local) interaction
|
||||
*/
|
||||
[[nodiscard]] virtual std::optional<ocpp1_6::IdToken> readToken1_6() = 0;
|
||||
|
||||
/**
|
||||
* Returns an OCPP 2.0 ID token that was presented to the station. This method may either return the token once
|
||||
* every interaction, or it may return the IdToken for as long as the RFID tag is detected by the RFID reader.
|
||||
* If the latter approach is used the running transaction module is expected to only respond to changes in
|
||||
* output.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] virtual std::optional<ocpp2_0::IdTokenType> readToken2_0() = 0;
|
||||
|
||||
/**
|
||||
* Gets the active firmware slot (generally some kind of partition ID). This is compared to the update slot to
|
||||
* determine whether or not the firmware update succeeded.
|
||||
*
|
||||
* @return active slot ID
|
||||
*/
|
||||
virtual std::string getActiveSlotId() = 0;
|
||||
|
||||
/**
|
||||
* Starts a firmware update operation. Must be paired with a terminating finishUpdateProcess call on success or
|
||||
* failure.
|
||||
*
|
||||
* @param update_size
|
||||
* @return kSucceeded on success, otherwise kFailed
|
||||
*/
|
||||
virtual Result startUpdateProcess(std::size_t update_size) = 0;
|
||||
|
||||
/**
|
||||
* Process an incoming block of data as part of a firmware update, generally writing that block to the update
|
||||
* partition. May only be called during a firmware update operation (after a call to startUpdateProcess but
|
||||
* before the associated call to finishUpdateProcess).
|
||||
*
|
||||
* @param block
|
||||
* @param size
|
||||
* @return kSucceeded on success, otherwise kFailed
|
||||
*/
|
||||
virtual Result processFirmwareChunk(std::uint8_t const* block, std::size_t size) = 0;
|
||||
|
||||
/**
|
||||
* Gets the update slot ID (generally some kind of partition ID). This is compared against the active slot ID
|
||||
* after an update completes to determine whether or not the firmware update succeeded. May only be called
|
||||
* during a firmware update operation (after a call to startUpdateProcess but before the associated call to
|
||||
* finishUpdateProcess).
|
||||
*
|
||||
* @return update slot ID
|
||||
*/
|
||||
virtual std::string getUpdateSlotId() = 0;
|
||||
|
||||
/**
|
||||
* Finishes a firmware update. If succeeded == true and this call returns kSucceeded then the next restart is
|
||||
* expected to attempt to boot into the updated firmware. If succeeded == false or this call returns kFailed
|
||||
* then the next restart is expected to boot into the active firmware slot. A return value of kFailed when
|
||||
* provided with a succeeded == false flag is treated as an error state and the next restart is expected to boot
|
||||
* into the active firmware slot;
|
||||
*
|
||||
* @param succeeded
|
||||
* @return kSucceeded on success, otherwise kFailed
|
||||
*/
|
||||
virtual Result finishUpdateProcess(bool succeeded) = 0;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Helper method to lookup an OCPP 1.6 connector ID based on the provided metadata.
|
||||
*
|
||||
* @param id an OCPP 1.6 connector ID
|
||||
* @return the associated EVSE if the connector was found, otherwise std::nullopt
|
||||
*/
|
||||
virtual std::optional<ocpp2_0::EVSEType> lookupConnectorId1_6(int id) {
|
||||
for (auto const& entry : getConnectorMetadata()) {
|
||||
if (entry.second.connector_id1_6 == id)
|
||||
return entry.first;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_STATION_INTERFACE_H
|
||||
11
tools/openocpp/include/openocpp/model/system_types.h
Normal file
11
tools/openocpp/include/openocpp/model/system_types.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_SYSTEM_TYPES_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_SYSTEM_TYPES_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace chargelab {
|
||||
enum SystemTimeMillis : std::int64_t {};
|
||||
enum SteadyPointMillis : std::int64_t {};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_SYSTEM_TYPES_H
|
||||
79
tools/openocpp/include/openocpp/module/abstract_module.h
Normal file
79
tools/openocpp/include/openocpp/module/abstract_module.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_ABSTRACT_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_ABSTRACT_MODULE_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_request_handler.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_response_handler.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_service.h"
|
||||
#include "openocpp/protocol/ocpp2_0/handlers/abstract_request_handler.h"
|
||||
#include "openocpp/protocol/ocpp2_0/handlers/abstract_response_handler.h"
|
||||
#include "openocpp/protocol/ocpp2_0/handlers/abstract_service.h"
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
class PureServiceInterface {
|
||||
public:
|
||||
virtual void runUnconditionally() {
|
||||
};
|
||||
};
|
||||
|
||||
struct ConcreteImplementationsOCPP1_6 {
|
||||
ocpp1_6::AbstractService* service;
|
||||
ocpp1_6::AbstractRequestHandler* request_handler;
|
||||
ocpp1_6::AbstractResponseHandler* response_handler;
|
||||
};
|
||||
|
||||
struct ConcreteImplementationsOCPP2_0 {
|
||||
ocpp2_0::AbstractService* service;
|
||||
ocpp2_0::AbstractRequestHandler* request_handler;
|
||||
ocpp2_0::AbstractResponseHandler* response_handler;
|
||||
};
|
||||
|
||||
struct ConcreteImplementations {
|
||||
ConcreteImplementationsOCPP1_6 ocpp1_6;
|
||||
ConcreteImplementationsOCPP2_0 ocpp2_0;
|
||||
PureServiceInterface* pure_service;
|
||||
};
|
||||
}
|
||||
|
||||
class AbstractModuleInterface {
|
||||
public:
|
||||
virtual ~AbstractModuleInterface() = default;
|
||||
virtual detail::ConcreteImplementations getImplementations() = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class AbstractModuleBase : public AbstractModuleInterface {
|
||||
public:
|
||||
detail::ConcreteImplementations getImplementations() override {
|
||||
auto parent_this = static_cast<T*>(this);
|
||||
|
||||
detail::ConcreteImplementations result {};
|
||||
PopulateImplementation(result.ocpp1_6.service, parent_this);
|
||||
PopulateImplementation(result.ocpp1_6.request_handler, parent_this);
|
||||
PopulateImplementation(result.ocpp1_6.response_handler, parent_this);
|
||||
PopulateImplementation(result.ocpp2_0.service, parent_this);
|
||||
PopulateImplementation(result.ocpp2_0.request_handler, parent_this);
|
||||
PopulateImplementation(result.ocpp2_0.response_handler, parent_this);
|
||||
PopulateImplementation(result.pure_service, parent_this);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename U>
|
||||
static typename std::enable_if<std::is_base_of<U, T>::value, U*>::type ConvertToBase(T* ptr) {
|
||||
return static_cast<U*>(ptr);
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
static typename std::enable_if<!std::is_base_of<U, T>::value, U*>::type ConvertToBase(T*) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
static void PopulateImplementation(U*& implementation, T* ptr) {
|
||||
implementation = ConvertToBase<U>(ptr);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_ABSTRACT_MODULE_H
|
||||
@@ -0,0 +1,699 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_BOOT_NOTIFICATION_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_BOOT_NOTIFICATION_MODULE_H
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/common/operation_holder.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
namespace chargelab {
|
||||
class BootNotificationModule : public ServiceStatefulGeneral
|
||||
{
|
||||
/* OCPP 1.6 Section 4.2 on Pending registration statuses:
|
||||
*
|
||||
* The Central System MAY also return a Pending registration status to indicate that it wants to retrieve or set
|
||||
* certain information on the Charge Point before the Central System will accept the Charge Point. If the Central
|
||||
* System returns the Pending status, the communication channel SHOULD NOT be closed by either the Charge
|
||||
* Point or the Central System. The Central System MAY send request messages to retrieve information from the
|
||||
* Charge Point or change its configuration. The Charge Point SHOULD respond to these messages. The Charge
|
||||
* Point SHALL NOT send request messages to the Central System unless it has been instructed by the Central
|
||||
* System to do so with a TriggerMessage.req request.
|
||||
*/
|
||||
|
||||
private:
|
||||
static constexpr const int kSecurityProfileMigrationResetGracePeriodMillis = 5*1000;
|
||||
|
||||
public:
|
||||
BootNotificationModule(
|
||||
std::shared_ptr<Settings> settings,
|
||||
std::shared_ptr<SystemInterface> const& system_interface
|
||||
)
|
||||
: settings_(std::move(settings)),
|
||||
system_interface_(system_interface),
|
||||
pending_boot_notification_req_ {system_interface}
|
||||
{
|
||||
assert(system_interface_ != nullptr);
|
||||
|
||||
if (settings_->hasPendingTransition(SettingTransitionType::kConnection)) {
|
||||
auto timeout = system_interface_->steadyClockNow() + settings_->ConnectionTransitionTimeout.getValue()*1000;
|
||||
if (settings_->startTransition(SettingTransitionType::kConnection)) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Starting connection transition";
|
||||
connection_transition_timeout_ = static_cast<SteadyPointMillis>(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
auto const reason_text = settings_->CustomBootReason.getValue();
|
||||
if (!reason_text.empty())
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Custom boot reason was: " << settings_->CustomBootReason.getValue();
|
||||
}
|
||||
|
||||
~BootNotificationModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting BootNotificationModule";
|
||||
}
|
||||
|
||||
public:
|
||||
bool registrationComplete() {
|
||||
return registration_complete_;
|
||||
}
|
||||
|
||||
private:
|
||||
// OCPP 1.6 implementation
|
||||
void runStep(ocpp1_6::OcppRemote &remote) override {
|
||||
// Clear this flag if present
|
||||
settings_->CustomBootReason.setValue("");
|
||||
|
||||
runStepImpl([&]() {
|
||||
// TODO: Improved validation/length safety (truncate?)
|
||||
ocpp1_6::BootNotificationReq req {};
|
||||
req.chargePointVendor = ocpp1_6::CiString20Type(settings_->ChargerVendor.getValue());
|
||||
req.chargePointModel = ocpp1_6::CiString20Type(settings_->ChargerModel.getValue());
|
||||
req.chargePointSerialNumber = ocpp1_6::CiString25Type(settings_->ChargerSerialNumber.getValue());
|
||||
req.firmwareVersion = ocpp1_6::CiString50Type(settings_->ChargerFirmwareVersion.getValue());
|
||||
if (auto iccid = settings_->ChargerICCID.getValue(); !iccid.empty())
|
||||
req.iccid = ocpp1_6::CiString20Type(iccid);
|
||||
if (auto imsi = settings_->ChargerIMSI.getValue(); !imsi.empty())
|
||||
req.imsi = ocpp1_6::CiString20Type(imsi);
|
||||
if (auto meter_serial_number = settings_->ChargerMeterSerialNumber.getValue(); !meter_serial_number.empty())
|
||||
req.meterSerialNumber = ocpp1_6::CiString25Type(meter_serial_number);
|
||||
if (auto meter_type = settings_->ChargerMeterType.getValue(); !meter_type.empty())
|
||||
req.meterType = ocpp1_6::CiString25Type(meter_type);
|
||||
|
||||
return remote.sendBootNotificationReq(req);
|
||||
});
|
||||
}
|
||||
|
||||
void onBootNotificationRsp(
|
||||
const std::string &unique_id,
|
||||
const ocpp1_6::ResponseMessage<ocpp1_6::BootNotificationRsp> &rsp
|
||||
) override {
|
||||
if (pending_boot_notification_req_ == unique_id) {
|
||||
pending_boot_notification_req_ = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<ocpp1_6::BootNotificationRsp>(rsp)) {
|
||||
auto const& value = std::get<ocpp1_6::BootNotificationRsp>(rsp);
|
||||
auto const& ts = value.currentTime.getTimestamp();
|
||||
|
||||
if (ts.has_value()) {
|
||||
system_interface_->setSystemClock(ts.value());
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Invalid BootNotification response timestamp: "
|
||||
<< value.currentTime;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "BootNotification response was: " << value;
|
||||
switch (value.status) {
|
||||
case ocpp1_6::RegistrationStatus::kAccepted:
|
||||
requested_next_boot_notification_req_ = std::nullopt;
|
||||
settings_->HeartbeatInterval.setValue(value.interval);
|
||||
break;
|
||||
|
||||
case ocpp1_6::RegistrationStatus::kValueNotFoundInEnum:
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Invalid BootNotification response status for request ID: " << unique_id;
|
||||
[[fallthrough]];
|
||||
|
||||
case ocpp1_6::RegistrationStatus::kPending:
|
||||
case ocpp1_6::RegistrationStatus::kRejected:
|
||||
requested_next_boot_notification_req_ = static_cast<SteadyPointMillis> (
|
||||
system_interface_->steadyClockNow() + value.interval*1000
|
||||
);
|
||||
break;
|
||||
}
|
||||
switch (value.status) {
|
||||
case ocpp1_6::RegistrationStatus::kRejected:
|
||||
case ocpp1_6::RegistrationStatus::kValueNotFoundInEnum:
|
||||
registration_complete_ = false;
|
||||
allow_ocpp_calls_ = false;
|
||||
break;
|
||||
|
||||
case ocpp1_6::RegistrationStatus::kPending:
|
||||
registration_complete_ = false;
|
||||
allow_ocpp_calls_ = true;
|
||||
break;
|
||||
|
||||
case ocpp1_6::RegistrationStatus::kAccepted:
|
||||
registration_complete_ = true;
|
||||
allow_ocpp_calls_ = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to BootNotification request: " << std::get<ocpp1_6::CallError> (rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
|
||||
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
|
||||
switch (req.requestedMessage) {
|
||||
case ocpp1_6::MessageTrigger::kBootNotification:
|
||||
// Note: deviating from the 2.0.1 specification and allowing boot notification trigger messages
|
||||
// after registration completed in 1.6 here.
|
||||
force_boot_notification_req_ = true;
|
||||
return ocpp1_6::TriggerMessageRsp{ocpp1_6::TriggerMessageStatus::kAccepted};
|
||||
|
||||
default:
|
||||
case ocpp1_6::MessageTrigger::kValueNotFoundInEnum:
|
||||
case ocpp1_6::MessageTrigger::kDiagnosticsStatusNotification:
|
||||
case ocpp1_6::MessageTrigger::kFirmwareStatusNotification:
|
||||
case ocpp1_6::MessageTrigger::kHeartbeat:
|
||||
case ocpp1_6::MessageTrigger::kMeterValues:
|
||||
case ocpp1_6::MessageTrigger::kStatusNotification:
|
||||
if (!registrationComplete()) {
|
||||
return unauthorizedError1_6();
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStartTransactionRsp>>
|
||||
onRemoteStartTransactionReq(const ocpp1_6::RemoteStartTransactionReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStopTransactionRsp>>
|
||||
onRemoteStopTransactionReq(const ocpp1_6::RemoteStopTransactionReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::CancelReservationRsp>>
|
||||
onCancelReservationReq(const ocpp1_6::CancelReservationReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeAvailabilityRsp>>
|
||||
onChangeAvailabilityReq(const ocpp1_6::ChangeAvailabilityReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeConfigurationRsp>>
|
||||
onChangeConfigurationReq(const ocpp1_6::ChangeConfigurationReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ClearCacheRsp>>
|
||||
onClearCacheReq(const ocpp1_6::ClearCacheReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ClearChargingProfileRsp>>
|
||||
onClearChargingProfileReq(const ocpp1_6::ClearChargingProfileReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::DataTransferRsp>>
|
||||
onDataTransferReq(const ocpp1_6::DataTransferReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetCompositeScheduleRsp>>
|
||||
onGetCompositeScheduleReq(const ocpp1_6::GetCompositeScheduleReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetConfigurationRsp>>
|
||||
onGetConfigurationReq(const ocpp1_6::GetConfigurationReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetDiagnosticsRsp>>
|
||||
onGetDiagnosticsReq(const ocpp1_6::GetDiagnosticsReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetLocalListVersionRsp>>
|
||||
onGetLocalListVersionReq(const ocpp1_6::GetLocalListVersionReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ReserveNowRsp>>
|
||||
onReserveNowReq(const ocpp1_6::ReserveNowReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ResetRsp>>
|
||||
onResetReq(const ocpp1_6::ResetReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::SendLocalListRsp>>
|
||||
onSendLocalListReq(const ocpp1_6::SendLocalListReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::SetChargingProfileRsp>>
|
||||
onSetChargingProfileReq(const ocpp1_6::SetChargingProfileReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UnlockConnectorRsp>>
|
||||
onUnlockConnectorReq(const ocpp1_6::UnlockConnectorReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UpdateFirmwareRsp>>
|
||||
onUpdateFirmwareReq(const ocpp1_6::UpdateFirmwareReq&) override {
|
||||
return unauthorizedCallHandler1_6();
|
||||
}
|
||||
|
||||
// OCPP 2.0.1 implementation
|
||||
void runStep(ocpp2_0::OcppRemote &remote) override {
|
||||
auto reason = ocpp2_0::BootReasonEnumType::kPowerUp;
|
||||
|
||||
auto const reason_text = settings_->CustomBootReason.getValue();
|
||||
if (!reason_text.empty()) {
|
||||
reason = ocpp2_0::BootReasonEnumType::from_string(reason_text);
|
||||
} else if (force_boot_notification_req_) {
|
||||
reason = ocpp2_0::BootReasonEnumType::kTriggered;
|
||||
}
|
||||
|
||||
runStepImpl([&]() {
|
||||
ocpp2_0::BootNotificationRequest req {};
|
||||
req.reason = reason;
|
||||
req.chargingStation.serialNumber = settings_->ChargerSerialNumber.getValue();
|
||||
req.chargingStation.model = settings_->ChargerModel.getValue();
|
||||
req.chargingStation.vendorName = settings_->ChargerVendor.getValue();
|
||||
req.chargingStation.firmwareVersion = settings_->ChargerFirmwareVersion.getValue();
|
||||
|
||||
auto modem = ocpp2_0::ModemType {};
|
||||
if (auto iccid = settings_->ChargerICCID.getValue(); !iccid.empty())
|
||||
modem.iccid = iccid;
|
||||
if (auto imsi = settings_->ChargerIMSI.getValue(); !imsi.empty())
|
||||
modem.imsi = imsi;
|
||||
if (modem.iccid.has_value() || modem.imsi.has_value())
|
||||
req.chargingStation.modem = std::move(modem);
|
||||
|
||||
return remote.sendBootNotificationReq(req);
|
||||
});
|
||||
}
|
||||
|
||||
void onBootNotificationRsp(
|
||||
const std::string &unique_id,
|
||||
const ocpp2_0::ResponseMessage<ocpp2_0::BootNotificationResponse> &rsp
|
||||
) override {
|
||||
if (pending_boot_notification_req_ == unique_id) {
|
||||
pending_boot_notification_req_ = kNoOperation;
|
||||
settings_->CustomBootReason.setValue("");
|
||||
|
||||
if (std::holds_alternative<ocpp2_0::BootNotificationResponse>(rsp)) {
|
||||
auto const& value = std::get<ocpp2_0::BootNotificationResponse>(rsp);
|
||||
auto const& ts = value.currentTime.getTimestamp();
|
||||
|
||||
if (ts.has_value()) {
|
||||
system_interface_->setSystemClock(ts.value());
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Invalid BootNotification response timestamp: "
|
||||
<< value.currentTime;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "BootNotification response was: " << value;
|
||||
switch (value.status) {
|
||||
case ocpp2_0::RegistrationStatusEnumType::kAccepted:
|
||||
requested_next_boot_notification_req_ = std::nullopt;
|
||||
settings_->HeartbeatInterval.setValue(value.interval);
|
||||
break;
|
||||
|
||||
case ocpp2_0::RegistrationStatusEnumType::kValueNotFoundInEnum:
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Invalid BootNotification response status for request ID: " << unique_id;
|
||||
[[fallthrough]];
|
||||
|
||||
case ocpp2_0::RegistrationStatusEnumType::kPending:
|
||||
case ocpp2_0::RegistrationStatusEnumType::kRejected:
|
||||
requested_next_boot_notification_req_ = static_cast<SteadyPointMillis> (
|
||||
system_interface_->steadyClockNow() + value.interval*1000
|
||||
);
|
||||
break;
|
||||
}
|
||||
switch (value.status) {
|
||||
case ocpp2_0::RegistrationStatusEnumType::kRejected:
|
||||
case ocpp2_0::RegistrationStatusEnumType::kValueNotFoundInEnum:
|
||||
registration_complete_ = false;
|
||||
allow_ocpp_calls_ = false;
|
||||
break;
|
||||
|
||||
case ocpp2_0::RegistrationStatusEnumType::kPending:
|
||||
registration_complete_ = false;
|
||||
allow_ocpp_calls_ = true;
|
||||
break;
|
||||
|
||||
case ocpp2_0::RegistrationStatusEnumType::kAccepted:
|
||||
registration_complete_ = true;
|
||||
allow_ocpp_calls_ = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to BootNotification request: " << std::get<ocpp2_0::CallError> (rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
|
||||
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest& req) override {
|
||||
switch (req.requestedMessage) {
|
||||
case ocpp2_0::MessageTriggerEnumType::kBootNotification:
|
||||
if (!registration_complete_) {
|
||||
force_boot_notification_req_ = true;
|
||||
return ocpp2_0::TriggerMessageResponse{ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
|
||||
} else {
|
||||
// F06.FR.17
|
||||
return ocpp2_0::TriggerMessageResponse{ocpp2_0::TriggerMessageStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
default:
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CancelReservationResponse>>
|
||||
onCancelReservationReq(const ocpp2_0::CancelReservationRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CertificateSignedResponse>>
|
||||
onCertificateSignedReq(const ocpp2_0::CertificateSignedRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ChangeAvailabilityResponse>>
|
||||
onChangeAvailabilityReq(const ocpp2_0::ChangeAvailabilityRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearCacheResponse>>
|
||||
onClearCacheReq(const ocpp2_0::ClearCacheRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearChargingProfileResponse>>
|
||||
onClearChargingProfileReq(const ocpp2_0::ClearChargingProfileRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearDisplayMessageResponse>>
|
||||
onClearDisplayMessageReq(const ocpp2_0::ClearDisplayMessageRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearVariableMonitoringResponse>>
|
||||
onClearVariableMonitoringReq(const ocpp2_0::ClearVariableMonitoringRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CostUpdatedResponse>>
|
||||
onCostUpdatedReq(const ocpp2_0::CostUpdatedRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CustomerInformationResponse>>
|
||||
onCustomerInformationReq(const ocpp2_0::CustomerInformationRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::DataTransferResponse>>
|
||||
onDataTransferReq(const ocpp2_0::DataTransferRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::DeleteCertificateResponse>>
|
||||
onDeleteCertificateReq(const ocpp2_0::DeleteCertificateRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetBaseReportResponse>>
|
||||
onGetBaseReportReq(const ocpp2_0::GetBaseReportRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetChargingProfilesResponse>>
|
||||
onGetChargingProfilesReq(const ocpp2_0::GetChargingProfilesRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetCompositeScheduleResponse>>
|
||||
onGetCompositeScheduleReq(const ocpp2_0::GetCompositeScheduleRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetDisplayMessagesResponse>>
|
||||
onGetDisplayMessagesReq(const ocpp2_0::GetDisplayMessagesRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetInstalledCertificateIdsResponse>>
|
||||
onGetInstalledCertificateIdsReq(const ocpp2_0::GetInstalledCertificateIdsRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetLocalListVersionResponse>>
|
||||
onGetLocalListVersionReq(const ocpp2_0::GetLocalListVersionRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetLogResponse>>
|
||||
onGetLogReq(const ocpp2_0::GetLogRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetMonitoringReportResponse>>
|
||||
onGetMonitoringReportReq(const ocpp2_0::GetMonitoringReportRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetReportResponse>>
|
||||
onGetReportReq(const ocpp2_0::GetReportRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetTransactionStatusResponse>>
|
||||
onGetTransactionStatusReq(const ocpp2_0::GetTransactionStatusRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetVariablesResponse>>
|
||||
onGetVariablesReq(const ocpp2_0::GetVariablesRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::InstallCertificateResponse>>
|
||||
onInstallCertificateReq(const ocpp2_0::InstallCertificateRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::PublishFirmwareResponse>>
|
||||
onPublishFirmwareReq(const ocpp2_0::PublishFirmwareRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::RequestStartTransactionResponse>>
|
||||
onRequestStartTransactionReq(const ocpp2_0::RequestStartTransactionRequest&) override {
|
||||
// B02.FR.05
|
||||
if (!registration_complete_) {
|
||||
return ocpp2_0::RequestStartTransactionResponse {ocpp2_0::RequestStartStopStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::RequestStopTransactionResponse>>
|
||||
onRequestStopTransactionReq(const ocpp2_0::RequestStopTransactionRequest&) override {
|
||||
// B02.FR.05
|
||||
if (!registration_complete_) {
|
||||
return ocpp2_0::RequestStopTransactionResponse {ocpp2_0::RequestStartStopStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ReserveNowResponse>>
|
||||
onReserveNowReq(const ocpp2_0::ReserveNowRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ResetResponse>>
|
||||
onResetReq(const ocpp2_0::ResetRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SendLocalListResponse>>
|
||||
onSendLocalListReq(const ocpp2_0::SendLocalListRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetChargingProfileResponse>>
|
||||
onSetChargingProfileReq(const ocpp2_0::SetChargingProfileRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetDisplayMessageResponse>>
|
||||
onSetDisplayMessageReq(const ocpp2_0::SetDisplayMessageRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetMonitoringBaseResponse>>
|
||||
onSetMonitoringBaseReq(const ocpp2_0::SetMonitoringBaseRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetMonitoringLevelResponse>>
|
||||
onSetMonitoringLevelReq(const ocpp2_0::SetMonitoringLevelRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetNetworkProfileResponse>>
|
||||
onSetNetworkProfileReq(const ocpp2_0::SetNetworkProfileRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetVariableMonitoringResponse>>
|
||||
onSetVariableMonitoringReq(const ocpp2_0::SetVariableMonitoringRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetVariablesResponse>>
|
||||
onSetVariablesReq(const ocpp2_0::SetVariablesRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UnlockConnectorResponse>>
|
||||
onUnlockConnectorReq(const ocpp2_0::UnlockConnectorRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UnpublishFirmwareResponse>>
|
||||
onUnpublishFirmwareReq(const ocpp2_0::UnpublishFirmwareRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UpdateFirmwareResponse>>
|
||||
onUpdateFirmwareReq(const ocpp2_0::UpdateFirmwareRequest&) override {
|
||||
return unauthorizedCallHandler2_0();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void runStepImpl(T&& sendBootNotification) {
|
||||
auto const now = system_interface_->steadyClockNow();
|
||||
if (connection_transition_timeout_.has_value()) {
|
||||
if (allow_ocpp_calls_) {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Connection transition completed - committing";
|
||||
connection_transition_timeout_ = std::nullopt;
|
||||
settings_->commit(SettingTransitionType::kConnection);
|
||||
} else if (now >= connection_transition_timeout_.value()) {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Connection transition failed - rolling back to previous settings";
|
||||
settings_->rollback(SettingTransitionType::kConnection);
|
||||
settings_->saveIfModified();
|
||||
system_interface_->resetHard();
|
||||
}
|
||||
}
|
||||
|
||||
// Note: only updating the security profile when first connecting to the back-end
|
||||
if (!security_profile_checked_ && allow_ocpp_calls_ && !settings_->hasRunningTransition(SettingTransitionType::kConnection)) {
|
||||
auto const activeNetworkProfileSlot = settings_->ActiveNetworkProfile.getValue();
|
||||
auto const activeNetworkProfile = settings_->NetworkConnectionProfiles.transitionCurrentValue(activeNetworkProfileSlot);
|
||||
if (activeNetworkProfile.has_value() &&
|
||||
activeNetworkProfile->securityProfile > settings_->SecurityProfile.getValue()) {
|
||||
// A05.FR.06
|
||||
for (std::size_t i = 0; i < settings_->NetworkConnectionProfiles.transitionSize(); i++) {
|
||||
auto const value = settings_->NetworkConnectionProfiles.transitionCurrentValue(i);
|
||||
if (!value.has_value())
|
||||
continue;
|
||||
if (value->securityProfile >= activeNetworkProfile->securityProfile)
|
||||
continue;
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Removing profile " << i << ": " << value;
|
||||
settings_->NetworkConnectionProfiles.forceCurrentValue(i, std::nullopt);
|
||||
|
||||
char const* ifs = "";
|
||||
std::string filtered_priorities;
|
||||
string::SplitVisitor(settings_->NetworkConfigurationPriority.transitionCurrentValue(), ",", [&](std::string const& value) {
|
||||
if (string::ToInteger(value) != std::make_optional(i)) {
|
||||
filtered_priorities += ifs + value;
|
||||
ifs = ",";
|
||||
}
|
||||
});
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Priorities before: " << settings_->NetworkConfigurationPriority.transitionCurrentValue();
|
||||
settings_->NetworkConfigurationPriority.forceValue(filtered_priorities);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Priorities after: " << settings_->NetworkConfigurationPriority.transitionCurrentValue();
|
||||
}
|
||||
|
||||
settings_->SecurityProfile.setValue(activeNetworkProfile->securityProfile);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Security profile updated to: " << activeNetworkProfile->securityProfile;
|
||||
}
|
||||
|
||||
security_profile_checked_ = true;
|
||||
}
|
||||
|
||||
if (force_boot_notification_req_) {
|
||||
force_boot_notification_req_ = false;
|
||||
pending_boot_notification_req_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
sendBootNotification()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (registrationComplete())
|
||||
return;
|
||||
|
||||
if (requested_next_boot_notification_req_.has_value()) {
|
||||
if (now - requested_next_boot_notification_req_.value() < 0)
|
||||
return;
|
||||
}
|
||||
|
||||
if (pending_boot_notification_req_.wasIdleFor(settings_->HeartbeatInterval.getValue())) {
|
||||
pending_boot_notification_req_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
sendBootNotification()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::CallError> unauthorizedCallHandler1_6() {
|
||||
if (allow_ocpp_calls_) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return unauthorizedError1_6();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::CallError> unauthorizedCallHandler2_0() {
|
||||
if (allow_ocpp_calls_) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return unauthorizedError2_0();
|
||||
}
|
||||
}
|
||||
|
||||
static ocpp1_6::CallError unauthorizedError1_6() {
|
||||
return ocpp1_6::CallError {
|
||||
ocpp1_6::ErrorCode::kSecurityError,
|
||||
"Waiting for CSMS to accept boot notification",
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
}
|
||||
|
||||
static ocpp2_0::CallError unauthorizedError2_0() {
|
||||
return ocpp2_0::CallError {
|
||||
ocpp2_0::ErrorCode::kSecurityError,
|
||||
"Waiting for CSMS to accept boot notification",
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<SystemInterface> system_interface_;
|
||||
OperationHolder<std::string> pending_boot_notification_req_;
|
||||
|
||||
std::optional<SteadyPointMillis> connection_transition_timeout_ = std::nullopt;
|
||||
std::optional<SteadyPointMillis> requested_next_boot_notification_req_ = std::nullopt;
|
||||
std::atomic<bool> registration_complete_ = false;
|
||||
std::atomic<bool> allow_ocpp_calls_ = false;
|
||||
std::atomic<bool> force_boot_notification_req_ = false;
|
||||
std::atomic<bool> security_profile_checked_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_BOOT_NOTIFICATION_MODULE_H
|
||||
79
tools/openocpp/include/openocpp/module/common_templates.h
Normal file
79
tools/openocpp/include/openocpp/module/common_templates.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_COMMON_TEMPLATES_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_COMMON_TEMPLATES_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_request_handler.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_response_handler.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_service.h"
|
||||
#include "openocpp/protocol/ocpp2_0/handlers/abstract_request_handler.h"
|
||||
#include "openocpp/protocol/ocpp2_0/handlers/abstract_response_handler.h"
|
||||
#include "openocpp/protocol/ocpp2_0/handlers/abstract_service.h"
|
||||
#include "openocpp/module/abstract_module.h"
|
||||
|
||||
namespace chargelab {
|
||||
class ServiceStateless1_6 :
|
||||
public AbstractModuleBase<ServiceStateless1_6>,
|
||||
private detail::PureServiceInterface,
|
||||
private ocpp1_6::AbstractRequestHandler
|
||||
{
|
||||
friend class AbstractModuleBase<ServiceStateless1_6>;
|
||||
};
|
||||
|
||||
class ServiceStateful1_6 :
|
||||
public AbstractModuleBase<ServiceStateful1_6>,
|
||||
private detail::PureServiceInterface,
|
||||
private ocpp1_6::AbstractService,
|
||||
private ocpp1_6::AbstractRequestHandler,
|
||||
private ocpp1_6::AbstractResponseHandler
|
||||
{
|
||||
friend class AbstractModuleBase<ServiceStateful1_6>;
|
||||
};
|
||||
|
||||
class ServiceStateless2_0 :
|
||||
public AbstractModuleBase<ServiceStateless2_0>,
|
||||
private detail::PureServiceInterface,
|
||||
private ocpp2_0::AbstractRequestHandler
|
||||
{
|
||||
friend class AbstractModuleBase<ServiceStateless2_0>;
|
||||
};
|
||||
|
||||
class ServiceStateful2_0 :
|
||||
public AbstractModuleBase<ServiceStateful2_0>,
|
||||
private detail::PureServiceInterface,
|
||||
private ocpp2_0::AbstractService,
|
||||
private ocpp2_0::AbstractRequestHandler,
|
||||
private ocpp2_0::AbstractResponseHandler
|
||||
{
|
||||
friend class AbstractModuleBase<ServiceStateful2_0>;
|
||||
};
|
||||
|
||||
class ServiceStatelessGeneral :
|
||||
public AbstractModuleBase<ServiceStatelessGeneral>,
|
||||
private detail::PureServiceInterface,
|
||||
private ocpp1_6::AbstractRequestHandler,
|
||||
private ocpp2_0::AbstractRequestHandler
|
||||
{
|
||||
friend class AbstractModuleBase<ServiceStatelessGeneral>;
|
||||
};
|
||||
|
||||
class ServiceStatefulGeneral :
|
||||
public AbstractModuleBase<ServiceStatefulGeneral>,
|
||||
private detail::PureServiceInterface,
|
||||
private ocpp1_6::AbstractService,
|
||||
private ocpp1_6::AbstractRequestHandler,
|
||||
private ocpp1_6::AbstractResponseHandler,
|
||||
private ocpp2_0::AbstractService,
|
||||
private ocpp2_0::AbstractRequestHandler,
|
||||
private ocpp2_0::AbstractResponseHandler
|
||||
{
|
||||
friend class AbstractModuleBase<ServiceStatefulGeneral>;
|
||||
};
|
||||
|
||||
class PureService :
|
||||
public AbstractModuleBase<PureService>,
|
||||
private detail::PureServiceInterface
|
||||
{
|
||||
friend class AbstractModuleBase<PureService>;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_COMMON_TEMPLATES_H
|
||||
521
tools/openocpp/include/openocpp/module/configuration_module.h
Normal file
521
tools/openocpp/include/openocpp/module/configuration_module.h
Normal file
@@ -0,0 +1,521 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_CONFIGURATION_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_CONFIGURATION_MODULE_H
|
||||
|
||||
#include "openocpp/protocol/common/protocol_constants.h"
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/helpers/string.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
|
||||
namespace chargelab {
|
||||
class ConfigurationModule : public ServiceStatefulGeneral {
|
||||
private:
|
||||
static constexpr char const* kMaskedValue = "****";
|
||||
static constexpr int kOcpp20NotifyReportRequestOverheadBytes = 100;
|
||||
|
||||
public:
|
||||
explicit ConfigurationModule(std::shared_ptr<Settings> settings, std::shared_ptr<SystemInterface> system)
|
||||
: settings_(std::move(settings)),
|
||||
system_(std::move(system))
|
||||
{
|
||||
assert(settings_ != nullptr);
|
||||
assert(system_ != nullptr);
|
||||
}
|
||||
|
||||
~ConfigurationModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ConfigurationModule";
|
||||
settings_->saveIfModified();
|
||||
}
|
||||
|
||||
public:
|
||||
void runUnconditionally() override {
|
||||
settings_->saveIfModified();
|
||||
}
|
||||
|
||||
void runStep(ocpp1_6::OcppRemote&) override {
|
||||
}
|
||||
|
||||
void runStep(ocpp2_0::OcppRemote &remote) override {
|
||||
if (ocpp2_0_pending_base_report_.has_value()) {
|
||||
auto request = ocpp2_0_pending_base_report_.value();
|
||||
auto settings = settings_;
|
||||
ocpp2_0::NotifyReportRequest response {
|
||||
request.requestId,
|
||||
{system_->systemClockNow()},
|
||||
false,
|
||||
0,
|
||||
{[&](std::function<void(ocpp2_0::ReportDataType const &)> const &visitor) {
|
||||
settings->visitSettings([&](SettingBase& setting) {
|
||||
auto const metadata = setting.getMetadata();
|
||||
bool include_characteristics;
|
||||
switch (request.reportBase) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected report type in generator: " << request.reportBase;
|
||||
return;
|
||||
|
||||
case ocpp2_0::ReportBaseEnumType::kConfigurationInventory:
|
||||
include_characteristics = true;
|
||||
if (!metadata.config.isAllowOcppWrite())
|
||||
return;
|
||||
break;
|
||||
|
||||
case ocpp2_0::ReportBaseEnumType::kFullInventory:
|
||||
include_characteristics = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!metadata.model2_0.has_value())
|
||||
return;
|
||||
|
||||
auto element = ocpp2_0::ReportDataType {
|
||||
metadata.model2_0->component_type,
|
||||
metadata.model2_0->variable_type,
|
||||
setting.getAttributes2_0()
|
||||
};
|
||||
|
||||
if (include_characteristics) {
|
||||
element.variableCharacteristics = metadata.model2_0->variable_characteristics;
|
||||
}
|
||||
|
||||
visitor(element);
|
||||
});
|
||||
}}
|
||||
};
|
||||
|
||||
if (remote.sendNotifyReportReq(response).has_value()) {
|
||||
ocpp2_0_pending_base_report_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetVariablesResponse>>
|
||||
onGetVariablesReq(const ocpp2_0::GetVariablesRequest &request) override {
|
||||
if ((int)request.getVariableData.size() > settings_->ItemsPerMessageGetVariables.getValue()) {
|
||||
return ocpp2_0::CallError {
|
||||
ocpp2_0::ErrorCode::kOccurrenceConstraintViolation,
|
||||
{"Exceeded ItemsPerMessageGetVariables limit"},
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
}
|
||||
|
||||
auto settings = settings_;
|
||||
return ocpp2_0::GetVariablesResponse {
|
||||
{[=](std::function<void(ocpp2_0::GetVariableResultType const &)> const &visitor) {
|
||||
for (auto const& get_variable : request.getVariableData) {
|
||||
bool componentFound = false;
|
||||
bool variableFound = false;
|
||||
auto result = ocpp2_0::GetVariableResultType {
|
||||
ocpp2_0::GetVariableStatusEnumType::kValueNotFoundInEnum,
|
||||
get_variable.attributeType,
|
||||
std::nullopt,
|
||||
get_variable.component,
|
||||
get_variable.variable
|
||||
};
|
||||
|
||||
settings->visitSettings([&](SettingBase& setting) {
|
||||
auto const metadata = setting.getMetadata();
|
||||
if (variableFound)
|
||||
return;
|
||||
if (!metadata.model2_0.has_value())
|
||||
return;
|
||||
|
||||
if (metadata.model2_0->component_type != get_variable.component) {
|
||||
return;
|
||||
} else {
|
||||
componentFound = true;
|
||||
}
|
||||
|
||||
if (metadata.model2_0->variable_type != get_variable.variable) {
|
||||
return;
|
||||
} else {
|
||||
variableFound = true;
|
||||
}
|
||||
|
||||
if (get_variable.attributeType.has_value()) {
|
||||
// TODO - right now everything defined is "actual"
|
||||
if (get_variable.attributeType.value() != ocpp2_0::AttributeEnumType::kActual) {
|
||||
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kNotSupportedAttributeType;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadata.config.isAllowOcppRead()) {
|
||||
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kRejected;
|
||||
return;
|
||||
}
|
||||
|
||||
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kAccepted;
|
||||
result.attributeValue = setting.getValueAsString();
|
||||
});
|
||||
|
||||
if (!componentFound) {
|
||||
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kUnknownComponent;
|
||||
} else if (!variableFound) {
|
||||
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kUnknownVariable;
|
||||
}
|
||||
|
||||
visitor(result);
|
||||
}
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetVariablesResponse>>
|
||||
onSetVariablesReq(const ocpp2_0::SetVariablesRequest &request) override {
|
||||
std::vector<ocpp2_0::SetVariableResultType> results;
|
||||
for (auto const& set_variable : request.setVariableData) {
|
||||
bool componentFound = false;
|
||||
bool variableFound = false;
|
||||
auto result = ocpp2_0::SetVariableResultType {
|
||||
set_variable.attributeType,
|
||||
ocpp2_0::SetVariableStatusEnumType::kValueNotFoundInEnum,
|
||||
set_variable.component,
|
||||
set_variable.variable
|
||||
};
|
||||
|
||||
auto const network_configuration_priority_id = settings_->NetworkConfigurationPriority.getId();
|
||||
settings_->visitSettings([&](SettingBase& setting) {
|
||||
auto const metadata = setting.getMetadata();
|
||||
if (variableFound)
|
||||
return;
|
||||
if (!metadata.model2_0.has_value())
|
||||
return;
|
||||
|
||||
if (metadata.model2_0->component_type != set_variable.component) {
|
||||
return;
|
||||
} else {
|
||||
componentFound = true;
|
||||
}
|
||||
|
||||
if (metadata.model2_0->variable_type != set_variable.variable) {
|
||||
return;
|
||||
} else {
|
||||
variableFound = true;
|
||||
}
|
||||
|
||||
if (set_variable.attributeType.has_value()) {
|
||||
// TODO - right now everything defined is "actual"
|
||||
if (set_variable.attributeType.value() != ocpp2_0::AttributeEnumType::kActual) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kNotSupportedAttributeType;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!metadata.config.isAllowOcppWrite()) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
|
||||
return;
|
||||
}
|
||||
|
||||
// A05.FR.02
|
||||
if (metadata.id == network_configuration_priority_id) {
|
||||
bool missing_profile = false;
|
||||
int target_security_profile = 0;
|
||||
string::SplitVisitor(set_variable.attributeValue.value(), ",", [&](std::string const& text) {
|
||||
auto slot = string::ToInteger(text);
|
||||
if (!slot.has_value())
|
||||
return;
|
||||
|
||||
auto const& profile = settings_->NetworkConnectionProfiles.getValue(slot.value());
|
||||
if (!profile.has_value()) {
|
||||
missing_profile = true;
|
||||
return;
|
||||
}
|
||||
|
||||
target_security_profile = std::max(target_security_profile, profile->securityProfile);
|
||||
});
|
||||
|
||||
// TODO: Is there a specific requirement for this?
|
||||
if (target_security_profile < settings_->SecurityProfile.getValue()) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
|
||||
return;
|
||||
}
|
||||
|
||||
if (target_security_profile > settings_->SecurityProfile.getValue()) {
|
||||
// A05.FR.02
|
||||
if (target_security_profile >= 2 && settings_->InstalledCSMSRootCertificateCount.getValue() <= 0) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
|
||||
return;
|
||||
}
|
||||
|
||||
// A05.FR.03
|
||||
// TODO: Not relevant until we have something that can store a charging station certificate
|
||||
if (target_security_profile == 3) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to update attribute?
|
||||
if (!settings_->setSettingValue(metadata.id, set_variable.attributeValue.value())) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
|
||||
return;
|
||||
}
|
||||
|
||||
if (metadata.config.isRebootRequired()) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRebootRequired;
|
||||
} else {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kAccepted;
|
||||
}
|
||||
});
|
||||
|
||||
if (!componentFound) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kUnknownComponent;
|
||||
} else if (!variableFound) {
|
||||
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kUnknownVariable;
|
||||
}
|
||||
|
||||
results.push_back(std::move(result));
|
||||
}
|
||||
|
||||
return ocpp2_0::SetVariablesResponse {std::move(results)};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetBaseReportResponse>>
|
||||
onGetBaseReportReq(const ocpp2_0::GetBaseReportRequest &request) override {
|
||||
if (ocpp2_0_pending_base_report_.has_value()) {
|
||||
return ocpp2_0::GetBaseReportResponse {
|
||||
ocpp2_0::GenericDeviceModelStatusEnumType::kRejected
|
||||
};
|
||||
}
|
||||
|
||||
switch (request.reportBase) {
|
||||
case ocpp2_0::ReportBaseEnumType::kValueNotFoundInEnum:
|
||||
case ocpp2_0::ReportBaseEnumType::kSummaryInventory:
|
||||
return ocpp2_0::GetBaseReportResponse {
|
||||
ocpp2_0::GenericDeviceModelStatusEnumType::kNotSupported
|
||||
};
|
||||
|
||||
case ocpp2_0::ReportBaseEnumType::kFullInventory:
|
||||
case ocpp2_0::ReportBaseEnumType::kConfigurationInventory:
|
||||
break;
|
||||
}
|
||||
|
||||
ocpp2_0_pending_base_report_ = request;
|
||||
return ocpp2_0::GetBaseReportResponse {
|
||||
ocpp2_0::GenericDeviceModelStatusEnumType::kAccepted
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeConfigurationRsp>>
|
||||
onChangeConfigurationReq(const ocpp1_6::ChangeConfigurationReq& req) override {
|
||||
return changeConfiguration(req, false);
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeConfigurationRsp>>
|
||||
changeConfiguration(const ocpp1_6::ChangeConfigurationReq& req, bool force_change) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "changing configuration, key:" << req.key.value() << ", value:" << req.value.value();
|
||||
auto const state = settings_->getSettingState(req.key.value());
|
||||
if (!force_change) {
|
||||
if (!state.has_value() || (!state->config.isAllowOcppWrite() && !state->config.isAllowOcppRead()))
|
||||
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kNotSupported};
|
||||
if (!state->config.isAllowOcppWrite())
|
||||
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kRejected};
|
||||
}
|
||||
|
||||
// TODO: Move to 1_6 device model rather than id
|
||||
if (!settings_->setSettingValue(req.key.value(), req.value.value()))
|
||||
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kRejected};
|
||||
|
||||
if (force_change) {
|
||||
settings_->saveIfModified();
|
||||
}
|
||||
|
||||
if (state->config.isRebootRequired()) {
|
||||
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kRebootRequired};
|
||||
} else {
|
||||
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kAccepted};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetConfigurationRsp>>
|
||||
onGetConfigurationReq(const ocpp1_6::GetConfigurationReq &req) override {
|
||||
return getConfiguration(req);
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetConfigurationRsp>>
|
||||
getConfiguration(const ocpp1_6::GetConfigurationReq &req) {
|
||||
std::vector<std::string> include_keys;
|
||||
std::vector<ocpp1_6::CiString50Type> unknown_keys = req.key.value();
|
||||
|
||||
// TODO: Move to 1_6 device model rather than id
|
||||
bool include_all_keys = req.key.value().empty();
|
||||
if (!unknown_keys.empty()) {
|
||||
if ((int)req.key.value().size() > settings_->GetConfigurationMaxKeys.getValue()) {
|
||||
return ocpp1_6::CallError {
|
||||
ocpp1_6::ErrorCode::kPropertyConstraintViolation,
|
||||
"ConnectorId is not same as the local connector Id - must be same",
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
}
|
||||
|
||||
unknown_keys = req.key.value();
|
||||
settings_->visitSettings([&](SettingBase const& setting) {
|
||||
auto const metadata = setting.getMetadata();
|
||||
if (!metadata.config.isAllowOcppRead() && !metadata.config.isAllowOcppWrite())
|
||||
return;
|
||||
|
||||
auto it = std::remove_if(unknown_keys.begin(), unknown_keys.end(), [&] (auto const& x) {
|
||||
return string::EqualsIgnoreCaseAscii(x.value(), metadata.id);
|
||||
});
|
||||
|
||||
if (it != unknown_keys.end()) {
|
||||
include_keys.push_back(metadata.id);
|
||||
unknown_keys.erase(it, unknown_keys.end());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto settings = settings_;
|
||||
return ocpp1_6::GetConfigurationRsp {
|
||||
{[=](std::function<void(ocpp1_6::KeyValue const &)> const &visitor) {
|
||||
auto const& keys = include_keys;
|
||||
settings->visitSettings([&](SettingBase const& setting) {
|
||||
auto const metadata = setting.getMetadata();
|
||||
if (!metadata.config.isAllowOcppRead() && !metadata.config.isAllowOcppWrite())
|
||||
return;
|
||||
|
||||
std::string value;
|
||||
if (metadata.config.isAllowOcppRead()) {
|
||||
value = setting.getValueAsString();
|
||||
} else {
|
||||
value = kMaskedValue;
|
||||
}
|
||||
|
||||
if (include_all_keys || std::find(keys.begin(), keys.end(), setting.getId()) != keys.end()) {
|
||||
visitor(ocpp1_6::KeyValue{
|
||||
{metadata.id},
|
||||
!metadata.config.isAllowOcppWrite(),
|
||||
std::move(value)
|
||||
});
|
||||
}
|
||||
});
|
||||
}},
|
||||
std::move(unknown_keys)
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetNetworkProfileResponse>>
|
||||
onSetNetworkProfileReq(const ocpp2_0::SetNetworkProfileRequest &request) override {
|
||||
if (request.configurationSlot < 0 || request.configurationSlot >= detail::SettingsConstants::kMaxProfileSlots) {
|
||||
// B09.FR.02
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"BadConfigurationSlot"}}
|
||||
};
|
||||
}
|
||||
|
||||
// Note: SIM/VPN not supported
|
||||
auto const& data = request.connectionData;
|
||||
if (data.apn.has_value()) {
|
||||
// B09.FR.02
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"ApnNotSupported"}}
|
||||
};
|
||||
}
|
||||
if (data.vpn.has_value()) {
|
||||
// B09.FR.02
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"VpnNotSupported"}}
|
||||
};
|
||||
}
|
||||
|
||||
// Note: only JSON is supported
|
||||
if (data.ocppTransport != ocpp2_0::OCPPTransportEnumType::kJSON) {
|
||||
// B09.FR.02
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"BadOcppTransport"}}
|
||||
};
|
||||
}
|
||||
|
||||
std::string protocol;
|
||||
switch (data.ocppVersion) {
|
||||
default:
|
||||
case ocpp2_0::OCPPVersionEnumType::kOCPP12:
|
||||
case ocpp2_0::OCPPVersionEnumType::kOCPP15:
|
||||
// B09.FR.02
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"BadOcppVersion"}}
|
||||
};
|
||||
|
||||
case ocpp2_0::OCPPVersionEnumType::kOCPP16:
|
||||
protocol = ProtocolConstants::kProtocolOcpp1_6;
|
||||
break;
|
||||
|
||||
case ocpp2_0::OCPPVersionEnumType::kOCPP20:
|
||||
protocol = ProtocolConstants::kProtocolOcpp2_0_1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Note: an assumed "wifi" interface is always used here
|
||||
if (data.ocppInterface != ocpp2_0::OCPPInterfaceEnumType::kWireless0) {
|
||||
// B09.FR.02
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"BadOcppInterface"}}
|
||||
};
|
||||
}
|
||||
|
||||
auto parsed = uri::ParseWebsocketUri(data.ocppCsmsUrl.value());
|
||||
if (!parsed.has_value()) {
|
||||
// B09.FR.02
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"BadOcppCsmsUrl"}}
|
||||
};
|
||||
}
|
||||
|
||||
if (request.connectionData.securityProfile < settings_->SecurityProfile.getValue()) {
|
||||
// B09.FR.04
|
||||
return ocpp2_0::SetNetworkProfileResponse {
|
||||
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {{"BadSecurityProfile"}}
|
||||
};
|
||||
}
|
||||
|
||||
if (settings_->NetworkConnectionProfiles.setValue(request.configurationSlot, request.connectionData)) {
|
||||
// B09.FR.01
|
||||
return ocpp2_0::SetNetworkProfileResponse {ocpp2_0::SetNetworkProfileStatusEnumType::kAccepted};
|
||||
} else {
|
||||
// B09.FR.03
|
||||
return ocpp2_0::SetNetworkProfileResponse {ocpp2_0::SetNetworkProfileStatusEnumType::kFailed};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetMonitoringReportResponse>>
|
||||
onGetMonitoringReportReq(const ocpp2_0::GetMonitoringReportRequest&) override {
|
||||
return ocpp2_0::GetMonitoringReportResponse {ocpp2_0::GenericDeviceModelStatusEnumType::kEmptyResultSet};
|
||||
}
|
||||
|
||||
private:
|
||||
template<int N>
|
||||
static bool ciEquals(ocpp2_0::IdentifierStringPrimitive<N> const& lhs, ocpp2_0::IdentifierStringPrimitive<N> const& rhs) {
|
||||
return string::EqualsIgnoreCaseAscii(lhs.value(), rhs.value());
|
||||
}
|
||||
|
||||
template<int N>
|
||||
static bool ciEquals(std::optional<ocpp2_0::IdentifierStringPrimitive<N>> const& lhs, std::optional<ocpp2_0::IdentifierStringPrimitive<N>> const& rhs) {
|
||||
if (lhs.has_value() != rhs.has_value())
|
||||
return false;
|
||||
if (!lhs.has_value())
|
||||
return true;
|
||||
|
||||
return ciEquals(lhs.value(), rhs.value());
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<SystemInterface> system_;
|
||||
|
||||
std::optional<ocpp2_0::GetBaseReportRequest> ocpp2_0_pending_base_report_ = std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_CONFIGURATION_MODULE_H
|
||||
849
tools/openocpp/include/openocpp/module/connector_status_module.h
Normal file
849
tools/openocpp/include/openocpp/module/connector_status_module.h
Normal file
@@ -0,0 +1,849 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_CONNECTOR_STATUS_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_CONNECTOR_STATUS_MODULE_H
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/interface/platform_interface.h"
|
||||
#include "openocpp/interface/station_interface.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/common/operation_holder.h"
|
||||
|
||||
#include <utility>
|
||||
#include <set>
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
struct ReportedConnectorStatus {
|
||||
std::optional<SteadyPointMillis> last_status_sent = std::nullopt;
|
||||
std::optional<ocpp2_0::StatusNotificationRequest> last_reported_status2_0 = std::nullopt;
|
||||
std::optional<ocpp1_6::StatusNotificationReq> last_reported_status1_6 = std::nullopt;
|
||||
bool current_plug_was_charging = false;
|
||||
bool force_update = false;
|
||||
};
|
||||
}
|
||||
|
||||
class ConnectorStatusModule : public ServiceStatefulGeneral {
|
||||
private:
|
||||
static constexpr int kSettingUpdateIntervalMillis = 500;
|
||||
|
||||
public:
|
||||
ConnectorStatusModule(
|
||||
std::shared_ptr<Settings> settings,
|
||||
std::shared_ptr<PlatformInterface> const& platform,
|
||||
std::shared_ptr<StationInterface> station
|
||||
)
|
||||
: settings_(std::move(settings)),
|
||||
platform_(platform),
|
||||
station_(std::move(station)),
|
||||
pending_connector_status_req_ {platform}
|
||||
{
|
||||
assert(platform_ != nullptr);
|
||||
loadInoperativeConnectors();
|
||||
}
|
||||
|
||||
~ConnectorStatusModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ConnectorStatusModule";
|
||||
}
|
||||
|
||||
public:
|
||||
bool isChargingEnabled() {
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
auto const current_state = station_->pollConnectorStatus(entry.first);
|
||||
if (current_state.has_value() && current_state->charging_enabled)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void setPendingReset(bool value, bool hard_reset) {
|
||||
pending_reset_ = value;
|
||||
reset_reason_hard_ = hard_reset;
|
||||
}
|
||||
|
||||
bool getPendingReset() const {
|
||||
return pending_reset_;
|
||||
}
|
||||
|
||||
bool getResetReasonHard() const {
|
||||
return reset_reason_hard_;
|
||||
}
|
||||
|
||||
void setPendingStartRequests(std::vector<std::optional<ocpp2_0::EVSEType>> const& pending) {
|
||||
pending_start_.assign(pending.begin(), pending.end());
|
||||
}
|
||||
|
||||
std::unordered_map<int, ocpp1_6::ChargePointStatus> getChargePointStatus1_6() {
|
||||
std::lock_guard<std::mutex> lock(last_charge_point_status_map1_6_mutex_);
|
||||
return last_charge_point_status_map1_6_;
|
||||
}
|
||||
|
||||
bool setConnector0Inoperative(bool inoperative) {
|
||||
if (inoperative_connectors_[std::nullopt] != inoperative) {
|
||||
inoperative_connectors_[std::nullopt] = inoperative;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
void runUnconditionally() override {
|
||||
addAndUpdateStateSettings();
|
||||
|
||||
auto const connection = platform_->ocppConnection();
|
||||
if (connection != nullptr && connection->isConnected()) {
|
||||
last_online_ = platform_->steadyClockNow();
|
||||
} else if (last_online_.has_value()) {
|
||||
// B04.FR.01
|
||||
auto const elapsed = platform_->steadyClockNow() - last_online_.value();
|
||||
if (elapsed/1000 >= settings_->OfflineThreshold.getValue()) {
|
||||
bool forced_update = false;
|
||||
for (auto& x : reported_status_) {
|
||||
if (!x.second.force_update) {
|
||||
x.second.force_update = true;
|
||||
forced_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (forced_update) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "OfflineThreshold exceeded - forcing status notification updates on next connection";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runStep(ocpp2_0::OcppRemote &remote) override {
|
||||
for (auto& entry : station_->getConnectorMetadata()) {
|
||||
auto const current_state = station_->pollConnectorStatus(entry.first);
|
||||
if (!current_state.has_value())
|
||||
continue;
|
||||
|
||||
auto& reported_status = reported_status_[entry.first];
|
||||
advanceConnectorState(reported_status, current_state.value());
|
||||
auto current_status = getStatus2_0(entry.first, current_state.value());
|
||||
|
||||
if (pending_connector_status_req_.operationInProgress())
|
||||
continue;
|
||||
|
||||
if (!reported_status.force_update && reported_status.last_reported_status2_0.has_value()) {
|
||||
if (current_status == reported_status.last_reported_status2_0->connectorStatus)
|
||||
continue;
|
||||
|
||||
// Note: rate limiting charging status doesn't appear to be part of the 2.0.1 spec
|
||||
}
|
||||
|
||||
reported_status.last_status_sent = platform_->steadyClockNow();
|
||||
pending_connector_status_update2_0_ = std::make_pair(entry.first, ocpp2_0::StatusNotificationRequest {
|
||||
ocpp2_0::DateTime{platform_->systemClockNow()},
|
||||
current_status,
|
||||
entry.first.id,
|
||||
entry.first.connectorId.value()
|
||||
});
|
||||
pending_connector_status_req_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
remote.sendStatusNotificationReq(pending_connector_status_update2_0_->second)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void onStatusNotificationRsp(
|
||||
const std::string &uniqueId,
|
||||
const ocpp2_0::ResponseMessage<ocpp2_0::StatusNotificationResponse> &rsp
|
||||
) override {
|
||||
if (pending_connector_status_req_ == uniqueId) {
|
||||
// TODO: Allowing failed operations to timeout instead of retrying immediately. This might need more
|
||||
// thought.
|
||||
//pending_connector_status_req_ = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<ocpp2_0::StatusNotificationResponse>(rsp)) {
|
||||
if (pending_connector_status_update2_0_.has_value()) {
|
||||
auto const& evse = pending_connector_status_update2_0_->first;
|
||||
auto const& request = pending_connector_status_update2_0_->second;
|
||||
reported_status_[evse].last_reported_status2_0 = request;
|
||||
reported_status_[evse].force_update = false;
|
||||
}
|
||||
|
||||
pending_connector_status_req_ = kNoOperation;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to StatusNotification request: " << std::get<ocpp2_0::CallError> (rsp);
|
||||
}
|
||||
|
||||
pending_connector_status_update2_0_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
|
||||
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &req) override {
|
||||
if (req.requestedMessage != ocpp2_0::MessageTriggerEnumType::kStatusNotification)
|
||||
return std::nullopt;
|
||||
|
||||
// Note: deviating from F06.FR.12 and treating req.evse as a filter instead. If evse isn't specified all
|
||||
// connector statuses are reported, if connector ID isn't specified all statuses for that EVSE ID are
|
||||
// reported.
|
||||
bool found = false;
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
if (req.evse.has_value()) {
|
||||
if (req.evse->id != entry.first.id)
|
||||
continue;
|
||||
if (req.evse->connectorId.has_value() && req.evse->connectorId != entry.first.connectorId)
|
||||
continue;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Got trigger message for EVSE: " << entry.first;
|
||||
reported_status_[entry.first].force_update = true;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
|
||||
}
|
||||
|
||||
void runStep(ocpp1_6::OcppRemote &remote) override {
|
||||
for (auto& entry : station_->getConnectorMetadata()) {
|
||||
auto const current_state = station_->pollConnectorStatus(entry.first);
|
||||
if (!current_state.has_value())
|
||||
continue;
|
||||
|
||||
auto& reported_status = reported_status_[entry.first];
|
||||
advanceConnectorState(reported_status, current_state.value());
|
||||
auto current_status = getStatus1_6(entry.first, current_state.value(), reported_status.current_plug_was_charging);
|
||||
|
||||
if (pending_connector_status_req_.operationInProgress())
|
||||
continue;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(last_charge_point_status_map1_6_mutex_);
|
||||
last_charge_point_status_map1_6_[entry.second.connector_id1_6] = current_status;
|
||||
}
|
||||
|
||||
ocpp1_6::StatusNotificationReq request {
|
||||
entry.second.connector_id1_6,
|
||||
ocpp1_6::ChargePointErrorCode::kNoError,
|
||||
std::nullopt,
|
||||
current_status,
|
||||
ocpp1_6::DateTime{platform_->systemClockNow()},
|
||||
std::nullopt,
|
||||
std::nullopt
|
||||
};
|
||||
|
||||
if (current_state->faulted_status.has_value()) {
|
||||
auto const& faulted = current_state->faulted_status->status1_6;
|
||||
request.errorCode = faulted.errorCode;
|
||||
request.info = faulted.info;
|
||||
request.vendorId = faulted.vendorId;
|
||||
request.vendorErrorCode = faulted.vendorErrorCode;
|
||||
}
|
||||
|
||||
if (!reported_status.force_update && reported_status.last_reported_status1_6.has_value()) {
|
||||
// Note: ignoring info parameter for updates; could potentially contain noisy information
|
||||
auto const& last = reported_status.last_reported_status1_6.value();
|
||||
if (request.status == last.status && request.errorCode == last.errorCode && request.vendorId == last.vendorId && request.vendorErrorCode == last.vendorErrorCode)
|
||||
continue;
|
||||
|
||||
// TODO: rate limiting?
|
||||
}
|
||||
|
||||
reported_status.last_status_sent = platform_->steadyClockNow();
|
||||
pending_connector_status_update1_6_ = std::make_pair(entry.first, request);
|
||||
pending_connector_status_req_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
remote.sendStatusNotificationReq(request)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void onStatusNotificationRsp(
|
||||
const std::string &uniqueId,
|
||||
const ocpp1_6::ResponseMessage<ocpp1_6::StatusNotificationRsp> &rsp
|
||||
) override {
|
||||
if (pending_connector_status_req_ == uniqueId) {
|
||||
// TODO: Allowing failed operations to timeout instead of retrying immediately. This might need more
|
||||
// thought.
|
||||
//pending_connector_status_req_ = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<ocpp1_6::StatusNotificationRsp>(rsp)) {
|
||||
if (pending_connector_status_update1_6_.has_value()) {
|
||||
auto const& evse = pending_connector_status_update1_6_->first;
|
||||
auto const& request = pending_connector_status_update1_6_->second;
|
||||
reported_status_[evse].last_reported_status1_6 = request;
|
||||
reported_status_[evse].force_update = false;
|
||||
}
|
||||
|
||||
pending_connector_status_req_ = kNoOperation;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to StatusNotification request: " << std::get<ocpp1_6::CallError> (rsp);
|
||||
}
|
||||
|
||||
pending_connector_status_update1_6_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
|
||||
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
|
||||
if (req.requestedMessage != ocpp1_6::MessageTrigger::kStatusNotification)
|
||||
return std::nullopt;
|
||||
|
||||
bool found = false;
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
if (req.connectorId.has_value()) {
|
||||
if (entry.second.connector_id1_6 != req.connectorId.value())
|
||||
continue;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Got trigger message for EVSE: " << entry.first;
|
||||
reported_status_[entry.first].force_update = true;
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kRejected};
|
||||
}
|
||||
|
||||
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kAccepted};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeAvailabilityRsp>>
|
||||
onChangeAvailabilityReq(const ocpp1_6::ChangeAvailabilityReq &req) override {
|
||||
std::vector<bool*> inoperative_connectors;
|
||||
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
if (req.connectorId == 0 || entry.second.connector_id1_6 == req.connectorId) {
|
||||
inoperative_connectors.emplace_back(&inoperative_connectors_[entry.first]);
|
||||
}
|
||||
}
|
||||
|
||||
if (inoperative_connectors.empty())
|
||||
return ocpp1_6::ChangeAvailabilityRsp {ocpp1_6::AvailabilityStatus::kRejected};
|
||||
|
||||
switch (req.type) {
|
||||
default:
|
||||
return ocpp1_6::CallError {
|
||||
ocpp1_6::ErrorCode::kPropertyConstraintViolation,
|
||||
"Bad ChangeAvailabilityReq type enum",
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
|
||||
case ocpp1_6::AvailabilityType::kOperative:
|
||||
for (auto &connector : inoperative_connectors) {
|
||||
*connector = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case ocpp1_6::AvailabilityType::kInoperative:
|
||||
for (auto &connector : inoperative_connectors) {
|
||||
*connector = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bool charging = false;
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
if (req.connectorId != 0) {
|
||||
if (req.connectorId != entry.second.connector_id1_6)
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const status = station_->pollConnectorStatus(entry.first);
|
||||
if (status.has_value() && status->charging_enabled) {
|
||||
charging = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
saveInoperativeConnectors();
|
||||
|
||||
if (charging) {
|
||||
return ocpp1_6::ChangeAvailabilityRsp {ocpp1_6::AvailabilityStatus::kScheduled};
|
||||
} else {
|
||||
return ocpp1_6::ChangeAvailabilityRsp {ocpp1_6::AvailabilityStatus::kAccepted};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ChangeAvailabilityResponse>>
|
||||
onChangeAvailabilityReq(const ocpp2_0::ChangeAvailabilityRequest &req) override {
|
||||
std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> metadata;
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
if (req.evse.has_value()) {
|
||||
if (req.evse->id != entry.first.id)
|
||||
continue;
|
||||
if (req.evse->connectorId.has_value() && req.evse->connectorId != entry.first.connectorId)
|
||||
continue;
|
||||
}
|
||||
|
||||
metadata[entry.first] = entry.second;
|
||||
}
|
||||
|
||||
if (metadata.size() == 0)
|
||||
return ocpp2_0::ChangeAvailabilityResponse {ocpp2_0::ChangeAvailabilityStatusEnumType::kRejected};
|
||||
|
||||
switch (req.operationalStatus) {
|
||||
default:
|
||||
return ocpp2_0::CallError {
|
||||
ocpp2_0::ErrorCode::kPropertyConstraintViolation,
|
||||
"Bad ChangeAvailabilityRequest operationalStatus enum",
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
|
||||
case ocpp2_0::OperationalStatusEnumType::kOperative:
|
||||
inoperative_connectors_[req.evse] = false;
|
||||
break;
|
||||
|
||||
case ocpp2_0::OperationalStatusEnumType::kInoperative:
|
||||
inoperative_connectors_[req.evse] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
bool charging = false;
|
||||
for (auto const& entry : metadata) {
|
||||
auto const status = station_->pollConnectorStatus(entry.first);
|
||||
if (status.has_value() && status->charging_enabled) {
|
||||
charging = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
saveInoperativeConnectors();
|
||||
|
||||
if (charging) {
|
||||
return ocpp2_0::ChangeAvailabilityResponse {ocpp2_0::ChangeAvailabilityStatusEnumType::kScheduled};
|
||||
} else {
|
||||
return ocpp2_0::ChangeAvailabilityResponse {ocpp2_0::ChangeAvailabilityStatusEnumType::kAccepted};
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void advanceConnectorState(detail::ReportedConnectorStatus& reported, charger::ConnectorStatus const& current) {
|
||||
if (current.vehicle_connected && current.charging_enabled) {
|
||||
reported.current_plug_was_charging = true;
|
||||
} else if (!current.vehicle_connected) {
|
||||
reported.current_plug_was_charging = false;
|
||||
}
|
||||
}
|
||||
|
||||
void saveInoperativeConnectors() {
|
||||
std::string result;
|
||||
char const* ifs = "";
|
||||
for (auto const& x : inoperative_connectors_) {
|
||||
if (!x.second)
|
||||
continue;
|
||||
|
||||
result += ifs;
|
||||
if (!x.first.has_value()) {
|
||||
result += "0";
|
||||
} else if (!x.first->connectorId.has_value()) {
|
||||
result += std::to_string(x.first->id);
|
||||
} else {
|
||||
result += std::to_string(x.first->id) + "." + std::to_string(x.first->connectorId.value());
|
||||
}
|
||||
ifs = ",";
|
||||
}
|
||||
|
||||
settings_->InoperativeConnectors.setValue(result);
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Saved inoperative connectors: " << result;
|
||||
}
|
||||
|
||||
void loadInoperativeConnectors() {
|
||||
auto const value = settings_->InoperativeConnectors.getValue();
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Loading inoperative connectors: " << value;
|
||||
|
||||
inoperative_connectors_.clear();
|
||||
string::SplitVisitor(value, ",", [&](std::string const& text) {
|
||||
if (text.empty())
|
||||
return;
|
||||
|
||||
auto const it = text.find('.');
|
||||
if (it != std::string::npos) {
|
||||
auto const id = string::ToInteger(text.substr(0, it));
|
||||
auto const connectorId = string::ToInteger(text.substr(it+1));
|
||||
if (id && connectorId) {
|
||||
inoperative_connectors_[ocpp2_0::EVSEType{id.value(), connectorId}] = true;
|
||||
}
|
||||
} else {
|
||||
auto const id = string::ToInteger(text);
|
||||
if (id) {
|
||||
if (id.value() == 0) {
|
||||
inoperative_connectors_[std::nullopt] = true;
|
||||
} else {
|
||||
inoperative_connectors_[ocpp2_0::EVSEType{id.value()}] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Inoperative connectors: " << inoperative_connectors_;
|
||||
}
|
||||
|
||||
ocpp2_0::ConnectorStatusEnumType getStatus2_0(ocpp2_0::EVSEType const& evse, charger::ConnectorStatus const& status) {
|
||||
if (status.faulted_status.has_value())
|
||||
return ocpp2_0::ConnectorStatusEnumType::kFaulted;
|
||||
if (status.vehicle_connected && status.charging_enabled)
|
||||
return ocpp2_0::ConnectorStatusEnumType::kOccupied;
|
||||
|
||||
// TODO: Need to explore this further. This is failing TC_B_22_CS in OCTT; is that because the transaction
|
||||
// stop message wasn't received first or because the station must not send out an Unavailable status under
|
||||
// these conditions?
|
||||
// if (pending_reset_)
|
||||
// return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
|
||||
|
||||
if (!status.connector_available)
|
||||
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
|
||||
if (status.vehicle_connected)
|
||||
return ocpp2_0::ConnectorStatusEnumType::kOccupied;
|
||||
|
||||
// TODO: What is the precedence of "Unavailable" in 2.0.1?
|
||||
// Note: this fails test case TC_G_11_CS if this takes precedence over the "Occupied" state above.
|
||||
if (inoperative_connectors_[std::nullopt])
|
||||
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
|
||||
if (inoperative_connectors_[ocpp2_0::EVSEType {evse.id}])
|
||||
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
|
||||
if (inoperative_connectors_[evse])
|
||||
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
|
||||
|
||||
return ocpp2_0::ConnectorStatusEnumType::kAvailable;
|
||||
}
|
||||
|
||||
ocpp1_6::ChargePointStatus getStatus1_6(ocpp2_0::EVSEType const& evse, charger::ConnectorStatus const& current, bool was_charging) {
|
||||
if (current.faulted_status.has_value())
|
||||
return ocpp1_6::ChargePointStatus::kFaulted;
|
||||
|
||||
if (current.vehicle_connected && current.charging_enabled) {
|
||||
if (current.suspended_by_charger) {
|
||||
return ocpp1_6::ChargePointStatus::kSuspendedEVSE;
|
||||
} else if (current.suspended_by_vehicle) {
|
||||
return ocpp1_6::ChargePointStatus::kSuspendedEV;
|
||||
} else {
|
||||
return ocpp1_6::ChargePointStatus::kCharging;
|
||||
}
|
||||
}
|
||||
|
||||
if (inoperative_connectors_[std::nullopt])
|
||||
return ocpp1_6::ChargePointStatus::kUnavailable;
|
||||
if (inoperative_connectors_[evse])
|
||||
return ocpp1_6::ChargePointStatus::kUnavailable;
|
||||
|
||||
// if (pending_reset_)
|
||||
// return ocpp1_6::ChargePointStatus::kUnavailable;
|
||||
|
||||
if (!current.connector_available)
|
||||
return ocpp1_6::ChargePointStatus::kUnavailable;
|
||||
if (current.vehicle_connected) {
|
||||
if (was_charging) {
|
||||
return ocpp1_6::ChargePointStatus::kFinishing;
|
||||
} else {
|
||||
return ocpp1_6::ChargePointStatus::kPreparing;
|
||||
}
|
||||
}
|
||||
|
||||
// For RFID, we have a pending start entry but with a nullopt value
|
||||
auto it = std::find_if(pending_start_.begin(), pending_start_.end(), [=] (auto entry) {
|
||||
return !entry.has_value() || entry->id == evse.id; } );
|
||||
|
||||
// Connector 0 can't be preparing
|
||||
if (it != pending_start_.end() && evse.id != 0) {
|
||||
return ocpp1_6::ChargePointStatus::kPreparing;
|
||||
}
|
||||
|
||||
return ocpp1_6::ChargePointStatus::kAvailable;
|
||||
}
|
||||
|
||||
template <typename Map, typename Key, typename Generator>
|
||||
auto& getOrCreateSetting(Map& map, Key&& key, Generator&& generator) {
|
||||
auto it = map.find(key);
|
||||
if (it != map.end() && it->second != nullptr)
|
||||
return *it->second;
|
||||
|
||||
auto setting = generator();
|
||||
map[key] = setting;
|
||||
settings_->registerCustomSetting(setting);
|
||||
return *setting;
|
||||
}
|
||||
|
||||
void addAndUpdateStateSettings() {
|
||||
auto const now = platform_->steadyClockNow();
|
||||
if (last_settings_update_.has_value()) {
|
||||
auto const delta = now - last_settings_update_.value();
|
||||
if (delta < kSettingUpdateIntervalMillis)
|
||||
return;
|
||||
}
|
||||
last_settings_update_ = now;
|
||||
|
||||
// Update station level settings
|
||||
{
|
||||
auto const& metadata = station_->getStationMetadata();
|
||||
settings_->ChargingStationAvailable.setValue(true);
|
||||
settings_->ChargingStationAvailabilityState.setValue(
|
||||
inoperative_connectors_[std::nullopt] ? "Unavailable" : "Available");
|
||||
settings_->ChargingStationSupplyPhases.setValue(metadata.supply_phases);
|
||||
}
|
||||
|
||||
// Update connector level settings
|
||||
std::set<int> evse_ids {};
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
if (!entry.first.connectorId.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Bad connector metadata - must specify a connector ID";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& evse_id = entry.first.id;
|
||||
auto const& connector_id = entry.first.connectorId.value();
|
||||
auto const& connector_status = station_->pollConnectorStatus(entry.first);
|
||||
evse_ids.insert(evse_id);
|
||||
|
||||
// 2.13.1
|
||||
auto& available = getOrCreateSetting(settings_available_, entry.first, [&]() {
|
||||
return std::make_shared<SettingBool>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
"EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "Available",
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
std::nullopt,
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
|
||||
ocpp2_0::VariableType{"Available"},
|
||||
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kboolean}
|
||||
},
|
||||
SettingBool::kTextTrue
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
available.setValue(connector_status.has_value());
|
||||
|
||||
// 2.13.2
|
||||
auto& availability_state = getOrCreateSetting(settings_availability_state_, entry.first, [&]() {
|
||||
return std::make_shared<SettingString>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
"EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "AvailabilityState",
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
std::nullopt,
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
|
||||
ocpp2_0::VariableType{"AvailabilityState"},
|
||||
ocpp2_0::VariableCharacteristicsType{
|
||||
std::nullopt,
|
||||
ocpp2_0::DataEnumType::kOptionList,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
"Available,Occupied,Reserved,Unavailable,Faulted"
|
||||
}
|
||||
},
|
||||
"Unavailable"
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
|
||||
if (connector_status.has_value()) {
|
||||
auto const& status = getStatus2_0(entry.first, connector_status.value());
|
||||
availability_state.setValue(status.to_string());
|
||||
}
|
||||
|
||||
// 2.13.4
|
||||
auto& connector_type = getOrCreateSetting(settings_connector_type_, entry.first, [&]() {
|
||||
auto const& name = "EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "ConnectorType";
|
||||
return std::make_shared<SettingString>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
name,
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
DeviceModel1_6{name},
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
|
||||
ocpp2_0::VariableType{"ConnectorType"},
|
||||
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kstring}
|
||||
},
|
||||
""
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
connector_type.setValue(entry.second.connector_type);
|
||||
|
||||
// 2.13.6
|
||||
auto& supply_phases = getOrCreateSetting(settings_supply_phases_, entry.first, [&]() {
|
||||
auto const& name = "EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "SupplyPhases";
|
||||
return std::make_shared<SettingInt>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
name,
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
DeviceModel1_6{name},
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
|
||||
ocpp2_0::VariableType{"SupplyPhases"},
|
||||
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kinteger}
|
||||
},
|
||||
std::to_string(1)
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
supply_phases.setValue(entry.second.supply_phases);
|
||||
|
||||
// 2.13.7
|
||||
// Note: max power won't be updated dynamically if the station information changes; this could possibly
|
||||
// be improved in the future, but that may not be something that's ever needed in practice.
|
||||
auto& power = getOrCreateSetting(settings_power_, entry.first, [&]() {
|
||||
auto const& name = "EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "Power";
|
||||
return std::make_shared<SettingDouble>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
name,
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
DeviceModel1_6{name},
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
|
||||
ocpp2_0::VariableType{"Power"},
|
||||
ocpp2_0::VariableCharacteristicsType{
|
||||
"W",
|
||||
ocpp2_0::DataEnumType::kdecimal,
|
||||
std::nullopt,
|
||||
entry.second.power_max_watts
|
||||
}
|
||||
},
|
||||
std::to_string(0)
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Take this from the supplied meter values for the connector? Note that a wattage reading isn't
|
||||
// mandatory, in which case we'd need to support not reporting the actual characteristic.
|
||||
power.setValue(0);
|
||||
}
|
||||
|
||||
// Update EVSE level settings
|
||||
for (auto const& entry : station_->getEvseMetadata()) {
|
||||
if (entry.first.connectorId.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Bad EVSE metadata - should not specify a connector ID";
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2.13.1
|
||||
getOrCreateSetting(settings_available_, ocpp2_0::EVSEType{entry.first.id}, [&]() {
|
||||
return std::make_shared<SettingBool>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
"EVSE" + std::to_string(entry.first.id) + "Available",
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
std::nullopt,
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
|
||||
ocpp2_0::VariableType{"Available"},
|
||||
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kboolean}
|
||||
},
|
||||
SettingBool::kTextTrue
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
|
||||
// 2.13.2
|
||||
auto& availability_state = getOrCreateSetting(settings_availability_state_, ocpp2_0::EVSEType{entry.first.id}, [&]() {
|
||||
return std::make_shared<SettingString>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
"EVSE" + std::to_string(entry.first.id) + "AvailabilityState",
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
std::nullopt,
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
|
||||
ocpp2_0::VariableType{"AvailabilityState"},
|
||||
ocpp2_0::VariableCharacteristicsType{
|
||||
std::nullopt,
|
||||
ocpp2_0::DataEnumType::kOptionList,
|
||||
std::nullopt,
|
||||
std::nullopt,
|
||||
"Available,Occupied,Reserved,Unavailable,Faulted"
|
||||
}
|
||||
},
|
||||
"Unavailable"
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: How should this be interpreted when there's more than one connector under an EVSE? Is it
|
||||
// reasonable to only report "Available"/"Unavailable" here based on what was configured via
|
||||
// ChangeAvailability?
|
||||
availability_state.setValue(inoperative_connectors_[entry.first] ? "Unavailable" : "Available");
|
||||
|
||||
// 2.13.6
|
||||
auto& supply_phases = getOrCreateSetting(settings_supply_phases_, entry.first, [&]() {
|
||||
auto const& name = "EVSE" + std::to_string(entry.first.id) + "SupplyPhases";
|
||||
return std::make_shared<SettingInt>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
name,
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
DeviceModel1_6{name},
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
|
||||
ocpp2_0::VariableType{"SupplyPhases"},
|
||||
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kinteger}
|
||||
},
|
||||
std::to_string(1)
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
supply_phases.setValue(entry.second.supply_phases);
|
||||
|
||||
// 2.13.7
|
||||
// Note: max power won't be updated dynamically if the station information changes; this could possibly
|
||||
// be improved in the future, but that may not be something that's ever needed in practice.
|
||||
auto& power = getOrCreateSetting(settings_power_, entry.first, [&]() {
|
||||
auto const& name = "EVSE" + std::to_string(entry.first.id) + "Power";
|
||||
return std::make_shared<SettingDouble>(
|
||||
std::make_unique<SettingMetadata>(SettingMetadata {
|
||||
name,
|
||||
SettingConfig::roNotSavedPolicy(),
|
||||
DeviceModel1_6{name},
|
||||
DeviceModel2_0{
|
||||
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
|
||||
ocpp2_0::VariableType{"Power"},
|
||||
ocpp2_0::VariableCharacteristicsType{
|
||||
"W",
|
||||
ocpp2_0::DataEnumType::kdecimal,
|
||||
std::nullopt,
|
||||
entry.second.power_max_watts
|
||||
}
|
||||
},
|
||||
std::to_string(0)
|
||||
}),
|
||||
[](auto const&) {return true;}
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: Take this from the supplied meter values for the connector? Note that a wattage reading isn't
|
||||
// mandatory, in which case we'd need to support not reporting the actual characteristic.
|
||||
power.setValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<PlatformInterface> platform_;
|
||||
std::shared_ptr<StationInterface> station_;
|
||||
|
||||
OperationHolder<std::string> pending_connector_status_req_;
|
||||
std::optional<std::pair<chargelab::ocpp2_0::EVSEType, ocpp2_0::StatusNotificationRequest>> pending_connector_status_update2_0_ = std::nullopt;
|
||||
std::optional<std::pair<chargelab::ocpp2_0::EVSEType, ocpp1_6::StatusNotificationReq>> pending_connector_status_update1_6_ = std::nullopt;
|
||||
std::map<chargelab::ocpp2_0::EVSEType, detail::ReportedConnectorStatus> reported_status_;
|
||||
std::map<std::optional<chargelab::ocpp2_0::EVSEType>, bool> inoperative_connectors_;
|
||||
std::atomic<bool> pending_reset_ = false;
|
||||
std::atomic<bool> reset_reason_hard_ = false; // hard reset or soft reset
|
||||
std::optional<SteadyPointMillis> last_online_ = std::nullopt;
|
||||
|
||||
std::vector<std::optional<ocpp2_0::EVSEType>> pending_start_;
|
||||
|
||||
std::optional<SteadyPointMillis> last_settings_update_ = std::nullopt;
|
||||
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingBool>> settings_available_ {};
|
||||
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingString>> settings_availability_state_ {};
|
||||
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingString>> settings_connector_type_ {};
|
||||
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingInt>> settings_supply_phases_ {};
|
||||
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingDouble>> settings_power_ {};
|
||||
|
||||
// For portal module
|
||||
std::unordered_map<int, ocpp1_6::ChargePointStatus> last_charge_point_status_map1_6_ {};
|
||||
std::mutex last_charge_point_status_map1_6_mutex_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_CONNECTOR_STATUS_MODULE_H
|
||||
102
tools/openocpp/include/openocpp/module/fallback_module.h
Normal file
102
tools/openocpp/include/openocpp/module/fallback_module.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_FALLBACK_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_FALLBACK_MODULE_H
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
|
||||
namespace chargelab {
|
||||
class FallbackModule : public ServiceStatefulGeneral {
|
||||
public:
|
||||
FallbackModule(std::shared_ptr<SystemInterface> system)
|
||||
: system_(std::move(system))
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
void runStep(ocpp1_6::OcppRemote&) override {
|
||||
}
|
||||
|
||||
void runStep(ocpp2_0::OcppRemote &remote) override {
|
||||
if (pending_customer_report_.has_value()) {
|
||||
auto id = remote.sendNotifyCustomerInformationReq(ocpp2_0::NotifyCustomerInformationRequest {
|
||||
"",
|
||||
false,
|
||||
0,
|
||||
system_->systemClockNow(),
|
||||
pending_customer_report_->requestId
|
||||
});
|
||||
|
||||
if (id.has_value()) {
|
||||
pending_customer_report_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
|
||||
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq&) override {
|
||||
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kNotImplemented};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
|
||||
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest&) override {
|
||||
// F06.FR.08
|
||||
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kNotImplemented};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::DataTransferRsp>>
|
||||
onDataTransferReq(const ocpp1_6::DataTransferReq&) override {
|
||||
return ocpp1_6::DataTransferRsp {ocpp1_6::DataTransferStatus::kUnknownVendorId};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::DataTransferResponse>>
|
||||
onDataTransferReq(const ocpp2_0::DataTransferRequest&) override {
|
||||
// P01.FR.05
|
||||
return ocpp2_0::DataTransferResponse {ocpp2_0::DataTransferStatusEnumType::kUnknownVendorId};
|
||||
}
|
||||
|
||||
// TODO: This operation doesn't seem relevant if we're not caching customer identifiers like authorization
|
||||
// tokens in any sort of local list or authorization cache. Leaving this here for now.
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CustomerInformationResponse>>
|
||||
onCustomerInformationReq(const ocpp2_0::CustomerInformationRequest& request) override {
|
||||
// N10.FR.05
|
||||
if (pending_customer_report_.has_value())
|
||||
return ocpp2_0::CustomerInformationResponse {ocpp2_0::CustomerInformationStatusEnumType::kRejected};
|
||||
|
||||
// N10.FR.07
|
||||
if (!request.clear && !request.report)
|
||||
return ocpp2_0::CustomerInformationResponse {ocpp2_0::CustomerInformationStatusEnumType::kRejected};
|
||||
|
||||
// N10.FR.01
|
||||
pending_customer_report_ = request;
|
||||
return ocpp2_0::CustomerInformationResponse {ocpp2_0::CustomerInformationStatusEnumType::kAccepted};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UnlockConnectorRsp>>
|
||||
onUnlockConnectorReq(const ocpp1_6::UnlockConnectorReq&) override {
|
||||
return ocpp1_6::UnlockConnectorRsp {ocpp1_6::UnlockStatus::kNotSupported};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UnlockConnectorResponse>>
|
||||
onUnlockConnectorReq(const ocpp2_0::UnlockConnectorRequest&) override {
|
||||
// Note - responding here to satisfy TC_E_16_CS, however a NotImplemented default response seems more
|
||||
// appropriate.
|
||||
return ocpp2_0::UnlockConnectorResponse {ocpp2_0::UnlockStatusEnumType::kUnlockFailed};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ClearCacheRsp>>
|
||||
onClearCacheReq(const ocpp1_6::ClearCacheReq&) override {
|
||||
return ocpp1_6::ClearCacheRsp {ocpp1_6::ClearCacheStatus::kAccepted};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearCacheResponse>>
|
||||
onClearCacheReq(const ocpp2_0::ClearCacheRequest&) override {
|
||||
return ocpp2_0::ClearCacheResponse {ocpp2_0::ClearCacheStatusEnumType::kAccepted};
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SystemInterface> system_;
|
||||
|
||||
std::optional<ocpp2_0::CustomerInformationRequest> pending_customer_report_ = std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_FALLBACK_MODULE_H
|
||||
788
tools/openocpp/include/openocpp/module/firmware_update_module.h
Normal file
788
tools/openocpp/include/openocpp/module/firmware_update_module.h
Normal file
@@ -0,0 +1,788 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_FIRMWARE_UPDATE_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_FIRMWARE_UPDATE_MODULE_H
|
||||
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/interface/component/fetch_interface.h"
|
||||
#include "openocpp/interface/platform_interface.h"
|
||||
#include "openocpp/common/operation_holder.h"
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/module/pending_messages_module.h"
|
||||
#include "openocpp/module/connector_status_module.h"
|
||||
#include "openocpp/module/reset_module.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/common/macro.h"
|
||||
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <charconv>
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
template <typename HM>
|
||||
struct FirmwareUpdateOperation {
|
||||
ocpp2_0::UpdateFirmwareRequest request;
|
||||
|
||||
// for ocpp1.6 only
|
||||
std::optional<ocpp1_6::FirmwareStatus> last_status1_6 = std::nullopt;
|
||||
|
||||
// L01.FR.04
|
||||
std::unique_ptr<typename HM::hash_calculator_type> signature_hash = HM::createCalculator(HM::kHashTypeSHA256);
|
||||
std::shared_ptr<RestConnectionInterface> connection = nullptr;
|
||||
std::vector<uint8_t> buffer {};
|
||||
std::size_t content_length = 0;
|
||||
std::size_t total_bytes_read = 0;
|
||||
std::size_t total_failures = 0;
|
||||
|
||||
std::vector<std::vector<uint8_t>> block_hashes {};
|
||||
std::optional<std::vector<uint8_t>> expected_signature_hash = std::nullopt;
|
||||
std::optional<ocpp2_0::FirmwareStatusEnumType> last_status2_0 = std::nullopt;
|
||||
|
||||
bool finished_signature_check = false;
|
||||
bool finished_flashing_firmware = false;
|
||||
bool running_firmware_update = false;
|
||||
};
|
||||
}
|
||||
|
||||
template <typename HM>
|
||||
class FirmwareUpdateModule : public ServiceStatefulGeneral {
|
||||
private:
|
||||
static constexpr int kChunkSize = 20*1024;
|
||||
static constexpr int kPriorityFirmwareStatusNotification = 100;
|
||||
|
||||
// Note: arbitrary random assigned ID
|
||||
static constexpr std::uint64_t kOperationGroupId = 0x78106033AC8E780Aull;
|
||||
public:
|
||||
explicit FirmwareUpdateModule(
|
||||
std::shared_ptr<PlatformInterface> platform,
|
||||
std::shared_ptr<ResetModule> reset,
|
||||
std::shared_ptr<PendingMessagesModule> pending_messages,
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status,
|
||||
std::shared_ptr<StationInterface> station
|
||||
)
|
||||
: platform_(std::move(platform)),
|
||||
reset_(std::move(reset)),
|
||||
pending_messages_(std::move(pending_messages)),
|
||||
connector_status_(std::move(connector_status)),
|
||||
station_(std::move(station))
|
||||
{
|
||||
assert(platform_ != nullptr);
|
||||
assert(station_ != nullptr);
|
||||
|
||||
settings_ = platform_->getSettings();
|
||||
assert(settings_ != nullptr);
|
||||
}
|
||||
|
||||
~FirmwareUpdateModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting FirmwareUpdateModule";
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UpdateFirmwareRsp>>
|
||||
onUpdateFirmwareReq(const ocpp1_6::UpdateFirmwareReq &req) override {
|
||||
if (operation_.has_value()) {
|
||||
return ocpp1_6::CallError {
|
||||
ocpp1_6::ErrorCode::kGenericError,
|
||||
"InProgress",
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
}
|
||||
|
||||
// Check if there is transaction in progress
|
||||
bool found_charging_connector = false;
|
||||
for (auto const& entry: station_->getConnectorMetadata()) {
|
||||
if (entry.first.id == 0) continue;
|
||||
auto const connector_status = station_->pollConnectorStatus(entry.first);
|
||||
if (!connector_status.has_value())
|
||||
continue;
|
||||
if (connector_status->charging_enabled) {
|
||||
found_charging_connector = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_charging_connector) {
|
||||
return ocpp1_6::CallError {
|
||||
ocpp1_6::ErrorCode::kGenericError,
|
||||
"ActiveChargeSession",
|
||||
common::RawJson::empty_object()
|
||||
};
|
||||
}
|
||||
|
||||
// Set all connectors to Unavailable
|
||||
need_to_restore_connector_0_operative_ = connector_status_->setConnector0Inoperative(true);
|
||||
|
||||
operation_ = detail::FirmwareUpdateOperation<HM> {
|
||||
ocpp2_0::UpdateFirmwareRequest {
|
||||
req.retries,
|
||||
req.retryInterval,
|
||||
0,
|
||||
ocpp2_0::FirmwareType {
|
||||
req.location.value(),
|
||||
optional::GetOrDefault(req.retrieveDate.getTimestamp(), platform_->systemClockNow())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return ocpp1_6::UpdateFirmwareRsp {};
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UpdateFirmwareResponse>>
|
||||
onUpdateFirmwareReq(const ocpp2_0::UpdateFirmwareRequest &request) override {
|
||||
// Note: there doesn't appear to be any specific requirement here in the 2.0.1 specification and this is
|
||||
// incompatible with a station simultaneously supporting L02...but this is required by TC_L_18_CS which is
|
||||
// mandatory for core certification.
|
||||
if (!request.firmware.signature.has_value()) {
|
||||
return ocpp2_0::UpdateFirmwareResponse{
|
||||
ocpp2_0::UpdateFirmwareStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {"MissingSignature"}
|
||||
};
|
||||
}
|
||||
if (!request.firmware.signingCertificate.has_value()) {
|
||||
return ocpp2_0::UpdateFirmwareResponse{
|
||||
ocpp2_0::UpdateFirmwareStatusEnumType::kRejected,
|
||||
ocpp2_0::StatusInfoType {"MissingSigningCert"}
|
||||
};
|
||||
}
|
||||
|
||||
// L01.FR.21
|
||||
// L01.FR.22
|
||||
if (request.firmware.signingCertificate.has_value()) {
|
||||
if (!platform_->verifyManufacturerCertificate(request.firmware.signingCertificate->value(), std::nullopt)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad manufacturer certificate";
|
||||
|
||||
// L01.FR.02
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::SecurityEventNotificationRequest {
|
||||
"InvalidFirmwareSigningCertificate",
|
||||
platform_->systemClockNow()
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
|
||||
);
|
||||
|
||||
return ocpp2_0::UpdateFirmwareResponse{ocpp2_0::UpdateFirmwareStatusEnumType::kInvalidCertificate};
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this isn't strictly necessary, however passing a free-form text string to the platform URI parsing
|
||||
// method seems potentially dangerous.
|
||||
if (!uri::parseHttpUri(request.firmware.location.value()).has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad firmware location: " << request.firmware.location.value();
|
||||
return ocpp2_0::UpdateFirmwareResponse{ocpp2_0::UpdateFirmwareStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
auto status = ocpp2_0::UpdateFirmwareStatusEnumType::kAccepted;
|
||||
if (operation_.has_value()) {
|
||||
// L01.FR.24
|
||||
operation_ = std::nullopt;
|
||||
status = ocpp2_0::UpdateFirmwareStatusEnumType::kAcceptedCanceled;
|
||||
}
|
||||
|
||||
operation_ = detail::FirmwareUpdateOperation<HM>{request};
|
||||
return ocpp2_0::UpdateFirmwareResponse{status};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
|
||||
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq&) override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
|
||||
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &req) override {
|
||||
if (req.requestedMessage != ocpp2_0::MessageTriggerEnumType::kFirmwareStatusNotification)
|
||||
return std::nullopt;
|
||||
|
||||
if (req.evse.has_value()) {
|
||||
force_update_ = false;
|
||||
} else {
|
||||
force_update_ = true;
|
||||
}
|
||||
|
||||
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
|
||||
}
|
||||
|
||||
void checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType status) {
|
||||
if (operation_->last_status2_0 == std::make_optional(status))
|
||||
return;
|
||||
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::FirmwareStatusNotificationRequest {
|
||||
status,
|
||||
operation_->request.requestId
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
|
||||
);
|
||||
|
||||
operation_->last_status2_0 = status;
|
||||
}
|
||||
|
||||
void checkAndUpdateStatus(ocpp1_6::FirmwareStatus status) {
|
||||
if (operation_->last_status1_6 == std::make_optional(status))
|
||||
return;
|
||||
|
||||
pending_messages_->sendRequest1_6(
|
||||
ocpp1_6::FirmwareStatusNotificationReq {
|
||||
status
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
|
||||
);
|
||||
|
||||
operation_->last_status1_6 = status;
|
||||
}
|
||||
|
||||
|
||||
static bool checkOrRetryConnection(
|
||||
std::shared_ptr<PlatformInterface> const& platform,
|
||||
detail::FirmwareUpdateOperation<HM>& operation
|
||||
) {
|
||||
if (operation.connection != nullptr)
|
||||
return true;
|
||||
|
||||
auto const uri = operation.request.firmware.location.value();
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Downloading firmware from: " << uri;
|
||||
operation.connection = platform->getRequest(uri);
|
||||
if (operation.connection == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed establishing connection to: " << uri;
|
||||
operation.total_failures++;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!operation.connection->open(0)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed opening connection to server";
|
||||
operation.total_failures++;
|
||||
operation.connection = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!operation.connection->send()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed sending request";
|
||||
operation.total_failures++;
|
||||
operation.connection = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const status = operation.connection->getStatusCode();
|
||||
if (!(status >= 200 && status < 300)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad status code - expecting 2xx response: " << status;
|
||||
operation.total_failures++;
|
||||
operation.connection = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
operation.content_length = operation.connection->getContentLength();
|
||||
operation.total_bytes_read = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void runStep(ocpp1_6::OcppRemote &) override {
|
||||
performBootChecks1_6();
|
||||
performFirmwareUpdate1_6();
|
||||
}
|
||||
|
||||
void runStep(ocpp2_0::OcppRemote& remote) override {
|
||||
performBootChecks2_0();
|
||||
performFirmwareUpdate2_0();
|
||||
|
||||
if (force_update_.has_value()) {
|
||||
if (!force_update_.value()) {
|
||||
// Note - doesn't seem to be defined by 2.0.1 requirements, following TC_F_17_CS
|
||||
auto const result = remote.sendCall(ocpp2_0::FirmwareStatusNotificationRequest {
|
||||
ocpp2_0::FirmwareStatusEnumType::kIdle
|
||||
});
|
||||
|
||||
if (result.has_value())
|
||||
force_update_ = std::nullopt;
|
||||
} else {
|
||||
if (operation_.has_value() && operation_->last_status2_0.has_value()) {
|
||||
auto const result = remote.sendCall(ocpp2_0::FirmwareStatusNotificationRequest {
|
||||
operation_->last_status2_0.value(),
|
||||
operation_->request.requestId
|
||||
});
|
||||
|
||||
if (result.has_value())
|
||||
force_update_ = std::nullopt;
|
||||
} else {
|
||||
// F06.FR.16
|
||||
auto const result = remote.sendCall(ocpp2_0::FirmwareStatusNotificationRequest {
|
||||
ocpp2_0::FirmwareStatusEnumType::kIdle
|
||||
});
|
||||
|
||||
if (result.has_value())
|
||||
force_update_ = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void performBootChecks2_0() {
|
||||
if (performed_boot_checks_)
|
||||
return;
|
||||
|
||||
auto const active = station_->getActiveSlotId();
|
||||
settings_->ActiveFirmwareSlotId.setValue(active);
|
||||
|
||||
auto const update = settings_->ExpectedUpdateFirmwareSlotId.getValue();
|
||||
if (!update.empty()) {
|
||||
int request_id;
|
||||
std::string slot_id;
|
||||
|
||||
parseExpectedUpdateFirmwareSlotId(update, request_id, slot_id);
|
||||
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::FirmwareStatusNotificationRequest {
|
||||
slot_id == active ?
|
||||
ocpp2_0::FirmwareStatusEnumType::kInstalled :
|
||||
ocpp2_0::FirmwareStatusEnumType::kInstallationFailed,
|
||||
request_id
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
|
||||
);
|
||||
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::SecurityEventNotificationRequest{
|
||||
"FirmwareUpdated",
|
||||
platform_->systemClockNow()
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
|
||||
);
|
||||
|
||||
settings_->ExpectedUpdateFirmwareSlotId.setValue("");
|
||||
}
|
||||
|
||||
performed_boot_checks_ = true;
|
||||
}
|
||||
|
||||
void performFirmwareUpdate2_0() {
|
||||
if (!operation_.has_value())
|
||||
return;
|
||||
|
||||
auto const max_retries = optional::GetOrDefault(
|
||||
operation_->request.retries,
|
||||
settings_->FirmwareUpdateDefaultRetries.getValue()
|
||||
);
|
||||
|
||||
if (operation_->request.firmware.retrieveDateTime.isAfter(platform_->systemClockNow(), false)) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloadScheduled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!operation_->finished_signature_check) {
|
||||
if ((int)operation_->total_failures > max_retries) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloadFailed);
|
||||
operation_ = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloading);
|
||||
if (!checkOrRetryConnection(platform_, operation_.value()))
|
||||
return;
|
||||
|
||||
auto const remaining = (int)operation_->content_length - (int)operation_->total_bytes_read;
|
||||
if (remaining > 0) {
|
||||
if (operation_->total_bytes_read == 0)
|
||||
operation_->signature_hash->reset();
|
||||
|
||||
operation_->buffer.resize(kChunkSize);
|
||||
auto const bytes_read = operation_->connection->read((char*)operation_->buffer.data(), (int)operation_->buffer.size());
|
||||
// Note: treating zero bytes read as a failure condition here to prevent an infinite loop under
|
||||
// those conditions.
|
||||
if (bytes_read <= 0) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data - retrying operation";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Block hash
|
||||
auto hash = HM::calculateHashBinary(
|
||||
HM::kHashTypeSHA256,
|
||||
(unsigned char const*)operation_->buffer.data(),
|
||||
bytes_read
|
||||
);
|
||||
if (!hash.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed hashing block";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
operation_->block_hashes.push_back(std::move(hash.value()));
|
||||
operation_->signature_hash->update((unsigned char const*)operation_->buffer.data(), bytes_read);
|
||||
operation_->total_bytes_read += bytes_read;
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Hashing progress: " << operation_->total_bytes_read << " / " << operation_->content_length;
|
||||
}
|
||||
|
||||
if (operation_->total_bytes_read >= operation_->content_length) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloaded);
|
||||
|
||||
auto signature_hash = operation_->signature_hash->finishBinary();
|
||||
if (!signature_hash.has_value()) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInvalidSignature);
|
||||
|
||||
// L01.FR.03
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::SecurityEventNotificationRequest {
|
||||
"InvalidFirmwareSignature",
|
||||
platform_->systemClockNow()
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
|
||||
);
|
||||
|
||||
operation_ = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& firmware = operation_->request.firmware;
|
||||
if (firmware.signingCertificate.has_value() && firmware.signature.has_value()) {
|
||||
auto signature_binary = HM::decodeBase64(firmware.signature->value());
|
||||
if (!signature_binary.has_value()) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInvalidSignature);
|
||||
|
||||
// L01.FR.03
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::SecurityEventNotificationRequest {
|
||||
"InvalidFirmwareSignature",
|
||||
platform_->systemClockNow()
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
|
||||
);
|
||||
|
||||
operation_ = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
SignatureAndHash signature_and_hash {};
|
||||
signature_and_hash.sig = signature_binary->data();
|
||||
signature_and_hash.sig_len = signature_binary->size();
|
||||
signature_and_hash.hash = signature_hash->data();
|
||||
signature_and_hash.hash_len = signature_hash->size();
|
||||
|
||||
auto const valid = platform_->verifyManufacturerCertificate(
|
||||
firmware.signingCertificate->value(),
|
||||
signature_and_hash
|
||||
);
|
||||
if (!valid) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInvalidSignature);
|
||||
|
||||
// L01.FR.03
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::SecurityEventNotificationRequest {
|
||||
"InvalidFirmwareSignature",
|
||||
platform_->systemClockNow()
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
|
||||
);
|
||||
|
||||
operation_ = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kSignatureVerified);
|
||||
}
|
||||
|
||||
std::size_t total_block_hash_size = 0;
|
||||
for (auto const& x : operation_->block_hashes)
|
||||
total_block_hash_size += x.size();
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Total block hash size (" << operation_->block_hashes.size() << " blocks): " << total_block_hash_size;
|
||||
|
||||
operation_->expected_signature_hash = std::move(signature_hash);
|
||||
operation_->finished_signature_check = true;
|
||||
operation_->connection = nullptr;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!operation_->finished_flashing_firmware) {
|
||||
// L01.FR.16
|
||||
if (operation_->request.firmware.installDateTime.has_value()) {
|
||||
if (operation_->request.firmware.installDateTime->isAfter(platform_->systemClockNow(), false)) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstallScheduled);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((int)operation_->total_failures > max_retries) {
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstallationFailed);
|
||||
operation_ = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstalling);
|
||||
if (!checkOrRetryConnection(platform_, operation_.value()))
|
||||
return;
|
||||
|
||||
auto const remaining = (int)operation_->content_length - (int)operation_->total_bytes_read;
|
||||
if (remaining > 0) {
|
||||
operation_->buffer.resize(kChunkSize);
|
||||
auto const bytes_read = operation_->connection->read((char*)operation_->buffer.data(), (int)operation_->buffer.size());
|
||||
// Note: treating zero bytes read as a failure condition here to prevent an infinite loop under
|
||||
// those conditions.
|
||||
if (bytes_read <= 0) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data - retrying operation";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation_->total_bytes_read == 0) {
|
||||
if (operation_->running_firmware_update) {
|
||||
station_->finishUpdateProcess(false);
|
||||
operation_->running_firmware_update = false;
|
||||
}
|
||||
|
||||
if (station_->startUpdateProcess(operation_->content_length) != StationInterface::Result::kSucceeded) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed starting firmware update process";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
operation_->running_firmware_update = true;
|
||||
} else {
|
||||
if (!operation_->running_firmware_update) {
|
||||
CHARGELAB_LOG_MESSAGE(error)
|
||||
<< "Unexpected state - expected running firmware update operation";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (station_->processFirmwareChunk(operation_->buffer.data(), bytes_read) != StationInterface::Result::kSucceeded) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed processing firmware chunk";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
operation_->signature_hash->update((unsigned char const*)operation_->buffer.data(), bytes_read);
|
||||
operation_->total_bytes_read += bytes_read;
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Flashing progress: " << operation_->total_bytes_read << " / " << operation_->content_length;
|
||||
}
|
||||
|
||||
if (operation_->total_bytes_read >= operation_->content_length) {
|
||||
// TODO: Check signature
|
||||
|
||||
auto const slot_id = station_->getUpdateSlotId();
|
||||
if (station_->finishUpdateProcess(true) != StationInterface::Result::kSucceeded) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed finishing update process";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
operation_->finished_flashing_firmware = true;
|
||||
operation_->connection = nullptr;
|
||||
// settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(std::to_string(operation_->request.requestId) + ":" + slot_id);
|
||||
settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(buildExpectedUpdateFirmwareSlotId(
|
||||
operation_->request.requestId, slot_id));
|
||||
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstallRebooting);
|
||||
|
||||
reset_->resetOnIdle(ocpp2_0::BootReasonEnumType::kFirmwareUpdate);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void performBootChecks1_6() {
|
||||
if (performed_boot_checks_)
|
||||
return;
|
||||
|
||||
auto const active = station_->getActiveSlotId();
|
||||
settings_->ActiveFirmwareSlotId.setValue(active);
|
||||
|
||||
auto const update = settings_->ExpectedUpdateFirmwareSlotId.getValue();
|
||||
if (!update.empty()) {
|
||||
int request_id;
|
||||
std::string slot_id;
|
||||
|
||||
parseExpectedUpdateFirmwareSlotId(update, request_id, slot_id);
|
||||
|
||||
pending_messages_->sendRequest1_6(
|
||||
ocpp1_6::FirmwareStatusNotificationReq {
|
||||
slot_id == active ?
|
||||
ocpp1_6::FirmwareStatus::kInstalled :
|
||||
ocpp1_6::FirmwareStatus::kInstallationFailed
|
||||
},
|
||||
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
|
||||
);
|
||||
|
||||
settings_->ExpectedUpdateFirmwareSlotId.setValue("");
|
||||
}
|
||||
|
||||
performed_boot_checks_ = true;
|
||||
}
|
||||
|
||||
void performFirmwareUpdate1_6() {
|
||||
if (!operation_.has_value())
|
||||
return;
|
||||
|
||||
auto const max_retries = optional::GetOrDefault(
|
||||
operation_->request.retries,
|
||||
settings_->FirmwareUpdateDefaultRetries.getValue()
|
||||
);
|
||||
|
||||
if ((int)operation_->total_failures > max_retries) {
|
||||
if (operation_->content_length == 0 || operation_->content_length > operation_->total_bytes_read) {
|
||||
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kDownloadFailed);
|
||||
} else {
|
||||
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kInstallationFailed);
|
||||
}
|
||||
operation_ = std::nullopt;
|
||||
restoreConnector0OperativeIfNeeded();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!operation_->last_status1_6.has_value()) {
|
||||
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kDownloading);
|
||||
} else {
|
||||
if (operation_->last_status1_6 == ocpp1_6::FirmwareStatus::kDownloaded) {
|
||||
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kInstalling);
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkOrRetryConnection(platform_, operation_.value()))
|
||||
return;
|
||||
|
||||
auto const remaining = (int)operation_->content_length - (int)operation_->total_bytes_read;
|
||||
if (remaining > 0) {
|
||||
operation_->buffer.resize(kChunkSize);
|
||||
auto const bytes_read = operation_->connection->read((char*)operation_->buffer.data(), (int)operation_->buffer.size());
|
||||
// Note: treating zero bytes read as a failure condition here to prevent an infinite loop under
|
||||
// those conditions.
|
||||
if (bytes_read <= 0) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data - retrying operation";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation_->total_bytes_read == 0) {
|
||||
if (operation_->running_firmware_update) {
|
||||
station_->finishUpdateProcess(false);
|
||||
operation_->running_firmware_update = false;
|
||||
}
|
||||
|
||||
if (station_->startUpdateProcess(operation_->content_length) != StationInterface::Result::kSucceeded) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed starting firmware update process";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
operation_->running_firmware_update = true;
|
||||
} else {
|
||||
if (!operation_->running_firmware_update) {
|
||||
CHARGELAB_LOG_MESSAGE(error)
|
||||
<< "Unexpected state - expected running firmware update operation";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (station_->processFirmwareChunk(operation_->buffer.data(), bytes_read) != StationInterface::Result::kSucceeded) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed processing firmware chunk";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: For ocpp1.6, the existing approach is to pre-set the hash value to the setting parameter and
|
||||
// read the firmware hash value from the firmware itself. Then compare them to decide if the downloaded firmware is the right one.
|
||||
// What approach is expected here?
|
||||
operation_->signature_hash->update((unsigned char const*)operation_->buffer.data(), bytes_read);
|
||||
operation_->total_bytes_read += bytes_read;
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Flashing progress: " << operation_->total_bytes_read << " / " << operation_->content_length;
|
||||
}
|
||||
|
||||
if (operation_->total_bytes_read >= operation_->content_length) {
|
||||
// TODO: Check signature
|
||||
|
||||
if (operation_->last_status1_6 == ocpp1_6::FirmwareStatus::kDownloading) {
|
||||
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kDownloaded);
|
||||
return;
|
||||
} else if (operation_->last_status1_6 == ocpp1_6::FirmwareStatus::kDownloaded) {
|
||||
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kInstalling);
|
||||
return;
|
||||
}
|
||||
|
||||
auto const slot_id = station_->getUpdateSlotId();
|
||||
if (station_->finishUpdateProcess(true) != StationInterface::Result::kSucceeded) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed finishing update process";
|
||||
operation_->connection = nullptr;
|
||||
operation_->total_failures++;
|
||||
return;
|
||||
}
|
||||
|
||||
//settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(std::to_string(operation_->request.requestId) + ":" + slot_id);
|
||||
settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(buildExpectedUpdateFirmwareSlotId(
|
||||
operation_->request.requestId, slot_id));
|
||||
|
||||
operation_ = std::nullopt;
|
||||
restoreConnector0OperativeIfNeeded();
|
||||
|
||||
reset_->resetOnIdle(ocpp2_0::BootReasonEnumType::kFirmwareUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
void restoreConnector0OperativeIfNeeded() {
|
||||
if (need_to_restore_connector_0_operative_) {
|
||||
connector_status_->setConnector0Inoperative(false);
|
||||
need_to_restore_connector_0_operative_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string buildExpectedUpdateFirmwareSlotId(int request_id, std::string const& slot_id) {
|
||||
return std::to_string(request_id) + ":" + slot_id;
|
||||
}
|
||||
|
||||
static void parseExpectedUpdateFirmwareSlotId(std::string const& expectedUpdateFirmwareSlotId,
|
||||
int& request_id, std::string& slot_id) {
|
||||
|
||||
auto index = expectedUpdateFirmwareSlotId.find(':');
|
||||
if (index != std::string::npos) {
|
||||
request_id = optional::GetOrDefault(string::ToInteger(expectedUpdateFirmwareSlotId.substr(0, index)), 0);
|
||||
slot_id = expectedUpdateFirmwareSlotId.substr(index+1);
|
||||
} else {
|
||||
request_id = 0;
|
||||
slot_id = expectedUpdateFirmwareSlotId;
|
||||
}
|
||||
}
|
||||
|
||||
PendingMessagePolicy buildPendingMessagePolicy(PendingMessageType message_type) {
|
||||
return PendingMessagePolicy {
|
||||
message_type,
|
||||
kOperationGroupId,
|
||||
settings_->NotificationMessageDefaultRetries.getValue(),
|
||||
settings_->NotificationMessageDefaultRetryInterval.getValue(),
|
||||
kPriorityFirmwareStatusNotification,
|
||||
false,
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<PlatformInterface> platform_;
|
||||
std::shared_ptr<ResetModule> reset_;
|
||||
std::shared_ptr<PendingMessagesModule> pending_messages_;
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_; // TODO: Remove dependency on ConnectorStatusModule
|
||||
std::shared_ptr<StationInterface> station_;
|
||||
std::shared_ptr<Settings> settings_;
|
||||
|
||||
/**
|
||||
* Set to true to force an OCPP 2.0.1 or 1.6 firmware status update and false to force an "idle" firmware status
|
||||
* update (only applies when an associated trigger message is received with an EVSE filter).
|
||||
*/
|
||||
std::optional<bool> force_update_ = std::nullopt;
|
||||
|
||||
std::optional<detail::FirmwareUpdateOperation<HM>> operation_ = std::nullopt;
|
||||
bool performed_boot_checks_ = false;
|
||||
|
||||
bool need_to_restore_connector_0_operative_ { false };
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_FIRMWARE_UPDATE_MODULE_H
|
||||
288
tools/openocpp/include/openocpp/module/get_diagnostics_module.h
Normal file
288
tools/openocpp/include/openocpp/module/get_diagnostics_module.h
Normal file
@@ -0,0 +1,288 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_GET_DIAGNOSTICS_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_GET_DIAGNOSTICS_MODULE_H
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "log_streaming_module.h"
|
||||
#include "openocpp/common/ring_buffer.h"
|
||||
#include "openocpp/common/operation_holder.h"
|
||||
#include "openocpp/interface/component/upload_interface.h"
|
||||
#include "openocpp/helpers/chrono.h"
|
||||
#include "openocpp/common/macro.h"
|
||||
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <sstream>
|
||||
#include <ctime>
|
||||
#include <utility>
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
struct DiagnosticsUploadState {
|
||||
explicit DiagnosticsUploadState(std::shared_ptr<SystemInterface> const& system)
|
||||
: pending_notification {system} {
|
||||
}
|
||||
|
||||
OperationHolder<std::string> pending_notification;
|
||||
|
||||
std::string location {};
|
||||
int failed_attempts = 0;
|
||||
int total_retries = 0;
|
||||
int retry_interval_seconds = 0;
|
||||
|
||||
std::atomic<DiagnosticsStatus> operation_state = DiagnosticsStatus::kPending;
|
||||
std::atomic<DiagnosticsStatus> messaging_state = DiagnosticsStatus::kPending;
|
||||
std::optional<std::thread> operation = std::nullopt;
|
||||
|
||||
std::optional<std::vector<uint8_t>> current_upload = std::nullopt;
|
||||
std::queue<detail::DiagnosticsLine> pending_upload;
|
||||
};
|
||||
}
|
||||
|
||||
class GetDiagnosticsModule : public ServiceStatefulGeneral {
|
||||
private:
|
||||
static constexpr int kBufferLimitBytes = 3*1024; // 10*1024;
|
||||
static constexpr int kRingBufferMaxSize = 25; // 50;
|
||||
// for diagnostic uploading
|
||||
static constexpr int kDefaultUploadRetries = 1;
|
||||
static constexpr int kDefaultUploadRetryIntervalSeconds = 10;
|
||||
static constexpr int kMaxBytesPerUpload = 3*1024; // 5*1024;
|
||||
|
||||
public:
|
||||
explicit GetDiagnosticsModule(
|
||||
Settings& settings,
|
||||
std::shared_ptr<SystemInterface> system_interface,
|
||||
std::shared_ptr<UploadInterface> upload
|
||||
) :
|
||||
system_interface_(std::move(system_interface)),
|
||||
upload_(std::move(upload)),
|
||||
charge_point_id_ {settings.getSetting(settings::CommonKey::kChargePointId)}
|
||||
{
|
||||
listener_ = std::make_shared<LoggingListenerFunction>([&](LogMetadata const& metadata, std::string_view const& message) {
|
||||
if (metadata.level != LogLevel::warning && metadata.level != LogLevel::error && metadata.level != LogLevel::fatal)
|
||||
return;
|
||||
|
||||
detail::DiagnosticsLine line {};
|
||||
line.level = metadata.level;
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
line.file = metadata.file;
|
||||
line.line = metadata.line;
|
||||
#endif
|
||||
line.function = metadata.function;
|
||||
line.message = message;
|
||||
|
||||
line.timestamp = system_interface_->systemClockNow();
|
||||
|
||||
std::lock_guard lock {mutex_};
|
||||
|
||||
int total_size = history_byte_size_ + line.size();
|
||||
while (total_size > kBufferLimitBytes && !history_.empty()) {
|
||||
total_size -= history_.front().size();
|
||||
history_.popFront();
|
||||
}
|
||||
if (total_size < kBufferLimitBytes) {
|
||||
history_byte_size_ = total_size;
|
||||
history_.pushBack(std::move(line));
|
||||
} else {
|
||||
history_byte_size_ = 0;
|
||||
}
|
||||
});
|
||||
|
||||
RegisterLoggingListener(listener_); // register this callback to the global listener
|
||||
}
|
||||
~GetDiagnosticsModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting GetDiagnosticsModule";
|
||||
UnregisterLoggingListener(listener_);
|
||||
}
|
||||
private:
|
||||
void runStep(ocpp1_6::ChargePointRemoteInterface &remote) override {
|
||||
if (!in_progress_.has_value())
|
||||
return;
|
||||
|
||||
auto &state = in_progress_.value();
|
||||
detail::DiagnosticsStatus const operation_state = state.operation_state;
|
||||
detail::DiagnosticsStatus const messaging_state = state.messaging_state;
|
||||
|
||||
if (operation_state == messaging_state)
|
||||
return;
|
||||
|
||||
ocpp1_6::DiagnosticsStatus status;
|
||||
|
||||
switch (operation_state) {
|
||||
default:
|
||||
assert(false && "Unexpected diagnostics status");
|
||||
|
||||
case detail::DiagnosticsStatus::kPending:
|
||||
case detail::DiagnosticsStatus::kUploading:
|
||||
status = ocpp1_6::DiagnosticsStatus::kUploading;
|
||||
break;
|
||||
case detail::DiagnosticsStatus::kUploaded:
|
||||
status = ocpp1_6::DiagnosticsStatus::kUploaded;
|
||||
state.operation->join();
|
||||
in_progress_ = std::nullopt;
|
||||
break;
|
||||
case detail::DiagnosticsStatus::kUploadFailed:
|
||||
status = ocpp1_6::DiagnosticsStatus::kUploadFailed;
|
||||
state.operation->join();
|
||||
in_progress_ = std::nullopt;
|
||||
break;
|
||||
}
|
||||
|
||||
state.messaging_state = operation_state;
|
||||
state.pending_notification.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
remote.sendDiagnosticsStatusNotificationReq({status})
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetDiagnosticsRsp>>
|
||||
onGetDiagnosticsReq(
|
||||
const ocpp1_6::GetDiagnosticsReq& req
|
||||
) override {
|
||||
if (in_progress_.has_value()) {
|
||||
return ocpp1_6::CallError {
|
||||
ocpp1_6::ErrorCode::kGenericError,
|
||||
"InProgress",
|
||||
"An uploading diagnostics operation is already in progress"
|
||||
};
|
||||
}
|
||||
|
||||
ocpp1_6::GetDiagnosticsRsp rsp;
|
||||
|
||||
std::queue<detail::DiagnosticsLine> pending_upload;
|
||||
{
|
||||
std::lock_guard lock{mutex_};
|
||||
int size = history_.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
if (req.startTime.has_value() && history_[i].timestamp < req.startTime->getTimestamp())
|
||||
continue;
|
||||
if (req.stopTime.has_value() && history_[i].timestamp > req.stopTime->getTimestamp())
|
||||
continue;
|
||||
pending_upload.push(history_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (pending_upload.empty()) {
|
||||
rsp.fileName = std::nullopt;
|
||||
return rsp;
|
||||
}
|
||||
|
||||
in_progress_.emplace(system_interface_);
|
||||
in_progress_->pending_upload = std::move(pending_upload);
|
||||
|
||||
rsp.fileName = buildDiagnosticsFileName();
|
||||
|
||||
auto &state = in_progress_.value();
|
||||
// Trim leading and trailing whitespaces
|
||||
std::string location = req.location.value();
|
||||
auto trim = [](const std::string &str, const std::string &whitespaces = " \t") -> std::string {
|
||||
auto begin = str.find_first_not_of(whitespaces);
|
||||
if (begin == std::string::npos) return "";
|
||||
auto end = str.find_last_not_of(whitespaces);
|
||||
return str.substr(begin, end - begin + 1);
|
||||
};
|
||||
location = trim(location);
|
||||
|
||||
if (!location.empty() && location.back() != '/')
|
||||
state.location = location + "/" + rsp.fileName.value();
|
||||
else
|
||||
state.location = location + rsp.fileName.value();
|
||||
|
||||
state.total_retries = optional::GetOrDefault(req.retries, kDefaultUploadRetries);
|
||||
state.retry_interval_seconds = optional::GetOrDefault(req.retryInterval,
|
||||
kDefaultUploadRetryIntervalSeconds);
|
||||
|
||||
state.operation = std::thread([&]() {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
CHARGELAB_TRY {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Starting diagnostics upload: " << state.location;
|
||||
|
||||
state.operation_state = detail::DiagnosticsStatus::kUploading;
|
||||
bool append = false;
|
||||
|
||||
while (!state.pending_upload.empty() || state.current_upload.has_value()) {
|
||||
if (!state.current_upload.has_value()) {
|
||||
std::string content;
|
||||
size_t content_len = 0;
|
||||
while (!state.pending_upload.empty()) {
|
||||
std::string s = state.pending_upload.front().to_string();
|
||||
if (content_len + s.size() > kMaxBytesPerUpload)
|
||||
break;
|
||||
content.append(s);
|
||||
content_len += s.size();
|
||||
state.pending_upload.pop();
|
||||
}
|
||||
state.current_upload.emplace(std::vector<uint8_t>(content.begin(), content.end()));
|
||||
}
|
||||
// uploading
|
||||
std::vector<uint8_t> const &content = state.current_upload.value();
|
||||
auto result = upload_->write(state.location, content, append, [&](std::size_t) {});
|
||||
if (result == UploadInterface::Result::kFailed) {
|
||||
if (++state.failed_attempts > state.total_retries) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Diagnostics upload failed writing";
|
||||
state.operation_state = detail::DiagnosticsStatus::kUploadFailed;
|
||||
return;
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::seconds(state.retry_interval_seconds));
|
||||
} else {
|
||||
state.current_upload = std::nullopt;
|
||||
append = true; // appending for next block
|
||||
}
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Diagnostics upload succeeded";
|
||||
state.operation_state = detail::DiagnosticsStatus::kUploaded;
|
||||
} CHARGELAB_CATCH {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Diagnostics upload failed unexpectedly: " << e.what();
|
||||
state.operation_state = detail::DiagnosticsStatus::kUploadFailed;
|
||||
}
|
||||
});
|
||||
|
||||
return rsp;
|
||||
}
|
||||
|
||||
void onDiagnosticsStatusNotificationRsp(
|
||||
std::string const& unique_id,
|
||||
ocpp1_6::ResponseMessage<::chargelab::ocpp1_6::DiagnosticsStatusNotificationRsp> const& rsp
|
||||
) override {
|
||||
if (in_progress_.has_value()) {
|
||||
auto& state = in_progress_.value();
|
||||
if (state.pending_notification == unique_id) {
|
||||
state.pending_notification = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<ocpp1_6::CallError>(rsp)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to FirmwareStatusNotification request: " << std::get<ocpp1_6::CallError> (rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string buildDiagnosticsFileName() {
|
||||
std::time_t t = system_interface_->systemClockNow()/1000;
|
||||
std::tm *tm = gmtime(&t);
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf, "%d-%02d-%02dT%02d-%02d-%02dZ", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
std::string file_name = "diagn_" + charge_point_id_.getValue() + "_" + std::string(buf) + ".txt";
|
||||
|
||||
return file_name;
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<SystemInterface> system_interface_;
|
||||
|
||||
std::shared_ptr<LoggingListenerFunction> listener_ = nullptr;
|
||||
std::mutex mutex_;
|
||||
RingBuffer<detail::DiagnosticsLine, kRingBufferMaxSize> history_; // keep the original diagnostics lines
|
||||
int history_byte_size_ = 0;
|
||||
|
||||
std::shared_ptr<UploadInterface> upload_;
|
||||
std::optional<detail::DiagnosticsUploadState> in_progress_ = std::nullopt;
|
||||
BasicTextSetting charge_point_id_;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_GET_DIAGNOSTICS_MODULE_H
|
||||
492
tools/openocpp/include/openocpp/module/get_logs_module.h
Normal file
492
tools/openocpp/include/openocpp/module/get_logs_module.h
Normal file
@@ -0,0 +1,492 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_GET_LOGS_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_GET_LOGS_MODULE_H
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/module/pending_messages_module.h"
|
||||
#include "openocpp/protocol/common/small_string.h"
|
||||
#include "openocpp/interface/element/rest_connection_interface.h"
|
||||
#include "openocpp/interface/platform_interface.h"
|
||||
#include "openocpp/common/ring_buffer.h"
|
||||
#include "openocpp/common/serialization.h"
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
struct UploadState {
|
||||
ocpp2_0::GetLogRequest request;
|
||||
std::string filename;
|
||||
|
||||
std::shared_ptr<RestConnectionInterface> connection = nullptr;
|
||||
int total_failures = 0;
|
||||
|
||||
std::size_t bytes_written = 0;
|
||||
std::size_t content_length = 0;
|
||||
int min_index = 0;
|
||||
int max_index = 0;
|
||||
int last_index = 0;
|
||||
|
||||
std::optional<ocpp2_0::UploadLogStatusEnumType> last_status2_0 = std::nullopt;
|
||||
std::optional<SteadyPointMillis> first_attempt = std::nullopt;
|
||||
};
|
||||
|
||||
struct LogLine {
|
||||
int message_index;
|
||||
SystemTimeMillis timestamp;
|
||||
logging::LogLevel level;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
class LogLineSerializer {
|
||||
public:
|
||||
static std::optional<LogLine> read(std::string_view const& text) {
|
||||
LogLine result;
|
||||
std::optional<int> index = 0;
|
||||
|
||||
index = readPrimitive(text, index, result.message_index);
|
||||
index = readPrimitive(text, index, result.timestamp);
|
||||
index = readPrimitive(text, index, result.level);
|
||||
index = readPrimitive(text, index, result.message);
|
||||
|
||||
if (!index.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string write(LogLine const& wrapper) {
|
||||
std::string result;
|
||||
|
||||
writePrimitive(result, wrapper.message_index);
|
||||
writePrimitive(result, wrapper.timestamp);
|
||||
writePrimitive(result, wrapper.level);
|
||||
writePrimitive(result, wrapper.message);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class GetLogsModule : public ServiceStatefulGeneral {
|
||||
private:
|
||||
// Note: this is the decompressed size of the dropped records
|
||||
static constexpr int kTargetDroppedLogMessageBytes = 1024;
|
||||
static constexpr int kMaxRetainedLogMessagesBytes = 2*1024;
|
||||
static constexpr int kUploadStepMaxTimeMillis = 100;
|
||||
static constexpr int kPriorityLogStatusNotification = 100;
|
||||
static constexpr int kQueueReportFrequencySeconds = 10;
|
||||
static constexpr int kMaxRingBufferSize = 5;
|
||||
|
||||
// Note: arbitrary random assigned ID
|
||||
static constexpr std::uint64_t kOperationGroupId = 0xDB0A571F9D6E2A10ull;
|
||||
|
||||
public:
|
||||
explicit GetLogsModule(
|
||||
std::shared_ptr<PlatformInterface> platform,
|
||||
std::shared_ptr<PendingMessagesModule> pending_messages
|
||||
)
|
||||
: platform_(std::move(platform)),
|
||||
pending_messages_(std::move(pending_messages))
|
||||
{
|
||||
assert(platform_ != nullptr);
|
||||
settings_ = platform_->getSettings();
|
||||
assert(settings_ != nullptr);
|
||||
|
||||
listener_ = std::make_shared<logging::LoggingListenerFunction>([&](logging::LogMetadata const& metadata, std::string_view const& message) {
|
||||
std::string merged_message;
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
merged_message += "[";
|
||||
merged_message += metadata.file;
|
||||
merged_message += ":";
|
||||
merged_message += std::to_string(metadata.line);
|
||||
merged_message += "]";
|
||||
#endif
|
||||
merged_message += message;
|
||||
|
||||
log_buffer_.pushBack(detail::LogLine{
|
||||
index_++,
|
||||
platform_->systemClockNow(),
|
||||
metadata.level,
|
||||
std::move(merged_message)
|
||||
});
|
||||
});
|
||||
|
||||
logging::RegisterLoggingListener(listener_); // register this callback to the global listener
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Size of platform pointer: " << sizeof(platform_);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Size of operation: " << sizeof(operation_);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Size of listener: " << sizeof(*listener_);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Size of queue: " << sizeof(log_queue_);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Size of buffer: " << sizeof(log_buffer_);
|
||||
}
|
||||
|
||||
private:
|
||||
void runUnconditionally() override {
|
||||
reportQueueSize();
|
||||
uploadLogs();
|
||||
flushLogMessages();
|
||||
}
|
||||
|
||||
void flushLogMessages() {
|
||||
for (int i=0; i < 10; i++) {
|
||||
auto next = log_buffer_.popFront();
|
||||
if (!next.has_value())
|
||||
break;
|
||||
|
||||
log_queue_.pushBack(std::move(next.value()));
|
||||
}
|
||||
|
||||
while (log_queue_.totalBytes() > kMaxRetainedLogMessagesBytes) {
|
||||
std::size_t total_removed = 0;
|
||||
log_queue_.removeIf([&] (std::string_view const& text, detail::LogLine const&) {
|
||||
if (total_removed > kTargetDroppedLogMessageBytes)
|
||||
return false;
|
||||
|
||||
total_removed += text.size();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetLogResponse>>
|
||||
onGetLogReq(const ocpp2_0::GetLogRequest& request) override {
|
||||
if (!uri::parseHttpUri(request.log.remoteLocation.value()).has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad remoteLocation: " << request.log.remoteLocation.value();
|
||||
return ocpp2_0::GetLogResponse {ocpp2_0::LogStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
std::string filename = settings_->ChargerSerialNumber.getValue();
|
||||
switch (request.logType) {
|
||||
case ocpp2_0::LogEnumType::kValueNotFoundInEnum:
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad logType in request";
|
||||
return ocpp2_0::GetLogResponse {ocpp2_0::LogStatusEnumType::kRejected};
|
||||
|
||||
case ocpp2_0::LogEnumType::kDiagnosticsLog:
|
||||
filename += "-diagnostics-";
|
||||
break;
|
||||
|
||||
case ocpp2_0::LogEnumType::kSecurityLog:
|
||||
filename += "-security-";
|
||||
break;
|
||||
}
|
||||
filename += std::to_string(platform_->systemClockNow()/1000) + ".txt";
|
||||
|
||||
ocpp2_0::LogStatusEnumType status;
|
||||
if (!operation_.has_value()) {
|
||||
// N01.FR.01
|
||||
status = ocpp2_0::LogStatusEnumType::kAccepted;
|
||||
} else {
|
||||
// N01.FR.12
|
||||
status = ocpp2_0::LogStatusEnumType::kAcceptedCanceled;
|
||||
|
||||
// N01.FR.20
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kAcceptedCanceled);
|
||||
}
|
||||
|
||||
operation_ = detail::UploadState {request, filename};
|
||||
return ocpp2_0::GetLogResponse {status, filename};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
|
||||
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
|
||||
(void)req;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
|
||||
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &req) override {
|
||||
if (req.requestedMessage != ocpp2_0::MessageTriggerEnumType::kLogStatusNotification)
|
||||
return std::nullopt;
|
||||
|
||||
if (req.evse.has_value() || !operation_.has_value()) {
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::LogStatusNotificationRequest {
|
||||
ocpp2_0::UploadLogStatusEnumType::kIdle
|
||||
},
|
||||
PendingMessagePolicy {
|
||||
PendingMessageType::kNotificationEvent,
|
||||
kOperationGroupId,
|
||||
settings_->NotificationMessageDefaultRetries.getValue(),
|
||||
settings_->NotificationMessageDefaultRetryInterval.getValue(),
|
||||
kPriorityLogStatusNotification,
|
||||
false,
|
||||
false
|
||||
}
|
||||
);
|
||||
} else {
|
||||
operation_->last_status2_0 = std::nullopt;
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploading);
|
||||
}
|
||||
|
||||
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
|
||||
}
|
||||
|
||||
private:
|
||||
void reportQueueSize() {
|
||||
auto const now = platform_->steadyClockNow();
|
||||
if (last_queue_size_report_.has_value()) {
|
||||
auto const delta = now - last_queue_size_report_.value();
|
||||
if (delta < kQueueReportFrequencySeconds*1000)
|
||||
return;
|
||||
}
|
||||
|
||||
last_queue_size_report_ = now;
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Total log queue size: " << log_queue_.totalBytes();
|
||||
}
|
||||
|
||||
void uploadLogs() {
|
||||
if (!operation_.has_value())
|
||||
return;
|
||||
|
||||
if ((int)operation_->total_failures > optional::GetOrDefault(operation_->request.retries, 0)) {
|
||||
// N01.FR.10
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
|
||||
operation_ = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkOrRetryConnection())
|
||||
return;
|
||||
if (!operation_->first_attempt.has_value())
|
||||
operation_->first_attempt = platform_->steadyClockNow();
|
||||
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploading);
|
||||
|
||||
bool wrote_first = false;
|
||||
bool ran_out_of_time = false;
|
||||
auto const start_ts = platform_->steadyClockNow();
|
||||
{
|
||||
bool only_security_logs = operation_->request.logType == ocpp2_0::LogEnumType::kSecurityLog;
|
||||
log_queue_.visit([&] (std::string_view const&, detail::LogLine const& line) {
|
||||
if (ran_out_of_time)
|
||||
return;
|
||||
if (operation_->connection == nullptr)
|
||||
return;
|
||||
if (operation_->bytes_written >= operation_->content_length)
|
||||
return;
|
||||
|
||||
if (wrote_first) {
|
||||
if (platform_->steadyClockNow() - start_ts > kUploadStepMaxTimeMillis) {
|
||||
ran_out_of_time = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto const& log = operation_->request.log;
|
||||
if (log.oldestTimestamp.has_value() && log.oldestTimestamp->isAfter(line.timestamp, false))
|
||||
return;
|
||||
if (log.latestTimestamp.has_value() && log.latestTimestamp->isBefore(line.timestamp, false))
|
||||
return;
|
||||
if (only_security_logs && line.message.find("[security]") == std::string::npos)
|
||||
return;
|
||||
|
||||
// Note: allowing for integer overflow; checking if message_index < min_index
|
||||
if (line.message_index - operation_->min_index < 0)
|
||||
return;
|
||||
|
||||
// Note: allowing for integer overflow; checking if message_index > max_index
|
||||
if (line.message_index - operation_->max_index > 0)
|
||||
return;
|
||||
|
||||
// Note: allowing for integer overflow; checking if message_index <= last_index
|
||||
if (line.message_index - operation_->last_index <= 0)
|
||||
return;
|
||||
|
||||
// Truncate the message to fit in the original content_length
|
||||
auto message = renderLine(line);
|
||||
if (operation_->bytes_written + message.size() > operation_->content_length) {
|
||||
auto const truncated = operation_->bytes_written + message.size() - operation_->content_length;
|
||||
message.resize(std::min(std::max((std::size_t)0, truncated), message.size()));
|
||||
}
|
||||
|
||||
if (!operation_->connection->write(message.data(), message.size())) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Failed writing payload at " << operation_->bytes_written << " bytes";
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
|
||||
operation_->total_failures++;
|
||||
operation_->connection = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
operation_->last_index = line.message_index;
|
||||
operation_->bytes_written += message.size();
|
||||
wrote_first = true;
|
||||
});
|
||||
}
|
||||
|
||||
if (ran_out_of_time)
|
||||
return;
|
||||
if (operation_->connection == nullptr)
|
||||
return;
|
||||
|
||||
std::string buffer;
|
||||
while (operation_->bytes_written < operation_->content_length) {
|
||||
// Note: this can happen if log lines are removed while an upload operation is in progress
|
||||
buffer.resize(std::min((std::size_t)1024, operation_->content_length - operation_->bytes_written), ' ');
|
||||
|
||||
if (!operation_->connection->write(buffer.data(), buffer.size())) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed writing padding to server";
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
|
||||
operation_->total_failures++;
|
||||
operation_->connection = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Added padding: " << buffer.size() << " bytes";
|
||||
operation_->bytes_written += buffer.size();
|
||||
}
|
||||
|
||||
if (!operation_->connection->send()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed finishing HTTP request";
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
|
||||
operation_->total_failures++;
|
||||
operation_->connection = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const status = operation_->connection->getStatusCode();
|
||||
if (status == 403) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unauthorized response from server: " << status;
|
||||
// N01.FR.10
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kPermissionDenied);
|
||||
operation_->total_failures++;
|
||||
operation_->connection = nullptr;
|
||||
return;
|
||||
}
|
||||
if (status < 200 || status >= 300) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected status code: " << status;
|
||||
// N01.FR.10
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kBadMessage);
|
||||
operation_->total_failures++;
|
||||
operation_->connection = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Upload succeeded - response was: " << status;
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploaded);
|
||||
operation_ = std::nullopt;
|
||||
}
|
||||
|
||||
static std::string renderLine(detail::LogLine const& line) {
|
||||
std::string result;
|
||||
result += "[" + write_json_to_string(ocpp2_0::DateTime{line.timestamp}) + "] ";
|
||||
result += "[" + write_json_to_string(line.level) + "] ";
|
||||
result += line.message;
|
||||
result += "\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
bool checkOrRetryConnection() {
|
||||
if (!operation_.has_value())
|
||||
return false;
|
||||
if (operation_->connection != nullptr)
|
||||
return true;
|
||||
|
||||
if (operation_->first_attempt.has_value()) {
|
||||
auto const delta_seconds = (platform_->steadyClockNow() - operation_->first_attempt.value())/1000;
|
||||
auto const interval = optional::GetOrDefault(operation_->request.retryInterval, 0);
|
||||
if (delta_seconds < interval*operation_->total_failures)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const uri = operation_->request.log.remoteLocation.value();
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Uploading logs to: " << uri;
|
||||
operation_->connection = platform_->putRequest(uri);
|
||||
if (operation_->connection == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed creating post request for: " << uri;
|
||||
// N01.FR.10
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
|
||||
operation_->total_failures++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compute the size and index limits
|
||||
std::optional<int> min_index = std::nullopt;
|
||||
std::optional<int> max_index = std::nullopt;
|
||||
std::size_t content_length = 0;
|
||||
{
|
||||
log_queue_.visit([&] (std::string_view const&, detail::LogLine const& line) {
|
||||
auto const& log = operation_->request.log;
|
||||
if (log.oldestTimestamp.has_value() && log.oldestTimestamp->isAfter(line.timestamp, false))
|
||||
return;
|
||||
if (log.latestTimestamp.has_value() && log.latestTimestamp->isBefore(line.timestamp, false))
|
||||
return;
|
||||
|
||||
if (!min_index.has_value()) {
|
||||
min_index = line.message_index;
|
||||
} else {
|
||||
// Note: allowing for integer overflow; checking if message_index < min_index
|
||||
if (line.message_index - min_index.value() < 0)
|
||||
min_index = line.message_index;
|
||||
}
|
||||
|
||||
if (!max_index.has_value()) {
|
||||
max_index = line.message_index;
|
||||
} else {
|
||||
// Note: allowing for integer overflow; checking if message_index > max_index
|
||||
if (line.message_index - max_index.value() > 0)
|
||||
max_index = line.message_index;
|
||||
}
|
||||
|
||||
content_length += renderLine(line).size();
|
||||
});
|
||||
}
|
||||
|
||||
operation_->bytes_written = 0;
|
||||
operation_->content_length = content_length;
|
||||
operation_->min_index = optional::GetOrDefault(min_index, 0);
|
||||
operation_->max_index = optional::GetOrDefault(max_index, 0);
|
||||
operation_->last_index = optional::GetOrDefault(min_index, 0) - 1;
|
||||
|
||||
// N01.FR.19
|
||||
operation_->connection->setHeader("Content-Type", "text/plain");
|
||||
operation_->connection->setHeader("Content-Disposition", operation_->filename);
|
||||
|
||||
if (!operation_->connection->open(content_length)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed opening connection to server";
|
||||
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
|
||||
operation_->total_failures++;
|
||||
operation_->connection = nullptr;
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Connection opened to server - uploading " << content_length << " bytes...";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType status) {
|
||||
if (operation_->last_status2_0 == std::make_optional(status))
|
||||
return;
|
||||
|
||||
pending_messages_->sendRequest2_0(
|
||||
ocpp2_0::LogStatusNotificationRequest {
|
||||
status,
|
||||
operation_->request.requestId
|
||||
},
|
||||
PendingMessagePolicy {
|
||||
PendingMessageType::kNotificationEvent,
|
||||
kOperationGroupId,
|
||||
settings_->NotificationMessageDefaultRetries.getValue(),
|
||||
settings_->NotificationMessageDefaultRetryInterval.getValue(),
|
||||
kPriorityLogStatusNotification,
|
||||
false,
|
||||
false
|
||||
}
|
||||
);
|
||||
|
||||
operation_->last_status2_0 = status;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<PlatformInterface> platform_;
|
||||
std::shared_ptr<PendingMessagesModule> pending_messages_;
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<logging::LoggingListenerFunction> listener_;
|
||||
|
||||
std::optional<detail::UploadState> operation_ = std::nullopt;
|
||||
std::optional<SteadyPointMillis> last_queue_size_report_ = std::nullopt;
|
||||
RingBuffer<detail::LogLine, kMaxRingBufferSize> log_buffer_;
|
||||
|
||||
std::atomic<int> index_ = 0;
|
||||
CompressedQueueCustom<detail::LogLine, detail::LogLineSerializer> log_queue_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_GET_LOGS_MODULE_H
|
||||
129
tools/openocpp/include/openocpp/module/heartbeat_module.h
Normal file
129
tools/openocpp/include/openocpp/module/heartbeat_module.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_HEARTBEAT_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_HEARTBEAT_MODULE_H
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/common/operation_holder.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace chargelab {
|
||||
class HeartbeatModule : public ServiceStatefulGeneral {
|
||||
public:
|
||||
HeartbeatModule(
|
||||
std::shared_ptr<Settings> settings,
|
||||
std::shared_ptr<SystemInterface> const& system_interface
|
||||
)
|
||||
: settings_(std::move(settings)),
|
||||
system_interface_(system_interface),
|
||||
pending_heartbeat_req_ {system_interface}
|
||||
{
|
||||
}
|
||||
|
||||
~HeartbeatModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting HeartbeatModule";
|
||||
}
|
||||
|
||||
public:
|
||||
void runStep(ocpp1_6::OcppRemote &remote) override {
|
||||
if (!pending_heartbeat_req_.wasIdleFor(settings_->HeartbeatInterval.getValue())) {
|
||||
if (!force_heartbeat_ || pending_heartbeat_req_.operationInProgress())
|
||||
return;
|
||||
}
|
||||
|
||||
pending_heartbeat_req_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
remote.sendHeartbeatReq({})
|
||||
);
|
||||
force_heartbeat_ = false;
|
||||
}
|
||||
|
||||
void onHeartbeatRsp(
|
||||
const std::string &unique_id,
|
||||
const std::variant<ocpp1_6::HeartbeatRsp, ocpp1_6::CallError> &rsp
|
||||
) override {
|
||||
if (pending_heartbeat_req_ == unique_id) {
|
||||
pending_heartbeat_req_ = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<ocpp1_6::HeartbeatRsp>(rsp)) {
|
||||
auto const& value = std::get<ocpp1_6::HeartbeatRsp>(rsp);
|
||||
auto const& ts = value.currentTime.getTimestamp();
|
||||
|
||||
if (ts.has_value()) {
|
||||
system_interface_->setSystemClock(ts.value());
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Invalid Heartbeat response timestamp: "
|
||||
<< value.currentTime;
|
||||
}
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to Hearbeat request: " << std::get<ocpp1_6::CallError> (rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runStep(ocpp2_0::OcppRemote &remote) override {
|
||||
if (!pending_heartbeat_req_.wasIdleFor(settings_->HeartbeatInterval.getValue())) {
|
||||
if (!force_heartbeat_ || pending_heartbeat_req_.operationInProgress())
|
||||
return;
|
||||
}
|
||||
|
||||
pending_heartbeat_req_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
remote.sendHeartbeatReq({})
|
||||
);
|
||||
force_heartbeat_ = false;
|
||||
}
|
||||
|
||||
void onHeartbeatRsp(
|
||||
const std::string &unique_id,
|
||||
const std::variant<ocpp2_0::HeartbeatResponse, ocpp2_0::CallError> &rsp
|
||||
) override {
|
||||
if (pending_heartbeat_req_ == unique_id) {
|
||||
pending_heartbeat_req_ = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<ocpp2_0::HeartbeatResponse>(rsp)) {
|
||||
auto const& value = std::get<ocpp2_0::HeartbeatResponse>(rsp);
|
||||
auto const& ts = value.currentTime.getTimestamp();
|
||||
|
||||
if (ts.has_value()) {
|
||||
system_interface_->setSystemClock(ts.value());
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Invalid Heartbeat response timestamp: "
|
||||
<< value.currentTime;
|
||||
}
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to Hearbeat request: " << std::get<ocpp2_0::CallError> (rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
|
||||
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
|
||||
if (req.requestedMessage == ocpp1_6::MessageTrigger::kHeartbeat) {
|
||||
force_heartbeat_ = true;
|
||||
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kAccepted};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
|
||||
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &request) override {
|
||||
if (request.requestedMessage == ocpp2_0::MessageTriggerEnumType::kHeartbeat) {
|
||||
force_heartbeat_ = true;
|
||||
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<SystemInterface> system_interface_;
|
||||
OperationHolder<std::string> pending_heartbeat_req_;
|
||||
bool force_heartbeat_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_HEARTBEAT_MODULE_H
|
||||
218
tools/openocpp/include/openocpp/module/local_limits_module.h
Normal file
218
tools/openocpp/include/openocpp/module/local_limits_module.h
Normal file
@@ -0,0 +1,218 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_LOCAL_LIMITS_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_LOCAL_LIMITS_MODULE_H
|
||||
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <iostream>
|
||||
|
||||
namespace chargelab {
|
||||
struct LeaseAmps {
|
||||
int created_index;
|
||||
double allocated_amps;
|
||||
SteadyPointMillis expiry;
|
||||
|
||||
std::optional<int> completed_index = std::nullopt;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct LocalLimitState {
|
||||
std::string charge_point_id_;
|
||||
std::vector<LeaseAmps> active_leases_;
|
||||
|
||||
std::optional<double> requested_amps_;
|
||||
std::optional<SteadyPointMillis> requested_amps_ts_;
|
||||
|
||||
// Informational
|
||||
std::optional<double> last_reported_amps_;
|
||||
std::optional<double> last_reported_watts_;
|
||||
};
|
||||
}
|
||||
|
||||
class LocalLimitModule {
|
||||
private:
|
||||
static constexpr int kRequestExpirySeconds = 3600;
|
||||
static constexpr int kLeaseDurationSeconds = 3600;
|
||||
static constexpr int kLeaseRefreshThresholdSeconds = 15*60;
|
||||
|
||||
public:
|
||||
explicit LocalLimitModule(
|
||||
std::shared_ptr<chargelab::SystemInterface> system,
|
||||
double max_amps
|
||||
)
|
||||
: system_(std::move(system)),
|
||||
max_amps_(max_amps)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
int getUniqueId(std::string const& charge_point_id) {
|
||||
std::lock_guard lock {mutex_};
|
||||
for (auto const& x : state_) {
|
||||
if (x.second.charge_point_id_ == charge_point_id)
|
||||
return x.first;
|
||||
}
|
||||
|
||||
auto result = last_id_++;
|
||||
state_[result].charge_point_id_ = charge_point_id;
|
||||
return result;
|
||||
}
|
||||
|
||||
void setRequestedAmps(int id, double amps) {
|
||||
std::lock_guard lock {mutex_};
|
||||
state_[id].requested_amps_ = amps;
|
||||
state_[id].requested_amps_ts_ = system_->steadyClockNow();
|
||||
}
|
||||
|
||||
void setCurrentAllocatedAmps(int id, double amps) {
|
||||
std::lock_guard lock {mutex_};
|
||||
state_[id].last_reported_amps_ = amps;
|
||||
}
|
||||
|
||||
void setCurrentWatts(int id, double watts) {
|
||||
std::lock_guard lock {mutex_};
|
||||
state_[id].last_reported_watts_ = watts;
|
||||
}
|
||||
|
||||
std::optional<LeaseAmps> leaseAmps(int id, std::optional<LeaseAmps> const& current) {
|
||||
std::lock_guard lock {mutex_};
|
||||
auto result = leaseAmpsImpl(id);
|
||||
|
||||
bool refresh = false;
|
||||
if (result.allocated_amps != current->allocated_amps)
|
||||
refresh = true;
|
||||
if ((current->expiry - system_->steadyClockNow())/1000 < kLeaseRefreshThresholdSeconds)
|
||||
refresh = true;
|
||||
|
||||
if (!refresh)
|
||||
return std::nullopt;
|
||||
|
||||
state_[id].active_leases_.push_back(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void activateLease(int id, LeaseAmps& current) {
|
||||
std::lock_guard lock {mutex_};
|
||||
|
||||
int completed_index = lease_index_++;
|
||||
current.completed_index = completed_index;
|
||||
|
||||
auto& leases = state_[id].active_leases_;
|
||||
for (auto& x : leases) {
|
||||
if (x.created_index == current.created_index) {
|
||||
x.completed_index = completed_index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
leases.erase(
|
||||
std::remove_if(
|
||||
leases.begin(),
|
||||
leases.end(),
|
||||
[=](LeaseAmps const& x){return x.completed_index && current.created_index - x.completed_index.value() > 0;}
|
||||
),
|
||||
leases.end()
|
||||
);
|
||||
}
|
||||
|
||||
void printCurrentAmps() {
|
||||
std::lock_guard lock {mutex_};
|
||||
|
||||
system("clear");
|
||||
|
||||
auto now = system_->steadyClockNow();
|
||||
for (auto& x : state_) {
|
||||
auto lease = removeDeadEntriesAndGetMaxLease(x.second.active_leases_);
|
||||
if (!lease)
|
||||
continue;
|
||||
|
||||
std::cout << "ID " << x.first << " {" << x.second.charge_point_id_ << "}: ";
|
||||
std::cout << std::setw(4) << std::round(lease->allocated_amps*10)/10.0 << "A (" << std::setw(2) << static_cast<int>((lease->expiry - now)/1000) << "s)";
|
||||
if (x.second.last_reported_amps_ && x.second.requested_amps_)
|
||||
std::cout << " " << std::setw(4) << std::round(x.second.last_reported_amps_.value()*10)/10.0 << "A/" << std::setw(4) << std::round(x.second.requested_amps_.value()*10)/10.0 << "A";
|
||||
if (x.second.last_reported_watts_)
|
||||
std::cout << " " << std::setw(4) << std::round(x.second.last_reported_watts_.value()) << "W";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
LeaseAmps leaseAmpsImpl(int id) {
|
||||
auto now = system_->steadyClockNow();
|
||||
auto expiry = static_cast<SteadyPointMillis>(now + kLeaseDurationSeconds*1000);
|
||||
int index = lease_index_++;
|
||||
|
||||
auto const& state = state_[id];
|
||||
if (!state.requested_amps_)
|
||||
return {index, 0.0, expiry};
|
||||
if (!state.requested_amps_ts_ || (now - state.requested_amps_ts_.value())/1000.0 >= kRequestExpirySeconds)
|
||||
return {index, 0.0, expiry};
|
||||
|
||||
double total_requested = 0;
|
||||
for (auto const& x : state_) {
|
||||
if (!x.second.requested_amps_ || !x.second.requested_amps_ts_)
|
||||
continue;
|
||||
if ((now - x.second.requested_amps_ts_.value())/1000.0 >= kRequestExpirySeconds)
|
||||
continue;
|
||||
|
||||
total_requested += x.second.requested_amps_.value();
|
||||
}
|
||||
|
||||
double total_leased = 0;
|
||||
for (auto& x : state_) {
|
||||
if (x.first == id)
|
||||
continue;
|
||||
|
||||
auto lease = removeDeadEntriesAndGetMaxLease(x.second.active_leases_);
|
||||
if (lease)
|
||||
total_leased += lease->allocated_amps;
|
||||
}
|
||||
|
||||
auto requested = state.requested_amps_.value();
|
||||
auto result = std::min(requested, requested*max_amps_/total_requested);
|
||||
result = std::min(max_amps_ - total_leased, result);
|
||||
result = std::max(0.0, result);
|
||||
|
||||
return {index, result, expiry};
|
||||
}
|
||||
|
||||
std::optional<LeaseAmps> removeDeadEntriesAndGetMaxLease(std::vector<LeaseAmps>& leases) {
|
||||
auto now = system_->steadyClockNow();
|
||||
leases.erase(
|
||||
std::remove_if(
|
||||
leases.begin(),
|
||||
leases.end(),
|
||||
[&](auto const& x) {return now - x.expiry > 0;}
|
||||
),
|
||||
leases.end()
|
||||
);
|
||||
|
||||
std::optional<LeaseAmps> max_lease;
|
||||
for (auto const& x : leases) {
|
||||
if (max_lease) {
|
||||
if (x.allocated_amps < max_lease->allocated_amps)
|
||||
continue;
|
||||
if (x.allocated_amps == max_lease->allocated_amps && x.expiry - max_lease->expiry < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
max_lease = x;
|
||||
}
|
||||
|
||||
return max_lease;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<chargelab::SystemInterface> system_;
|
||||
double max_amps_;
|
||||
int last_id_ = 0;
|
||||
int lease_index_ = 0;
|
||||
|
||||
std::mutex mutex_;
|
||||
std::map<int, detail::LocalLimitState> state_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_LOCAL_LIMITS_MODULE_H
|
||||
231
tools/openocpp/include/openocpp/module/log_streaming_module.h
Normal file
231
tools/openocpp/include/openocpp/module/log_streaming_module.h
Normal file
@@ -0,0 +1,231 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_LOG_STREAMING_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_LOG_STREAMING_MODULE_H
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
|
||||
#include <utility>
|
||||
#include <sstream>
|
||||
|
||||
namespace chargelab {
|
||||
namespace detail {
|
||||
struct LogLine {
|
||||
LogLevel level = LogLevel::kValueNotFoundInEnum;
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
std::string file {};
|
||||
int line = -1;
|
||||
#endif
|
||||
std::string function {};
|
||||
std::string message {};
|
||||
|
||||
int size() {
|
||||
return sizeof(LogLine) +
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
file.size() +
|
||||
#endif
|
||||
function.size() + message.size();
|
||||
}
|
||||
};
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(LogLine, level, file, line, function, message)
|
||||
#else
|
||||
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(LogLine, level, function, message)
|
||||
#endif
|
||||
struct LogMessage {
|
||||
std::vector<LogLine> messages;
|
||||
};
|
||||
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(LogMessage, messages)
|
||||
}
|
||||
|
||||
class LogStreamingModule : public ChargePointServiceStateful {
|
||||
private:
|
||||
static const int kBufferLimitBytes = 3*1024; // 10*1024;
|
||||
static const int kRingBufferMaxSize = 25; // 50;
|
||||
static const int kMaxLinesPerMessage = 15; // 20;
|
||||
static const int kBufferLimitMillis = 30*1000; // 30*1000;
|
||||
public:
|
||||
LogStreamingModule(
|
||||
Settings& settings,
|
||||
std::shared_ptr<SystemInterface> const& system_interface
|
||||
) : system_interface_(system_interface),
|
||||
pending_dump_request_ {system_interface},
|
||||
log_streaming_enabled_ {chargelab::settings::CreateBasicBooleanSetting<false>(
|
||||
settings,
|
||||
settings::CommonKey::kLogStreamingEnabled,
|
||||
"LogStreamingEnabled"
|
||||
)}
|
||||
{
|
||||
listener_ = std::make_shared<LoggingListenerFunction>([&](LogMetadata const& metadata, std::string_view const& message) {
|
||||
// TODO: Find a better way to filter out specific messages related to the dump itself
|
||||
if (message.find("Writing text message") != std::string::npos && message.find("StreamingLogMessage") != std::string::npos) {
|
||||
return;
|
||||
}
|
||||
if (!log_streaming_enabled_.getValue())
|
||||
return;
|
||||
|
||||
detail::LogLine line {};
|
||||
line.level = metadata.level;
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
line.file = metadata.file;
|
||||
line.line = metadata.line;
|
||||
line.file.shrink_to_fit();
|
||||
#endif
|
||||
line.function = metadata.function;
|
||||
line.message = message;
|
||||
|
||||
line.function.shrink_to_fit();
|
||||
line.message.shrink_to_fit();
|
||||
|
||||
std::lock_guard lock{mutex_};
|
||||
history_.pushBack(std::move(line));
|
||||
});
|
||||
|
||||
RegisterLoggingListener(listener_);
|
||||
}
|
||||
|
||||
~LogStreamingModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting LogStreamingModule";
|
||||
UnregisterLoggingListener(listener_);
|
||||
}
|
||||
|
||||
private:
|
||||
void runStep(ocpp1_6::ChargePointRemoteInterface &remote) override {
|
||||
{
|
||||
std::lock_guard lock{mutex_};
|
||||
if (history_.empty())
|
||||
return;
|
||||
|
||||
auto const now = system_interface_->steadyClockNow();
|
||||
if (history_.size() < kMaxLinesPerMessage && !getHistoryLimitIndex().has_value()) {
|
||||
if (!last_dump_.has_value())
|
||||
last_dump_ = now;
|
||||
|
||||
auto const delta = now - last_dump_.value();
|
||||
if (delta < kBufferLimitMillis) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pending_dump_request_.operationInProgress()) {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Flushing log messages - total size was: " << getHistorySize() << " bytes" << ", total memory size: " << getHistoryMemorySize()
|
||||
<< ",ring buffer size: " << history_.size();
|
||||
|
||||
detail::LogMessage payload {};
|
||||
{
|
||||
std::lock_guard lock{mutex_};
|
||||
int total_size = 0;
|
||||
for (int i=0; i < kMaxLinesPerMessage && !history_.empty(); i++) {
|
||||
if (total_size + history_.front().size() > kBufferLimitBytes) break;
|
||||
total_size += history_.front().size();
|
||||
|
||||
detail::LogLine value = std::move(history_.front());
|
||||
|
||||
history_.popFront();
|
||||
|
||||
payload.messages.push_back(std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
pending_dump_request_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
remote.sendDataTransferReq(ocpp1_6::DataTransferReq {
|
||||
ocpp1_6::CiString255Type("ChargeLab"),
|
||||
ocpp1_6::CiString50Type("StreamingLogMessage"),
|
||||
write_json_to_string(payload)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
std::lock_guard lock{mutex_};
|
||||
auto limit = getHistoryLimitIndex();
|
||||
if (limit.has_value()) {
|
||||
for (int i=0; i <= limit.value(); i++) {
|
||||
history_.front() = detail::LogLine {};
|
||||
history_.popFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onDataTransferRsp(
|
||||
const std::string &unique_id,
|
||||
const ocpp1_6::ResponseMessage <chargelab::ocpp1_6::DataTransferRsp> &rsp
|
||||
) override {
|
||||
if (pending_dump_request_ == unique_id) {
|
||||
pending_dump_request_ = kNoOperation;
|
||||
last_dump_ = system_interface_->steadyClockNow();
|
||||
|
||||
if (std::holds_alternative<ocpp1_6::CallError>(rsp)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to DataTransfer request: " << std::get<ocpp1_6::CallError> (rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<int> getHistoryLimitIndex() {
|
||||
int index;
|
||||
std::size_t total_bytes = 0;
|
||||
for (index = history_.size()-1; index >= 0; index--) {
|
||||
auto const& value = history_[index];
|
||||
total_bytes += sizeof(value.level);
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
total_bytes += sizeof(std::string) + value.file.size();
|
||||
total_bytes += sizeof(value.line);
|
||||
#endif
|
||||
total_bytes += sizeof(std::string) + value.function.size();
|
||||
total_bytes += sizeof(std::string) + value.message.size();
|
||||
|
||||
if (total_bytes >= kBufferLimitBytes)
|
||||
return index;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::size_t getHistorySize() {
|
||||
std::size_t total_bytes = 0;
|
||||
for (int index=0; index < history_.size(); index++) {
|
||||
auto const& value = history_[index];
|
||||
total_bytes += sizeof(value.level);
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
total_bytes += sizeof(std::string) + value.file.size();
|
||||
total_bytes += sizeof(value.line);
|
||||
#endif
|
||||
total_bytes += sizeof(std::string) + value.function.size();
|
||||
total_bytes += sizeof(std::string) + value.message.size();
|
||||
}
|
||||
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
std::size_t getHistoryMemorySize() {
|
||||
std::size_t total_bytes = 0;
|
||||
for (int index=0; index < history_.size(); index++) {
|
||||
auto const& value = history_[index];
|
||||
total_bytes += sizeof(value.level);
|
||||
#if defined(LOG_WITH_FILE_AND_LINE)
|
||||
total_bytes += sizeof(std::string) + value.file.capacity();
|
||||
total_bytes += sizeof(value.line);
|
||||
#endif
|
||||
total_bytes += sizeof(std::string) + value.function.capacity();
|
||||
total_bytes += sizeof(std::string) + value.message.capacity();
|
||||
}
|
||||
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<SystemInterface> system_interface_;
|
||||
OperationHolder<std::string> pending_dump_request_;
|
||||
BasicBooleanSetting<false> log_streaming_enabled_;
|
||||
|
||||
std::optional<SteadyPointMillis> last_dump_ = std::nullopt;
|
||||
std::shared_ptr<LoggingListenerFunction> listener_ = nullptr;
|
||||
|
||||
std::mutex mutex_;
|
||||
RingBuffer<detail::LogLine, kRingBufferMaxSize> history_;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_LOG_STREAMING_MODULE_H
|
||||
967
tools/openocpp/include/openocpp/module/pending_messages_module.h
Normal file
967
tools/openocpp/include/openocpp/module/pending_messages_module.h
Normal file
@@ -0,0 +1,967 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_PENDING_MESSAGES_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_PENDING_MESSAGES_MODULE_H
|
||||
|
||||
#include "openocpp/interface/platform_interface.h"
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/common/operation_holder.h"
|
||||
#include "openocpp/common/compressed_queue.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/common/serialization.h"
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace chargelab {
|
||||
CHARGELAB_JSON_ENUM(PendingMessageType,
|
||||
TransactionEvent,
|
||||
SecurityEvent,
|
||||
NotificationEvent,
|
||||
Other
|
||||
);
|
||||
|
||||
struct PendingMessagePolicy {
|
||||
PendingMessageType message_type;
|
||||
std::optional<uint64_t> group_id = std::nullopt;
|
||||
int message_attempts = 1;
|
||||
int retry_interval_seconds = 10;
|
||||
|
||||
// Determines which records are dropped first when available storage is exceeded - higher priority takes precedence
|
||||
int priority = 0;
|
||||
bool add_remote_transaction_id = false;
|
||||
bool add_message_sequence_number = false;
|
||||
bool must_flush_to_disk = false;
|
||||
CHARGELAB_JSON_INTRUSIVE(
|
||||
PendingMessagePolicy,
|
||||
message_type,
|
||||
group_id,
|
||||
message_attempts,
|
||||
retry_interval_seconds,
|
||||
priority,
|
||||
add_remote_transaction_id,
|
||||
add_message_sequence_number,
|
||||
must_flush_to_disk
|
||||
)
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct PendingMessageWrapper {
|
||||
int64_t unique_id;
|
||||
std::string payload;
|
||||
PendingMessagePolicy policy;
|
||||
std::optional<ocpp1_6::ActionId> action_id1_6;
|
||||
std::optional<ocpp2_0::ActionId> action_id2_0;
|
||||
|
||||
int attempts = 0;
|
||||
CHARGELAB_JSON_INTRUSIVE(PendingMessageWrapper, unique_id, payload, policy, action_id1_6, action_id2_0, attempts)
|
||||
};
|
||||
|
||||
class PendingMessageSerializer {
|
||||
public:
|
||||
static std::optional<PendingMessageWrapper> read(std::string_view const& text) {
|
||||
PendingMessageWrapper result;
|
||||
std::optional<int> index = 0;
|
||||
|
||||
index = readPrimitive(text, index, result.unique_id);
|
||||
index = readPrimitive(text, index, result.payload);
|
||||
index = readPrimitive(text, index, result.policy.message_type);
|
||||
index = readPrimitive(text, index, result.policy.group_id);
|
||||
index = readPrimitive(text, index, result.policy.retry_interval_seconds);
|
||||
index = readPrimitive(text, index, result.policy.message_attempts);
|
||||
index = readPrimitive(text, index, result.policy.priority);
|
||||
index = readPrimitive(text, index, result.policy.add_remote_transaction_id);
|
||||
index = readPrimitive(text, index, result.policy.add_message_sequence_number);
|
||||
index = readPrimitive(text, index, result.policy.must_flush_to_disk);
|
||||
index = readPrimitive(text, index, result.action_id1_6);
|
||||
index = readPrimitive(text, index, result.action_id2_0);
|
||||
index = readPrimitive(text, index, result.attempts);
|
||||
|
||||
if (!index.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string write(PendingMessageWrapper const& wrapper) {
|
||||
std::string result;
|
||||
|
||||
writePrimitive(result, wrapper.unique_id);
|
||||
writePrimitive(result, wrapper.payload);
|
||||
writePrimitive(result, wrapper.policy.message_type);
|
||||
writePrimitive(result, wrapper.policy.group_id);
|
||||
writePrimitive(result, wrapper.policy.retry_interval_seconds);
|
||||
writePrimitive(result, wrapper.policy.message_attempts);
|
||||
writePrimitive(result, wrapper.policy.priority);
|
||||
writePrimitive(result, wrapper.policy.add_remote_transaction_id);
|
||||
writePrimitive(result, wrapper.policy.add_message_sequence_number);
|
||||
writePrimitive(result, wrapper.policy.must_flush_to_disk);
|
||||
writePrimitive(result, wrapper.action_id1_6);
|
||||
writePrimitive(result, wrapper.action_id2_0);
|
||||
writePrimitive(result, wrapper.attempts);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class PendingMessagesModule : public ServiceStatefulGeneral {
|
||||
private:
|
||||
static constexpr int kUpdateCacheAndStatsFrequencySeconds = 15;
|
||||
static constexpr int kMaxLiveMessages = 5;
|
||||
static constexpr int kOfflineSizeLimitBytes = 10*1024;
|
||||
static constexpr int kTargetDeleteRecordCount = 10;
|
||||
static constexpr int kMaxStorageBlockSize = 50*1024;
|
||||
static constexpr int kFlushToDiskFrequencyMillis = 30*60*1000; // half hour
|
||||
|
||||
public:
|
||||
using saved_message_supplier = std::function<void(std::function<void(detail::PendingMessageWrapper const&)> const&)>;
|
||||
|
||||
public:
|
||||
PendingMessagesModule(
|
||||
std::shared_ptr<Settings> settings,
|
||||
std::shared_ptr<SystemInterface> const& system,
|
||||
std::shared_ptr<StorageInterface> storage
|
||||
)
|
||||
: settings_ {std::move(settings)},
|
||||
system_ {system},
|
||||
storage_ {std::move(storage)},
|
||||
random_engine_ {std::random_device{}()},
|
||||
// TODO: Review this; timeout will not change until reboot
|
||||
request_operation_ {system}
|
||||
{
|
||||
std::uniform_int_distribution<int64_t> distribution(0, std::numeric_limits<int64_t>::max());
|
||||
request_id_ = distribution(random_engine_);
|
||||
loadFromStorage();
|
||||
// Clear transaction_ids_ and sequence_ids_ if offline_queue_ and live_queue_ are both empty
|
||||
if (offline_queue_.empty() && live_queue_.empty()) {
|
||||
transaction_ids_.clear();
|
||||
sequence_ids_.clear();
|
||||
}
|
||||
settings_->MessageFlushCounter.setValue(pending_messages_write_count_);
|
||||
}
|
||||
|
||||
void registerOnSaveMessageSupplier(std::shared_ptr<saved_message_supplier> const& supplier) {
|
||||
for (auto const& x : saved_message_suppliers_) {
|
||||
if (x == supplier) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
saved_message_suppliers_.push_back(supplier);
|
||||
}
|
||||
|
||||
void unregisterOnSaveMessageSupplier(std::shared_ptr<saved_message_supplier> const& supplier) {
|
||||
auto initial_size = saved_message_suppliers_.size();
|
||||
saved_message_suppliers_.erase(
|
||||
std::remove_if(saved_message_suppliers_.begin(), saved_message_suppliers_.end(), [&](auto& x) {return x == supplier;}),
|
||||
saved_message_suppliers_.end()
|
||||
);
|
||||
|
||||
if (initial_size - saved_message_suppliers_.size() > 1)
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
detail::PendingMessageWrapper generateRequest1_6(T const& request, PendingMessagePolicy policy = PendingMessagePolicy{}) {
|
||||
return detail::PendingMessageWrapper {
|
||||
request_id_++,
|
||||
write_json_to_string(request),
|
||||
policy,
|
||||
T::kActionId,
|
||||
std::nullopt
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
detail::PendingMessageWrapper generateRequest2_0(T const& request, PendingMessagePolicy const& policy) {
|
||||
return detail::PendingMessageWrapper {
|
||||
request_id_++,
|
||||
write_json_to_string(request),
|
||||
policy,
|
||||
std::nullopt,
|
||||
T::kActionId
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string sendRequest1_6(T const& request, PendingMessagePolicy policy = PendingMessagePolicy{}) {
|
||||
detail::PendingMessageWrapper wrapper = generateRequest1_6(request, policy);
|
||||
if (activeGroupsContains(policy.group_id)) {
|
||||
offline_queue_.pushBack(wrapper);
|
||||
pending_messages_changed_ = true;
|
||||
} else {
|
||||
live_queue_.push_back(wrapper);
|
||||
}
|
||||
|
||||
if (policy.must_flush_to_disk)
|
||||
must_flush_to_disk_ = true;
|
||||
|
||||
return std::to_string(wrapper.unique_id);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::string sendRequest2_0(T const& request, PendingMessagePolicy const& policy) {
|
||||
detail::PendingMessageWrapper wrapper = generateRequest2_0(request, policy);
|
||||
|
||||
if (activeGroupsContains(policy.group_id)) {
|
||||
offline_queue_.pushBack(wrapper);
|
||||
pending_messages_changed_ = true;
|
||||
} else {
|
||||
live_queue_.push_back(wrapper);
|
||||
}
|
||||
|
||||
if (policy.must_flush_to_disk)
|
||||
must_flush_to_disk_ = true;
|
||||
|
||||
return std::to_string(wrapper.unique_id);
|
||||
}
|
||||
|
||||
template <typename Visitor>
|
||||
void visitPending(Visitor&& visitor) {
|
||||
for (auto const& x : live_queue_)
|
||||
visitor(x.policy, x.payload);
|
||||
|
||||
offline_queue_.visit([&] (std::string_view const&, detail::PendingMessageWrapper const& wrapper) {
|
||||
visitor(wrapper.policy, wrapper.payload);
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
void runUnconditionally() override {
|
||||
updateCacheAndStats();
|
||||
limitOfflineQueueSize();
|
||||
flushToDisk();
|
||||
|
||||
// If an operation is not in progress, flush messages from the live queue to the offline queue as necessary
|
||||
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
|
||||
while (live_queue_.size() > kMaxLiveMessages) {
|
||||
auto it = live_queue_.begin();
|
||||
if (it->policy.group_id.has_value())
|
||||
active_group_ids_.insert(it->policy.group_id.value());
|
||||
|
||||
offline_queue_.pushBack(live_queue_.front());
|
||||
live_queue_.erase(live_queue_.begin());
|
||||
pending_messages_changed_ = true;
|
||||
}
|
||||
|
||||
// Make sure live messages with the same group IDs also get moved to offline messages so that they're
|
||||
// not sent out of order.
|
||||
live_queue_.erase(
|
||||
std::remove_if(
|
||||
live_queue_.begin(),
|
||||
live_queue_.end(),
|
||||
[&](detail::PendingMessageWrapper const& wrapper) {
|
||||
if (activeGroupsContains(wrapper.policy.group_id)) {
|
||||
offline_queue_.pushBack(wrapper);
|
||||
pending_messages_changed_ = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
),
|
||||
live_queue_.end()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void runStep(ocpp1_6::OcppRemote& remote) override {
|
||||
// Send out a live message, if one is available
|
||||
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
|
||||
auto it = live_queue_.begin();
|
||||
bool delete_message = false;
|
||||
bool send_message = true;
|
||||
|
||||
if (blacklistContains(it->policy.group_id))
|
||||
delete_message = true;
|
||||
if (!it->action_id1_6.has_value())
|
||||
delete_message = true;
|
||||
if (it->attempts > 0 && it->attempts >= it->policy.message_attempts)
|
||||
delete_message = true;
|
||||
|
||||
if (it->attempts > 0) {
|
||||
auto const threshold = std::max(it->attempts, 1) * it->policy.retry_interval_seconds;
|
||||
if (request_operation_.getIdleDurationSeconds() < threshold)
|
||||
send_message = false;
|
||||
}
|
||||
|
||||
if (delete_message) {
|
||||
live_queue_.erase(it);
|
||||
} else if (send_message) {
|
||||
if (sendWithTransactionId(remote, *it)) {
|
||||
it->attempts++;
|
||||
request_operation_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
std::to_string(it->unique_id)
|
||||
);
|
||||
request_object_ = *it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next send out an offline message, if one is available
|
||||
if (!request_operation_.operationInProgress() && !offline_queue_.empty()) {
|
||||
auto record = offline_queue_.pollFront();
|
||||
if (record.has_value()) {
|
||||
bool delete_message = false;
|
||||
bool send_message = true;
|
||||
|
||||
if (blacklistContains(record->policy.group_id))
|
||||
delete_message = true;
|
||||
if (!record->action_id1_6.has_value())
|
||||
delete_message = true;
|
||||
if (record->attempts > 0 && record->attempts >= record->policy.message_attempts)
|
||||
delete_message = true;
|
||||
|
||||
if (record->attempts > 0) {
|
||||
auto const threshold = std::max(record->attempts, 1) * record->policy.retry_interval_seconds;
|
||||
if (request_operation_.getIdleDurationSeconds() < threshold)
|
||||
send_message = false;
|
||||
}
|
||||
|
||||
if (delete_message) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "popFront: delete_message";
|
||||
offline_queue_.popFront();
|
||||
pending_messages_changed_ = true;
|
||||
} else if (send_message) {
|
||||
if (sendWithTransactionId(remote, record.value())) {
|
||||
record->attempts++;
|
||||
offline_queue_.updateFront(record.value());
|
||||
pending_messages_changed_ = true;
|
||||
request_operation_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
std::to_string(record->unique_id)
|
||||
);
|
||||
request_object_ = record.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void runStep(ocpp2_0::OcppRemote &remote) override {
|
||||
updateCacheAndStats();
|
||||
limitOfflineQueueSize();
|
||||
|
||||
// If an operation is not in progress, flush messages from the live queue to the offline queue as necessary
|
||||
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
|
||||
while (live_queue_.size() > kMaxLiveMessages) {
|
||||
auto it = live_queue_.begin();
|
||||
if (it->policy.group_id.has_value())
|
||||
active_group_ids_.insert(it->policy.group_id.value());
|
||||
|
||||
offline_queue_.pushBack(live_queue_.front());
|
||||
live_queue_.erase(live_queue_.begin());
|
||||
pending_messages_changed_ = true;
|
||||
}
|
||||
|
||||
// Make sure live messages with the same group IDs also get moved to offline messages so that they're
|
||||
// not sent out of order.
|
||||
live_queue_.erase(
|
||||
std::remove_if(
|
||||
live_queue_.begin(),
|
||||
live_queue_.end(),
|
||||
[&](detail::PendingMessageWrapper const& wrapper) {
|
||||
if (activeGroupsContains(wrapper.policy.group_id)) {
|
||||
offline_queue_.pushBack(wrapper);
|
||||
pending_messages_changed_ = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
),
|
||||
live_queue_.end()
|
||||
);
|
||||
}
|
||||
|
||||
// First send out a live message, if one is available
|
||||
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
|
||||
auto it = live_queue_.begin();
|
||||
bool delete_message = false;
|
||||
bool send_message = true;
|
||||
|
||||
if (blacklistContains(it->policy.group_id))
|
||||
delete_message = true;
|
||||
if (!it->action_id2_0.has_value())
|
||||
delete_message = true;
|
||||
if (it->attempts > 0 && it->attempts >= it->policy.message_attempts)
|
||||
delete_message = true;
|
||||
|
||||
if (it->attempts > 0) {
|
||||
auto const threshold = std::max(it->attempts, 1) * it->policy.retry_interval_seconds;
|
||||
if (request_operation_.getIdleDurationSeconds() < threshold)
|
||||
send_message = false;
|
||||
}
|
||||
|
||||
if (delete_message) {
|
||||
if (it->policy.group_id.has_value()) {
|
||||
auto sequence = sequence_ids_.find(it->policy.group_id.value());
|
||||
if (sequence != sequence_ids_.end()) {
|
||||
if (shouldRemoveSequenceId(*it)) {
|
||||
sequence_ids_.erase(sequence);
|
||||
pending_messages_changed_ = true;
|
||||
} else {
|
||||
sequence->second++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
live_queue_.erase(it);
|
||||
} else if (send_message) {
|
||||
if (sendWithSequenceNumber(remote, *it)) {
|
||||
it->attempts++;
|
||||
request_operation_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
std::to_string(it->unique_id)
|
||||
);
|
||||
request_object_ = *it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next send out an offline message, if one is available
|
||||
if (!request_operation_.operationInProgress() && !offline_queue_.empty()) {
|
||||
auto record = offline_queue_.pollFront();
|
||||
if (record.has_value()) {
|
||||
bool delete_message = false;
|
||||
bool send_message = true;
|
||||
|
||||
if (blacklistContains(record->policy.group_id))
|
||||
delete_message = true;
|
||||
if (!record->action_id2_0.has_value())
|
||||
delete_message = true;
|
||||
if (record->attempts > 0 && record->attempts >= record->policy.message_attempts)
|
||||
delete_message = true;
|
||||
|
||||
if (record->attempts > 0) {
|
||||
auto const threshold = std::max(record->attempts, 1) * record->policy.retry_interval_seconds;
|
||||
if (request_operation_.getIdleDurationSeconds() < threshold)
|
||||
send_message = false;
|
||||
}
|
||||
|
||||
if (delete_message) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "popFront: delete_message";
|
||||
if (record->policy.group_id.has_value()) {
|
||||
auto sequence = sequence_ids_.find(record->policy.group_id.value());
|
||||
if (sequence != sequence_ids_.end()) {
|
||||
if (shouldRemoveSequenceId(record.value())) {
|
||||
sequence_ids_.erase(sequence);
|
||||
} else {
|
||||
sequence->second++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offline_queue_.popFront();
|
||||
pending_messages_changed_ = true;
|
||||
} else if (send_message) {
|
||||
if (sendWithSequenceNumber(remote, record.value())) {
|
||||
record->attempts++;
|
||||
offline_queue_.updateFront(record.value());
|
||||
pending_messages_changed_ = true;
|
||||
request_operation_.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
std::to_string(record->unique_id)
|
||||
);
|
||||
request_object_ = record.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onStartTransactionRsp(
|
||||
const std::string &unique_id,
|
||||
const ocpp1_6::ResponseMessage<ocpp1_6::StartTransactionRsp> &rsp
|
||||
) override {
|
||||
if (request_operation_ == unique_id && std::holds_alternative<ocpp1_6::StartTransactionRsp>(rsp)) {
|
||||
auto const& message = std::get<ocpp1_6::StartTransactionRsp>(rsp);
|
||||
if (!request_object_.has_value())
|
||||
return;
|
||||
if (!request_object_->policy.group_id.has_value())
|
||||
return;
|
||||
|
||||
transaction_ids_.insert(std::make_pair(request_object_->policy.group_id.value(), message.transactionId));
|
||||
|
||||
// Flush the pending message to remove StartTransaction from the live_queue_ and
|
||||
// update the StopTransaction with transaction Id is the StartTransaction is accepted
|
||||
must_flush_to_disk_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool activeGroupsContains(std::optional<int64_t> const& group_id) const {
|
||||
if (!group_id.has_value())
|
||||
return false;
|
||||
|
||||
return active_group_ids_.find(group_id.value()) != active_group_ids_.end();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool blacklistContains(std::optional<int64_t> const& group_id) const {
|
||||
if (!group_id.has_value())
|
||||
return false;
|
||||
|
||||
return group_blacklist_.find(group_id.value()) != group_blacklist_.end();
|
||||
}
|
||||
|
||||
bool sendWithTransactionId(ocpp1_6::OcppRemote& remote, detail::PendingMessageWrapper const& wrapper) {
|
||||
if (!wrapper.action_id1_6.has_value())
|
||||
return false;
|
||||
|
||||
std::string payload = wrapper.payload;
|
||||
if (wrapper.policy.group_id.has_value()) {
|
||||
auto it = transaction_ids_.find(wrapper.policy.group_id.value());
|
||||
if (it != transaction_ids_.end())
|
||||
payload = insert_into_object(payload, "transactionId", it->second);
|
||||
}
|
||||
|
||||
return remote.sendCall(
|
||||
std::to_string(wrapper.unique_id),
|
||||
wrapper.action_id1_6.value(),
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
bool sendWithSequenceNumber(ocpp2_0::OcppRemote& remote, detail::PendingMessageWrapper const& wrapper) {
|
||||
if (!wrapper.action_id2_0.has_value())
|
||||
return false;
|
||||
|
||||
std::string payload = wrapper.payload;
|
||||
if (wrapper.policy.add_message_sequence_number && wrapper.policy.group_id.has_value()) {
|
||||
auto it = sequence_ids_.find(wrapper.policy.group_id.value());
|
||||
if (it == sequence_ids_.end()) {
|
||||
auto first = read_field_from_object<int>(payload, "seqNo");
|
||||
sequence_ids_[wrapper.policy.group_id.value()] = first.value_or(0);
|
||||
pending_messages_changed_ = true;
|
||||
}
|
||||
|
||||
payload = insert_into_object(payload, "seqNo", sequence_ids_[wrapper.policy.group_id.value()]);
|
||||
}
|
||||
|
||||
return remote.sendCall(
|
||||
std::to_string(wrapper.unique_id),
|
||||
wrapper.action_id2_0.value(),
|
||||
payload
|
||||
);
|
||||
}
|
||||
|
||||
void limitOfflineQueueSize() {
|
||||
auto const initial_total_bytes = offline_queue_.totalBytes();
|
||||
if (initial_total_bytes < kOfflineSizeLimitBytes)
|
||||
return;
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Dropping messages to limit offline queue size: "
|
||||
<< initial_total_bytes << " > " << kOfflineSizeLimitBytes;
|
||||
|
||||
auto const start_ts = system_->steadyClockNow();
|
||||
std::map<std::pair<int, std::optional<int>>, std::pair<int, int>> stats;
|
||||
offline_queue_.visit([&] (std::string_view const& text, detail::PendingMessageWrapper const& wrapper) {
|
||||
auto& value = stats[std::make_pair(wrapper.policy.priority, wrapper.policy.group_id)];
|
||||
value.first += 1;
|
||||
value.second += (int)text.size();
|
||||
});
|
||||
auto const middle_ts1 = system_->steadyClockNow();
|
||||
|
||||
int max_priority = std::numeric_limits<int>::min();
|
||||
for (auto const& x : stats)
|
||||
max_priority = std::max(x.first.first, max_priority);
|
||||
|
||||
bool delete_safely = false;
|
||||
for (auto const& x : stats) {
|
||||
if (!x.first.second.has_value()) {
|
||||
delete_safely = true;
|
||||
break;
|
||||
}
|
||||
if (x.first.first == max_priority && x.second.first > 2) {
|
||||
delete_safely = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int delete_count = 0;
|
||||
for (auto const& x : stats) {
|
||||
if (!x.first.second.has_value()) {
|
||||
delete_count++;
|
||||
} else if (delete_safely) {
|
||||
delete_count += std::max(x.second.first - 2, 0);
|
||||
} else {
|
||||
delete_count += x.second.first;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t deleted_records = 0;
|
||||
std::size_t deleted_decompressed_bytes = 0;
|
||||
|
||||
std::uniform_int_distribution<int64_t> distribution(0, delete_count-1);
|
||||
std::map<int64_t, int> counter;
|
||||
auto const middle_ts2 = system_->steadyClockNow();
|
||||
offline_queue_.removeIf([&] (std::string_view const& text, detail::PendingMessageWrapper const& wrapper) {
|
||||
if (wrapper.policy.priority != max_priority)
|
||||
return false;
|
||||
|
||||
if (wrapper.policy.group_id.has_value() && delete_safely) {
|
||||
auto index = counter[wrapper.policy.group_id.value()]++;
|
||||
auto const& stat = stats[std::make_pair(wrapper.policy.priority, wrapper.policy.group_id)];
|
||||
if (index == 0 || index == stat.first-1)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (distribution(random_engine_) < kTargetDeleteRecordCount) {
|
||||
deleted_records++;
|
||||
deleted_decompressed_bytes += text.size();
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Dropping offline message: " << wrapper.payload;
|
||||
pending_messages_changed_ = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
auto const end_ts = system_->steadyClockNow();
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Delete stats:"
|
||||
<< " processing_ms=" << (end_ts - start_ts)
|
||||
<< " deleted_records=" << deleted_records
|
||||
<< " deleted_decompressed_bytes=" << deleted_decompressed_bytes
|
||||
<< " new_compressed_size=" << offline_queue_.totalBytes();
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Times: "
|
||||
<< " block1=" << (middle_ts1 - start_ts)
|
||||
<< " block2=" << (middle_ts2 - middle_ts1)
|
||||
<< " block3=" << (end_ts - middle_ts2);
|
||||
}
|
||||
|
||||
void onCallRsp(const std::string &unique_id, const ocpp1_6::ResponseMessage<common::RawJson>& payload) override {
|
||||
if (request_operation_ == unique_id) {
|
||||
request_operation_ = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<common::RawJson>(payload)) {
|
||||
if (!request_object_.has_value())
|
||||
return;
|
||||
|
||||
auto onCallRsp = [this](detail::PendingMessageWrapper const &wrapper) {
|
||||
if (wrapper.action_id1_6 && wrapper.action_id1_6.value() == ocpp1_6::ActionId::kStopTransaction) {
|
||||
auto const request = read_json_from_string<ocpp1_6::StopTransactionReq>(wrapper.payload);
|
||||
if (!request.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed read StopTransaction request from: " << wrapper.payload;
|
||||
return;
|
||||
}
|
||||
|
||||
// remove transaction id from transaction_ids_ if there is one
|
||||
for (auto it = transaction_ids_.begin(); it != transaction_ids_.end(); ) {
|
||||
if (it->second == request->transactionId) {
|
||||
it = transaction_ids_.erase(it);
|
||||
pending_messages_changed_ = true;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (request->reason == ocpp1_6::Reason::kHardReset || request->reason == ocpp1_6::Reason::kSoftReset) {
|
||||
must_flush_to_disk_ = true;
|
||||
flushToDisk();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!live_queue_.empty() && live_queue_.front().unique_id == request_object_->unique_id) {
|
||||
auto wrapper = live_queue_.front();
|
||||
live_queue_.erase(live_queue_.begin());
|
||||
onCallRsp(wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offline_queue_.pollFront().has_value() && offline_queue_.pollFront()->unique_id == request_object_->unique_id) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "popFront: onCallRsp";
|
||||
auto record = offline_queue_.pollFront();
|
||||
offline_queue_.popFront();
|
||||
onCallRsp(record.value());
|
||||
pending_messages_changed_ = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onCallRsp(const std::string &unique_id, const ocpp2_0::ResponseMessage<common::RawJson>& payload) override {
|
||||
if (request_operation_ == unique_id) {
|
||||
request_operation_ = kNoOperation;
|
||||
|
||||
if (std::holds_alternative<common::RawJson>(payload)) {
|
||||
if (!request_object_.has_value())
|
||||
return;
|
||||
|
||||
if (!live_queue_.empty() && live_queue_.front().unique_id == request_object_->unique_id) {
|
||||
auto it = sequence_ids_.find(live_queue_.front().policy.group_id.value());
|
||||
if (it != sequence_ids_.end()) {
|
||||
if (shouldRemoveSequenceId(live_queue_.front())) {
|
||||
sequence_ids_.erase(it);
|
||||
pending_messages_changed_ = true;
|
||||
} else {
|
||||
it->second++;
|
||||
}
|
||||
}
|
||||
|
||||
live_queue_.erase(live_queue_.begin());
|
||||
return;
|
||||
}
|
||||
|
||||
auto const& record = offline_queue_.pollFront();
|
||||
if (record.has_value() && record->unique_id == request_object_->unique_id) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "popFront: onCallRsp";
|
||||
auto it = sequence_ids_.find(record->policy.group_id.value());
|
||||
if (it != sequence_ids_.end()) {
|
||||
if (shouldRemoveSequenceId(record.value())) {
|
||||
sequence_ids_.erase(it);
|
||||
} else {
|
||||
it->second++;
|
||||
}
|
||||
}
|
||||
|
||||
offline_queue_.popFront();
|
||||
pending_messages_changed_ = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void updateCacheAndStats() {
|
||||
auto const now = system_->steadyClockNow();
|
||||
if (last_cache_update_.has_value()) {
|
||||
auto const delta = now - last_cache_update_.value();
|
||||
if (delta/1000 < kUpdateCacheAndStatsFrequencySeconds)
|
||||
return;
|
||||
}
|
||||
|
||||
last_cache_update_ = now;
|
||||
|
||||
std::size_t total_records = 0;
|
||||
std::size_t total_decompressed_size = 0;
|
||||
|
||||
active_group_ids_.clear();
|
||||
offline_queue_.visit([&](std::string_view const& text, detail::PendingMessageWrapper const& wrapper) {
|
||||
if (wrapper.policy.group_id.has_value())
|
||||
active_group_ids_.insert(wrapper.policy.group_id.value());
|
||||
|
||||
total_records++;
|
||||
total_decompressed_size += text.size();
|
||||
});
|
||||
|
||||
auto const total_compressed_size = offline_queue_.totalBytes();
|
||||
double compression_ratio = 0;
|
||||
if (total_decompressed_size > 0)
|
||||
compression_ratio = (double)total_compressed_size/(double)total_decompressed_size;
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Offline message stats:"
|
||||
<< " records=" << total_records
|
||||
<< " compressed_bytes=" << total_compressed_size
|
||||
<< " decompressed_bytes=" << total_decompressed_size
|
||||
<< " compression_ratio=" << compression_ratio;
|
||||
}
|
||||
|
||||
void flushToDisk() {
|
||||
if (!must_flush_to_disk_ && !pending_messages_changed_) {
|
||||
return; // No need to flush anything
|
||||
}
|
||||
|
||||
auto const now = system_->steadyClockNow();
|
||||
|
||||
auto total_writing_bytes = calculateAllPendingSavedInfoSize();
|
||||
|
||||
total_writing_bytes = ((total_writing_bytes + 255)/256)*256;
|
||||
int flush_interval = ((double)total_writing_bytes / kMaxStorageBlockSize) * kFlushToDiskFrequencyMillis;
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(trace) << "flush interval: " << flush_interval << ", offline_queue_ bytes: " << offline_queue_.totalBytes()
|
||||
<< ", must_flush_to_disk_: " << must_flush_to_disk_ << ", pending_messages_changed_: " << pending_messages_changed_;
|
||||
|
||||
if (!must_flush_to_disk_ && last_flush_to_disk_.has_value()) {
|
||||
// Note: flushing to disk explicitly after the historic backlog has cleared after an offline period or
|
||||
// after restarting.
|
||||
auto const delta = now - last_flush_to_disk_.value();
|
||||
if (delta < flush_interval)
|
||||
return;
|
||||
}
|
||||
last_flush_to_disk_ = now;
|
||||
must_flush_to_disk_ = false;
|
||||
|
||||
// Note: must flush to disk here even if online, otherwise the Ended messages aren't added to the stream and
|
||||
// a dangling transaction is created if the station looses power. On the other hand, at one extra
|
||||
// write per hour here we'll end up exhausting one block of flash memory on an ESP32 every ~11 years,
|
||||
// which may not be worth optimizing out (particularly with a wear-leveling filesystem).
|
||||
pending_messages_write_count_ ++;
|
||||
saveToStorage();
|
||||
settings_->MessageFlushCounter.setValue(pending_messages_write_count_);
|
||||
|
||||
pending_messages_changed_ = false;
|
||||
}
|
||||
|
||||
void saveToStorage() {
|
||||
// TODO: This could likely be optimized to avoid writing an empty file repeatedly while there's nothing in
|
||||
// the buffer (and nothing added via the registered suppliers).
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Saving pending messages to storage, write count: " << pending_messages_write_count_;
|
||||
storage_->write([&](auto file) {
|
||||
offline_queue_.write([&](void* data, std::size_t size) {
|
||||
auto length = (int)size;
|
||||
std::fwrite(&length, sizeof(length), 1, file);
|
||||
std::fwrite(data, size, 1, file);
|
||||
});
|
||||
|
||||
int terminator = -1;
|
||||
std::fwrite(&terminator, sizeof(terminator), 1, file);
|
||||
|
||||
file::json_write_object_to_file(file, active_group_ids_);
|
||||
file::json_write_object_to_file(file, group_blacklist_);
|
||||
file::json_write_object_to_file(file, transaction_ids_);
|
||||
file::json_write_object_to_file(file, sequence_ids_);
|
||||
file::json_write_object_to_file(file, pending_messages_write_count_);
|
||||
|
||||
for (auto const& x : live_queue_) {
|
||||
file::json_write_object_to_file(file, x);
|
||||
}
|
||||
|
||||
for (auto const& supplier : saved_message_suppliers_) {
|
||||
if (supplier == nullptr)
|
||||
continue;
|
||||
|
||||
(*supplier)([&](auto const& record) {
|
||||
file::json_write_object_to_file(file, record);
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void readFromFile(FILE* file, T& value) {
|
||||
auto parsed = file::json_read_object_from_file<T>(file);
|
||||
if (!parsed.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading value from file";
|
||||
return;
|
||||
}
|
||||
|
||||
value = parsed.value();
|
||||
}
|
||||
|
||||
void loadFromStorage() {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Reading pending messages from storage";
|
||||
storage_->read([&](auto file) {
|
||||
offline_queue_.read([&]() {
|
||||
std::optional<std::vector<uint8_t>> result = std::nullopt;
|
||||
|
||||
int length;
|
||||
if (std::fread(&length, sizeof(length), 1, file) != 1)
|
||||
return result;
|
||||
if (length == -1)
|
||||
return result;
|
||||
|
||||
if (length < 0 || length > kMaxStorageBlockSize) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Bad block length encountered reading historic data: " << length;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(length);
|
||||
if (std::fread(buffer.data(), sizeof(uint8_t), buffer.size(), file) != buffer.size()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed reading block data";
|
||||
return result;
|
||||
}
|
||||
|
||||
result = std::move(buffer);
|
||||
return result;
|
||||
});
|
||||
|
||||
{
|
||||
readFromFile(file, active_group_ids_);
|
||||
readFromFile(file, group_blacklist_);
|
||||
readFromFile(file, transaction_ids_);
|
||||
readFromFile(file, sequence_ids_);
|
||||
readFromFile(file, pending_messages_write_count_);
|
||||
}
|
||||
|
||||
// Loading previous live queue into offline queue
|
||||
while (true) {
|
||||
auto record = file::json_read_object_from_file<detail::PendingMessageWrapper>(file);
|
||||
if (!record.has_value())
|
||||
break;
|
||||
|
||||
offline_queue_.pushBack(record.value());
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
int calculateAllPendingSavedInfoSize() {
|
||||
int total_bytes = 0;
|
||||
total_bytes += offline_queue_.totalBytes();
|
||||
total_bytes += sizeof(int); // integer terminator size
|
||||
|
||||
total_bytes += calculate_size(active_group_ids_) + 1; // Count the newline '\n' character
|
||||
total_bytes += calculate_size(group_blacklist_) + 1; // Count the newline '\n' character
|
||||
total_bytes += calculate_size(transaction_ids_) + 1; // Count the newline '\n' character
|
||||
total_bytes += calculate_size(sequence_ids_) + 1; // Count the newline '\n' character
|
||||
total_bytes += calculate_size(pending_messages_write_count_) + 1; // CCount the newline '\n' character
|
||||
|
||||
for (auto const& x : live_queue_) {
|
||||
if (x.action_id1_6.has_value() && x.action_id1_6.value() == ocpp1_6::ActionId::kStartTransaction)
|
||||
continue;
|
||||
total_bytes += calculate_size(x) + 1; // Count the newline '\n' character
|
||||
}
|
||||
|
||||
for (auto const& supplier : saved_message_suppliers_) {
|
||||
if (supplier == nullptr)
|
||||
continue;
|
||||
|
||||
(*supplier)([&](auto const& record) {
|
||||
total_bytes += calculate_size(record) + 1; // Count the newline '\n' character
|
||||
});
|
||||
}
|
||||
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
bool shouldRemoveSequenceId(detail::PendingMessageWrapper const& wrapper) {
|
||||
if (!wrapper.action_id2_0 || wrapper.action_id2_0.value() != chargelab::ocpp2_0::ActionId::kTransactionEvent)
|
||||
return false;
|
||||
|
||||
auto const request = read_json_from_string<ocpp2_0::TransactionEventRequest>(wrapper.payload);
|
||||
if (!request.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Failed read TransactionEvent request from: " << wrapper.payload;
|
||||
return false;
|
||||
}
|
||||
|
||||
return request->eventType == chargelab::ocpp2_0::TransactionEventEnumType::kEnded;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<SystemInterface> system_;
|
||||
std::shared_ptr<StorageInterface> storage_;
|
||||
|
||||
std::default_random_engine random_engine_;
|
||||
std::vector<detail::PendingMessageWrapper> live_queue_;
|
||||
CompressedQueueCustom<detail::PendingMessageWrapper, detail::PendingMessageSerializer> offline_queue_;
|
||||
OperationHolder<std::string> request_operation_;
|
||||
std::optional<detail::PendingMessageWrapper> request_object_;
|
||||
std::vector<std::shared_ptr<saved_message_supplier>> saved_message_suppliers_ {};
|
||||
|
||||
bool must_flush_to_disk_ = false;
|
||||
int64_t request_id_;
|
||||
std::unordered_set<int64_t> active_group_ids_;
|
||||
std::unordered_set<int64_t> group_blacklist_;
|
||||
std::unordered_map<int64_t, int> transaction_ids_;
|
||||
std::unordered_map<int64_t, int> sequence_ids_;
|
||||
std::optional<SteadyPointMillis> last_cache_update_ = std::nullopt;
|
||||
std::optional<SteadyPointMillis> last_flush_to_disk_ = std::nullopt;
|
||||
bool pending_messages_changed_ = false;
|
||||
int32_t pending_messages_write_count_ = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_PENDING_MESSAGES_MODULE_H
|
||||
1141
tools/openocpp/include/openocpp/module/power_management_module1_6.h
Normal file
1141
tools/openocpp/include/openocpp/module/power_management_module1_6.h
Normal file
File diff suppressed because it is too large
Load Diff
1153
tools/openocpp/include/openocpp/module/power_management_module2_0.h
Normal file
1153
tools/openocpp/include/openocpp/module/power_management_module2_0.h
Normal file
File diff suppressed because it is too large
Load Diff
207
tools/openocpp/include/openocpp/module/reset_module.h
Normal file
207
tools/openocpp/include/openocpp/module/reset_module.h
Normal file
@@ -0,0 +1,207 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_RESET_MODULE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_RESET_MODULE_H
|
||||
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/module/connector_status_module.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
|
||||
namespace chargelab {
|
||||
class ResetModule : public ServiceStatefulGeneral {
|
||||
private:
|
||||
static constexpr int kMinimumResetDelayMillis = 1*1000; // 3 seconds
|
||||
|
||||
public:
|
||||
ResetModule(
|
||||
std::shared_ptr<Settings> settings,
|
||||
std::shared_ptr<SystemInterface> system,
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_module
|
||||
)
|
||||
: settings_(std::move(settings)),
|
||||
system_(std::move(system)),
|
||||
connector_status_module_(std::move(connector_status_module))
|
||||
{
|
||||
}
|
||||
|
||||
~ResetModule() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ResetModule";
|
||||
}
|
||||
|
||||
public:
|
||||
[[nodiscard]] bool pendingReset() const {
|
||||
return hard_reset_threshold_.has_value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the charger when it's no longer in use with the provided reason code. This method is thread safe.
|
||||
*
|
||||
* @param reason
|
||||
*/
|
||||
void resetOnIdle(ocpp2_0::BootReasonEnumType reason) {
|
||||
if (hard_reset_requested_) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Ignoring soft reset request - hard reset already scheduled";
|
||||
return;
|
||||
}
|
||||
|
||||
reset_reason_ = reason;
|
||||
soft_reset_requested_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the charger immediately with the provided reason code. This method is thread safe.
|
||||
*
|
||||
* @param reason
|
||||
*/
|
||||
void resetImmediately(ocpp2_0::BootReasonEnumType reason) {
|
||||
reset_reason_ = reason;
|
||||
hard_reset_requested_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
void runStep(ocpp1_6::OcppRemote&) override {
|
||||
runStepCommon();
|
||||
}
|
||||
|
||||
void runStep(ocpp2_0::OcppRemote&) override {
|
||||
runStepCommon();
|
||||
}
|
||||
|
||||
std::optional<ocpp2_0::ResponseToRequest <ocpp2_0::ResetResponse>>
|
||||
onResetReq(const ocpp2_0::ResetRequest &req) override {
|
||||
// B11.FR.09
|
||||
if (req.evseId.has_value()) {
|
||||
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
switch (req.type) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Invalid reset request type - treating as OnIdle";
|
||||
[[fallthrough]];
|
||||
|
||||
case ocpp2_0::ResetEnumType::kOnIdle:
|
||||
if (hard_reset_requested_) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
|
||||
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
|
||||
}
|
||||
if (soft_reset_requested_) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - soft reset already scheduled";
|
||||
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
soft_reset_requested_ = true;
|
||||
|
||||
// B12.FR.01
|
||||
if (connector_status_module_->isChargingEnabled()) {
|
||||
reset_reason_ = ocpp2_0::BootReasonEnumType::kScheduledReset;
|
||||
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kScheduled};
|
||||
}
|
||||
break;
|
||||
|
||||
case ocpp2_0::ResetEnumType::kImmediate:
|
||||
if (hard_reset_requested_) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
|
||||
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
|
||||
}
|
||||
|
||||
hard_reset_requested_ = true;
|
||||
break;
|
||||
}
|
||||
|
||||
reset_reason_ = ocpp2_0::BootReasonEnumType::kRemoteReset;
|
||||
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kAccepted};
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest <ocpp1_6::ResetRsp>>
|
||||
onResetReq(const ocpp1_6::ResetReq &req) override {
|
||||
switch (req.type) {
|
||||
case ocpp1_6::ResetType::kValueNotFoundInEnum:
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Invalid reset request type - treating as Soft";
|
||||
[[fallthrough]];
|
||||
|
||||
case ocpp1_6::ResetType::kSoft:
|
||||
if (hard_reset_requested_) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
|
||||
return ocpp1_6::ResetRsp{ocpp1_6::ResetStatus::kRejected};
|
||||
}
|
||||
if (soft_reset_requested_) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - soft reset already scheduled";
|
||||
return ocpp1_6::ResetRsp{ocpp1_6::ResetStatus::kRejected};
|
||||
}
|
||||
|
||||
soft_reset_requested_ = true;
|
||||
break;
|
||||
|
||||
case ocpp1_6::ResetType::kHard:
|
||||
if (hard_reset_requested_) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
|
||||
return ocpp1_6::ResetRsp{ocpp1_6::ResetStatus::kRejected};
|
||||
}
|
||||
|
||||
hard_reset_requested_ = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return ocpp1_6::ResetRsp {ocpp1_6::ResetStatus::kAccepted};
|
||||
}
|
||||
|
||||
private:
|
||||
void runStepCommon() {
|
||||
ocpp2_0::BootReasonEnumType const reason = reset_reason_;
|
||||
|
||||
auto const now = system_->steadyClockNow();
|
||||
bool running_transactions = connector_status_module_->isChargingEnabled();
|
||||
if (hard_reset_requested_) {
|
||||
if (!hard_reset_threshold_.has_value()) {
|
||||
hard_reset_threshold_ = static_cast<SteadyPointMillis> (now + kMinimumResetDelayMillis);
|
||||
connector_status_module_->setPendingReset(true, true);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Hard reset requested - restarting in: " << kMinimumResetDelayMillis << " millis";
|
||||
}
|
||||
|
||||
auto const delta = now - hard_reset_threshold_.value();
|
||||
if (delta >= 0) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Resetting system";
|
||||
hard_reset_threshold_ = std::nullopt;
|
||||
settings_->CustomBootReason.setValue(reason.to_string());
|
||||
settings_->saveIfModified();
|
||||
system_->resetHard();
|
||||
}
|
||||
} else if (soft_reset_requested_) {
|
||||
if (!running_transactions) {
|
||||
if (!soft_reset_threshold_.has_value()) {
|
||||
soft_reset_threshold_ = static_cast<SteadyPointMillis> (now + kMinimumResetDelayMillis);
|
||||
connector_status_module_->setPendingReset(true, false);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Soft reset requested - restarting in: " << kMinimumResetDelayMillis << " millis";
|
||||
}
|
||||
|
||||
auto const delta = now - soft_reset_threshold_.value();
|
||||
if (delta >= 0) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Resetting system";
|
||||
soft_reset_threshold_ = std::nullopt;
|
||||
settings_->CustomBootReason.setValue(reason.to_string());
|
||||
settings_->saveIfModified();
|
||||
system_->resetSoft();
|
||||
}
|
||||
} else {
|
||||
soft_reset_threshold_ = std::nullopt;
|
||||
// Tell the transaction module to stop transactions indirectly
|
||||
connector_status_module_->setPendingReset(true, false);
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(trace) << "Reset pending - waiting for running transactions to complete";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<SystemInterface> system_;
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_module_;
|
||||
|
||||
std::optional<SteadyPointMillis> hard_reset_threshold_ = std::nullopt;
|
||||
std::optional<SteadyPointMillis> soft_reset_threshold_ = std::nullopt;
|
||||
std::atomic<ocpp2_0::BootReasonEnumType> reset_reason_ = {ocpp2_0::BootReasonEnumType::kUnknown};
|
||||
std::atomic<bool> soft_reset_requested_ = false;
|
||||
std::atomic<bool> hard_reset_requested_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_RESET_MODULE_H
|
||||
766
tools/openocpp/include/openocpp/module/transaction_module1_6.h
Normal file
766
tools/openocpp/include/openocpp/module/transaction_module1_6.h
Normal file
@@ -0,0 +1,766 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_TRANSACTION_MODULE1_6_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_TRANSACTION_MODULE1_6_H
|
||||
|
||||
#include "openocpp/interface/platform_interface.h"
|
||||
#include "openocpp/interface/station_interface.h"
|
||||
#include "openocpp/module/common_templates.h"
|
||||
#include "openocpp/module/pending_messages_module.h"
|
||||
#include "openocpp/module/boot_notification_module.h"
|
||||
#include "openocpp/module/power_management_module1_6.h"
|
||||
#include "openocpp/module/connector_status_module.h"
|
||||
#include "openocpp/common/operation_holder.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <utility>
|
||||
#include <random>
|
||||
#include <unordered_set>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace chargelab {
|
||||
namespace transaction_module1_6 {
|
||||
struct TransactionContainer {
|
||||
int connector_id;
|
||||
uint64_t group_id;
|
||||
std::string id_tag;
|
||||
std::string start_transaction_request_id;
|
||||
|
||||
std::optional<int> transaction_id = std::nullopt;
|
||||
std::optional<SteadyPointMillis> last_reading_timestamp = std::nullopt;
|
||||
std::optional<SteadyPointMillis> last_clock_aligned_reading_timestamp = std::nullopt;
|
||||
bool connector_unplugged_after_transaction_ended = false;
|
||||
};
|
||||
|
||||
struct PendingStartRequest {
|
||||
PendingStartRequest(std::shared_ptr<PlatformInterface> const& platform, int connector_id, std::string tag_id)
|
||||
: created_timestamp {platform->steadyClockNow()},
|
||||
pending_authorize_req {platform},
|
||||
connector_id {connector_id},
|
||||
tag_id {tag_id},
|
||||
charging_profile {},
|
||||
authorize_finished {false}
|
||||
{
|
||||
}
|
||||
|
||||
PendingStartRequest(std::shared_ptr<PlatformInterface> const& platform, ocpp1_6::RemoteStartTransactionReq const& req)
|
||||
: created_timestamp {platform->steadyClockNow()},
|
||||
pending_authorize_req {platform},
|
||||
connector_id {req.connectorId.value_or(0)},
|
||||
tag_id {req.idTag.value()},
|
||||
charging_profile {req.chargingProfile},
|
||||
authorize_finished {!platform->getSettings()->AuthorizeRemoteTxRequests.getValue()}
|
||||
{
|
||||
}
|
||||
|
||||
SteadyPointMillis created_timestamp;
|
||||
OperationHolder<std::string> pending_authorize_req;
|
||||
int connector_id;
|
||||
std::string tag_id;
|
||||
std::optional<ocpp1_6::ChargingProfile> charging_profile;
|
||||
|
||||
bool authorize_finished;
|
||||
};
|
||||
}
|
||||
|
||||
class TransactionModule1_6 : public ServiceStateful1_6 {
|
||||
private:
|
||||
static constexpr int kPriorityStartTransaction = 0;
|
||||
static constexpr int kPriorityStopTransaction = 0;
|
||||
static constexpr int kPriorityMeterValue = 5;
|
||||
|
||||
static const int kMaxReadingsBytes = 10*1024;
|
||||
static const int kGeneralOperationMaxFailures = 4;
|
||||
static constexpr int kOfflineTransactions = 3;
|
||||
static constexpr char const* kDefaultTagId = "missing-tag";
|
||||
|
||||
static constexpr int kTransactionMessageRetryIntervalSeconds = 10;
|
||||
static const int kSecondsPerDay = 86400; // 24*60*60
|
||||
public:
|
||||
TransactionModule1_6(
|
||||
std::shared_ptr<PlatformInterface> const& platform,
|
||||
std::shared_ptr<BootNotificationModule> boot_notification_module,
|
||||
std::shared_ptr<PowerManagementModule1_6> power_management_module,
|
||||
std::shared_ptr<PendingMessagesModule> pending_messages_module,
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_module,
|
||||
std::shared_ptr<StationInterface> station
|
||||
) : platform_(platform),
|
||||
boot_notification_module_(std::move(boot_notification_module)),
|
||||
power_management_module_(std::move(power_management_module)),
|
||||
pending_messages_module_(std::move(pending_messages_module)),
|
||||
connector_status_module_(std::move(connector_status_module)),
|
||||
station_(std::move(station))
|
||||
{
|
||||
settings_ = platform_->getSettings();
|
||||
|
||||
std::default_random_engine random_engine {std::random_device{}()};
|
||||
std::uniform_int_distribution<int64_t> distribution(
|
||||
std::numeric_limits<int64_t>::min(),
|
||||
std::numeric_limits<int64_t>::max()
|
||||
);
|
||||
unique_index_ = distribution(random_engine);
|
||||
|
||||
stop_transaction_supplier_ = std::make_shared<PendingMessagesModule::saved_message_supplier> (
|
||||
[&](std::function<void(detail::PendingMessageWrapper const&)> const& processor) {
|
||||
for (auto& entry : active_transactions_) {
|
||||
if (!entry.second.has_value())
|
||||
continue;
|
||||
|
||||
auto const request = generateStopRequest(entry.first, ocpp1_6::Reason::kPowerLoss);
|
||||
if (request.has_value())
|
||||
processor(request.value());
|
||||
}
|
||||
}
|
||||
);
|
||||
pending_messages_module_->registerOnSaveMessageSupplier(stop_transaction_supplier_);
|
||||
}
|
||||
|
||||
~TransactionModule1_6() override {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Deleting TransactionModule1_6";
|
||||
}
|
||||
|
||||
private:
|
||||
void runStep(ocpp1_6::OcppRemote& remote) override {
|
||||
// If a reset was requested stop all active transactions
|
||||
if (connector_status_module_->getPendingReset()) {
|
||||
for (auto& x : active_transactions_) {
|
||||
if (!x.second.has_value())
|
||||
continue;
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Stopping transaction (pending reset) - transaction ID: " << x.second->transaction_id;
|
||||
stopTransaction(x.first, connector_status_module_->getResetReasonHard() ? ocpp1_6::Reason::kHardReset : ocpp1_6::Reason::kSoftReset);
|
||||
}
|
||||
return; // We should not handle any other events when reset is pending.
|
||||
}
|
||||
|
||||
// Process an RFID tap if there was a state change
|
||||
auto id_token = station_->readToken1_6();
|
||||
if (id_token != last_rfid_tag_id_) {
|
||||
last_rfid_tag_id_ = id_token;
|
||||
if (id_token.has_value())
|
||||
processRfidTap(id_token->value());
|
||||
}
|
||||
|
||||
// Process plugged in state changes (PlugAndCharge and stopping transaction on disconnect)
|
||||
for (auto const& entry: station_->getConnectorMetadata()) {
|
||||
// Only take action if the connector state has changed. If, for example, a pending RemoteStarTransaction
|
||||
// request timed out do not attempt an autostart transaction until the cable is unplugged and plugged
|
||||
// back in.
|
||||
auto const connector_status = station_->pollConnectorStatus(entry.first);
|
||||
if (!connector_status.has_value())
|
||||
continue;
|
||||
|
||||
auto const id = entry.second.connector_id1_6;
|
||||
if (connector_status->vehicle_connected == last_plugged_in_state_[id])
|
||||
continue;
|
||||
|
||||
last_plugged_in_state_[id] = connector_status->vehicle_connected;
|
||||
processVehicleConnectedStateChanged(id, connector_status->vehicle_connected);
|
||||
}
|
||||
|
||||
// First process any pending start requests for a specific connector, then process any for the station
|
||||
for (auto& entry : pending_start_req_) {
|
||||
if (entry.first > 0)
|
||||
processPendingStartRequest(remote, entry.second);
|
||||
}
|
||||
for (auto& entry : pending_start_req_) {
|
||||
if (entry.first <= 0)
|
||||
processPendingStartRequest(remote, entry.second);
|
||||
}
|
||||
|
||||
std::vector<std::optional<ocpp2_0::EVSEType>> pending;
|
||||
|
||||
for (auto& entry : pending_start_req_) {
|
||||
if (entry.second.has_value()) {
|
||||
if (entry.first > 0) {
|
||||
pending.emplace_back(ocpp2_0::EVSEType {entry.first, 1});
|
||||
} else if (entry.first == 0 && entry.second != std::nullopt) {
|
||||
pending.emplace_back(std::nullopt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connector_status_module_->setPendingStartRequests(pending);
|
||||
|
||||
// Process transactions
|
||||
for (auto& entry : active_transactions_) {
|
||||
if (!entry.second.has_value())
|
||||
continue;
|
||||
|
||||
auto const evse = station_->lookupConnectorId1_6(entry.first);
|
||||
if (!evse.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state: connector did not exist for running transaction - dropping: " << entry.second->transaction_id;
|
||||
entry.second = std::nullopt;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool first_reading = !entry.second->last_reading_timestamp.has_value();
|
||||
if (shouldTakeReading(entry.second->last_reading_timestamp, settings_->MeterValueSampleInterval.getValue()))
|
||||
addReading(entry.second.value(), first_reading ? ocpp1_6::ReadingContext::kTransactionBegin : ocpp1_6::ReadingContext::kSamplePeriodic);
|
||||
|
||||
first_reading = !entry.second->last_clock_aligned_reading_timestamp.has_value();
|
||||
if (shouldTakeReading(entry.second->last_clock_aligned_reading_timestamp, settings_->ClockAlignedDataInterval.getValue()))
|
||||
addReading(entry.second.value(), first_reading ? ocpp1_6::ReadingContext::kTransactionBegin : ocpp1_6::ReadingContext::kSampleClock);
|
||||
}
|
||||
|
||||
#if 0 // For OCTT _012
|
||||
for (auto it = pending_stop_transaction_times_.begin(); it != pending_stop_transaction_times_.end(); ) {
|
||||
if (platform_->steadyClockNow() - it->second > 5000 ) {
|
||||
stopTransaction(it->first, ocpp1_6::Reason::kRemote);
|
||||
it = pending_stop_transaction_times_.erase(it); // erase returns the next iterator
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStartTransactionRsp>> onRemoteStartTransactionReq(
|
||||
const ocpp1_6::RemoteStartTransactionReq &req
|
||||
) override {
|
||||
int connector_id = 0;
|
||||
if (req.connectorId.has_value()) {
|
||||
connector_id = req.connectorId.value();
|
||||
|
||||
bool found = false;
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
if (entry.second.connector_id1_6 == connector_id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return ocpp1_6::RemoteStartTransactionRsp{ocpp1_6::RemoteStartStopStatus::kRejected};
|
||||
}
|
||||
}
|
||||
|
||||
if (req.chargingProfile.has_value()) {
|
||||
// Note: as per 5.16.2. the profile purpose must be TxProfile
|
||||
if (req.chargingProfile->chargingProfilePurpose != ocpp1_6::ChargingProfilePurposeType::kTxProfile)
|
||||
return ocpp1_6::RemoteStartTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
|
||||
|
||||
// Note: as per 5.16.2. the transaction ID must not be set
|
||||
if (req.chargingProfile->transactionId.has_value())
|
||||
return ocpp1_6::RemoteStartTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
|
||||
}
|
||||
|
||||
bool available_connector = true;
|
||||
// check if there is an active transaction already
|
||||
for (auto& x : active_transactions_) {
|
||||
if (req.connectorId && req.connectorId == x.first && x.second) {
|
||||
available_connector = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!available_connector) {
|
||||
return ocpp1_6::RemoteStartTransactionRsp{ocpp1_6::RemoteStartStopStatus::kRejected};
|
||||
}
|
||||
|
||||
// Note: adopting the convention that a new RemoteStartTransaction request will replace an existing pending
|
||||
// one, rather than be blocked by it.
|
||||
pending_start_req_[connector_id] = transaction_module1_6::PendingStartRequest {platform_, req};
|
||||
return ocpp1_6::RemoteStartTransactionRsp {ocpp1_6::RemoteStartStopStatus::kAccepted};
|
||||
}
|
||||
|
||||
void onAuthorizeRsp(
|
||||
const std::string &unique_id,
|
||||
const std::variant<ocpp1_6::AuthorizeRsp, ocpp1_6::CallError> &rsp
|
||||
) override {
|
||||
for (auto& entry : pending_start_req_) {
|
||||
if (!entry.second.has_value())
|
||||
continue;
|
||||
if (entry.second->pending_authorize_req != unique_id)
|
||||
continue;
|
||||
|
||||
if (std::holds_alternative<ocpp1_6::AuthorizeRsp>(rsp)) {
|
||||
// Note: only clearing the operation if a valid response was received, otherwise treating as absent
|
||||
// and retrying after configured timeout.
|
||||
entry.second->pending_authorize_req = kNoOperation;
|
||||
|
||||
auto const& value = std::get<ocpp1_6::AuthorizeRsp>(rsp);
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Received authorize response: " << value;
|
||||
switch (value.idTagInfo.status) {
|
||||
case ocpp1_6::AuthorizationStatus::kAccepted:
|
||||
entry.second->authorize_finished = true;
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Set authorize_finished to true: " << entry.second->authorize_finished;
|
||||
break;
|
||||
|
||||
case ocpp1_6::AuthorizationStatus::kValueNotFoundInEnum:
|
||||
case ocpp1_6::AuthorizationStatus::kBlocked:
|
||||
case ocpp1_6::AuthorizationStatus::kExpired:
|
||||
case ocpp1_6::AuthorizationStatus::kInvalid:
|
||||
case ocpp1_6::AuthorizationStatus::kConcurrentTx:
|
||||
entry.second = std::nullopt;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to Authorize request: " << std::get<ocpp1_6::CallError> (rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onStartTransactionRsp(
|
||||
const std::string &unique_id,
|
||||
const std::variant<ocpp1_6::StartTransactionRsp, ocpp1_6::CallError> &rsp
|
||||
) override {
|
||||
// Note: this is processed here in addition to pending_messages to allow this module to take action if the
|
||||
// back-end does not accept the provided idTag.
|
||||
for (auto& entry : active_transactions_) {
|
||||
if (!entry.second.has_value())
|
||||
continue;
|
||||
if (unique_id != entry.second->start_transaction_request_id)
|
||||
continue;
|
||||
|
||||
if (std::holds_alternative<ocpp1_6::StartTransactionRsp>(rsp)) {
|
||||
auto const &value = std::get<ocpp1_6::StartTransactionRsp>(rsp);
|
||||
entry.second->transaction_id = value.transactionId;
|
||||
|
||||
switch (value.idTagInfo.status) {
|
||||
case ocpp1_6::AuthorizationStatus::kAccepted:
|
||||
power_management_module_->onActiveTransactionIdAssigned(entry.first, value.transactionId);
|
||||
break;
|
||||
|
||||
case ocpp1_6::AuthorizationStatus::kValueNotFoundInEnum:
|
||||
case ocpp1_6::AuthorizationStatus::kBlocked:
|
||||
case ocpp1_6::AuthorizationStatus::kExpired:
|
||||
case ocpp1_6::AuthorizationStatus::kInvalid:
|
||||
case ocpp1_6::AuthorizationStatus::kConcurrentTx:
|
||||
// TODO: Support MaxEnergyOnInvalidId?
|
||||
stopTransaction(entry.first, ocpp1_6::Reason::kDeAuthorized);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Error response to StartTransaction request: " << std::get<ocpp1_6::CallError>(rsp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStopTransactionRsp>> onRemoteStopTransactionReq(
|
||||
const ocpp1_6::RemoteStopTransactionReq &req
|
||||
) override {
|
||||
for (auto& entry : active_transactions_) {
|
||||
if (!entry.second.has_value())
|
||||
continue;
|
||||
if (!entry.second->transaction_id.has_value())
|
||||
continue;
|
||||
if (entry.second->transaction_id.value() != req.transactionId)
|
||||
continue;
|
||||
|
||||
#if 0 // For OCTT _012
|
||||
pending_stop_transaction_times_[entry.first] = platform_->steadyClockNow();
|
||||
#else
|
||||
stopTransaction(entry.first, ocpp1_6::Reason::kRemote);
|
||||
#endif
|
||||
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kAccepted};
|
||||
}
|
||||
|
||||
// TODO: unrecognized transaction module behaves badly with this one. Maybe fold that implementation into
|
||||
// this one?
|
||||
//return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
|
||||
|
||||
if (!settings_->StopTransactionWithDifferentId.getValue())
|
||||
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
|
||||
if (!force_stop_transaction_.has_value()) {
|
||||
force_stop_transaction_ = req.transactionId;
|
||||
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kAccepted};
|
||||
} else {
|
||||
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<ocpp1_6::ResponseToRequest <ocpp1_6::TriggerMessageRsp>>
|
||||
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
|
||||
if (req.requestedMessage != ocpp1_6::MessageTrigger::kMeterValues)
|
||||
return std::nullopt;
|
||||
|
||||
for (auto& entry : active_transactions_) {
|
||||
if (!entry.second.has_value())
|
||||
continue;
|
||||
if (req.connectorId.has_value() && req.connectorId.value() != entry.first)
|
||||
continue;
|
||||
|
||||
addReading(entry.second.value(), ocpp1_6::ReadingContext::kTrigger);
|
||||
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kAccepted};
|
||||
}
|
||||
|
||||
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kRejected};
|
||||
}
|
||||
|
||||
private:
|
||||
void startTransaction(
|
||||
int connector_id,
|
||||
std::string const& id_tag,
|
||||
std::optional<ocpp1_6::ChargingProfile> const& charging_profile = std::nullopt
|
||||
) {
|
||||
auto const group_id = unique_index_++;
|
||||
auto const evse = station_->lookupConnectorId1_6(connector_id);
|
||||
if (!evse.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << connector_id;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const status = station_->pollConnectorStatus(evse.value());
|
||||
if (!status.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed fetching connector status for EVSE: " << evse;
|
||||
return;
|
||||
}
|
||||
|
||||
auto start_transaction_request_id = pending_messages_module_->sendRequest1_6(
|
||||
ocpp1_6::StartTransactionReq {
|
||||
connector_id,
|
||||
id_tag,
|
||||
(int)status->meter_watt_hours,
|
||||
std::nullopt,
|
||||
platform_->systemClockNow()
|
||||
},
|
||||
PendingMessagePolicy {
|
||||
PendingMessageType::kTransactionEvent,
|
||||
group_id,
|
||||
settings_->TransactionMessageAttempts.getValue(),
|
||||
settings_->TransactionMessageRetryInterval.getValue(),
|
||||
kPriorityStartTransaction,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
}
|
||||
);
|
||||
// Setting enabling charging earlier for OCTT test case _010
|
||||
station_->setChargingEnabled(evse.value(), true);
|
||||
|
||||
active_transactions_[connector_id] = transaction_module1_6::TransactionContainer {
|
||||
connector_id,
|
||||
group_id,
|
||||
id_tag,
|
||||
start_transaction_request_id
|
||||
};
|
||||
|
||||
power_management_module_->onActiveTransactionStarted(
|
||||
connector_id,
|
||||
charging_profile
|
||||
);
|
||||
}
|
||||
|
||||
std::optional<detail::PendingMessageWrapper> generateStopRequest(int connector_id, ocpp1_6::Reason const& reason) {
|
||||
auto const& transaction = active_transactions_[connector_id];
|
||||
if (!transaction.has_value())
|
||||
return std::nullopt;
|
||||
|
||||
auto const evse = station_->lookupConnectorId1_6(connector_id);
|
||||
if (!evse.has_value()) {
|
||||
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << connector_id;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto const status = station_->pollConnectorStatus(evse.value());
|
||||
|
||||
if (!status.has_value()) {
|
||||
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed fetching connector status for EVSE: " << evse;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return pending_messages_module_->generateRequest1_6(
|
||||
ocpp1_6::StopTransactionReq {
|
||||
transaction->id_tag,
|
||||
(int)status->meter_watt_hours,
|
||||
platform_->systemClockNow(),
|
||||
transaction->transaction_id.value_or(0),
|
||||
reason,
|
||||
{} // TODO: Stop transaction data
|
||||
},
|
||||
PendingMessagePolicy {
|
||||
PendingMessageType::kTransactionEvent,
|
||||
transaction->group_id,
|
||||
settings_->TransactionMessageAttempts.getValue(),
|
||||
settings_->TransactionMessageRetryInterval.getValue(),
|
||||
kPriorityStopTransaction,
|
||||
false,
|
||||
false
|
||||
}
|
||||
);
|
||||
power_management_module_->onActiveTransactionFinished(connector_id);
|
||||
}
|
||||
|
||||
void stopTransaction(int connector_id, ocpp1_6::Reason const& reason) {
|
||||
std::optional<transaction_module1_6::TransactionContainer> transaction = std::nullopt;
|
||||
std::swap(transaction, active_transactions_[connector_id]);
|
||||
if (!transaction.has_value())
|
||||
return;
|
||||
|
||||
auto const evse = station_->lookupConnectorId1_6(connector_id);
|
||||
if (!evse.has_value()) {
|
||||
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << connector_id;
|
||||
return;
|
||||
}
|
||||
|
||||
auto const status = station_->pollConnectorStatus(evse.value());
|
||||
station_->setChargingEnabled(evse.value(), false);
|
||||
|
||||
if (!status.has_value()) {
|
||||
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed fetching connector status for EVSE: " << evse;
|
||||
return;
|
||||
}
|
||||
|
||||
pending_messages_module_->sendRequest1_6(
|
||||
ocpp1_6::StopTransactionReq {
|
||||
transaction->id_tag,
|
||||
(int)status->meter_watt_hours,
|
||||
platform_->systemClockNow(),
|
||||
transaction->transaction_id.value_or(0),
|
||||
reason,
|
||||
{} // TODO: Stop transaction data
|
||||
},
|
||||
PendingMessagePolicy {
|
||||
PendingMessageType::kTransactionEvent,
|
||||
transaction->group_id,
|
||||
settings_->TransactionMessageAttempts.getValue(),
|
||||
settings_->TransactionMessageRetryInterval.getValue(),
|
||||
kPriorityStopTransaction,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
}
|
||||
);
|
||||
power_management_module_->onActiveTransactionFinished(connector_id);
|
||||
}
|
||||
|
||||
void processRfidTap(std::string const& id_tag) {
|
||||
// Check if a running transaction already uses this tag; if so it should be interpreted as a stop
|
||||
// request.
|
||||
for (auto& x : active_transactions_) {
|
||||
if (!x.second.has_value())
|
||||
continue;
|
||||
if (string::EqualsIgnoreCaseAscii(x.second->id_tag, id_tag)) {
|
||||
stopTransaction(x.first, ocpp1_6::Reason::kLocal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to check if there's availabe connector for another transaction, otherwise ignore the rfid tap event.
|
||||
// This is for the OCTT 1.6 test case _068 where a different RFID should not trigger the sending of the
|
||||
// Authorize request if there is an active transaction.
|
||||
bool found_available_connector = false;
|
||||
for (auto const& entry: station_->getConnectorMetadata()) {
|
||||
if (entry.first.id == 0) continue;
|
||||
auto const connector_status = station_->pollConnectorStatus(entry.first);
|
||||
if (!connector_status.has_value())
|
||||
continue;
|
||||
if (connector_status->connector_available) {
|
||||
found_available_connector = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_available_connector)
|
||||
return;
|
||||
|
||||
// Add the PlugAndCharge entry
|
||||
pending_start_req_[0] = transaction_module1_6::PendingStartRequest{platform_, 0, id_tag};
|
||||
}
|
||||
|
||||
void processVehicleConnectedStateChanged(int connector_id, bool connected) {
|
||||
if (active_transactions_[connector_id].has_value()) {
|
||||
if (!connected) {
|
||||
stopTransaction(connector_id, ocpp1_6::Reason::kEVDisconnected);
|
||||
}
|
||||
|
||||
// Note: PlugAndCharge should only trigger if there's no activate transaction.
|
||||
return;
|
||||
}
|
||||
|
||||
auto plug_and_charge_id = settings_->PlugAndChargeId.getValue();
|
||||
if (connected && !plug_and_charge_id.empty()) {
|
||||
// If there's another potentially applicable start operation pending (remote start request, RFID tap,
|
||||
// etc) it should take precedence and autostart should be ignored.
|
||||
if (pending_start_req_[0].has_value() || pending_start_req_[connector_id].has_value())
|
||||
return;
|
||||
|
||||
// Add the PlugAndCharge entry
|
||||
pending_start_req_[connector_id] = transaction_module1_6::PendingStartRequest {
|
||||
platform_,
|
||||
connector_id,
|
||||
plug_and_charge_id
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void processPendingStartRequest(ocpp1_6::OcppRemote& remote, std::optional<transaction_module1_6::PendingStartRequest>& pending) {
|
||||
if (!pending.has_value())
|
||||
return;
|
||||
|
||||
// If the request has expired remove it
|
||||
auto const elapsed = platform_->steadyClockNow() - pending->created_timestamp;
|
||||
if (elapsed >= settings_->ConnectionTimeOut.getValue()*1000) {
|
||||
pending = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt to authorize the request if necessary
|
||||
if (!pending->authorize_finished) {
|
||||
if (treatAsConnected()) {
|
||||
if (!pending->pending_authorize_req.operationInProgress()) {
|
||||
pending->pending_authorize_req.setWithTimeout(
|
||||
settings_->DefaultMessageTimeout.getValue(),
|
||||
remote.sendAuthorizeReq(ocpp1_6::AuthorizeReq {pending->tag_id})
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (!settings_->AllowOfflineTxForUnknownId.getValue()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to start a transaction
|
||||
for (auto const& entry : station_->getConnectorMetadata()) {
|
||||
auto const id = entry.second.connector_id1_6;
|
||||
if (pending->connector_id != 0 && pending->connector_id != id)
|
||||
continue;
|
||||
|
||||
auto const status = station_->pollConnectorStatus(entry.first);
|
||||
if (!status.has_value() || !status->vehicle_connected)
|
||||
continue;
|
||||
if (active_transactions_[id].has_value())
|
||||
continue;
|
||||
|
||||
// Start new transaction
|
||||
startTransaction(id, pending->tag_id, pending->charging_profile);
|
||||
pending = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void addReading(
|
||||
transaction_module1_6::TransactionContainer& entry,
|
||||
ocpp1_6::ReadingContext const& context
|
||||
) {
|
||||
auto const evse = station_->lookupConnectorId1_6(entry.connector_id);
|
||||
if (!evse.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << entry.connector_id;
|
||||
return;
|
||||
}
|
||||
|
||||
auto enabled = parseMeasurandsString(settings_->MeterValuesSampledData.getValue());
|
||||
auto sampled_values = station_->pollMeterValues1_6(evse.value());
|
||||
sampled_values.erase(
|
||||
std::remove_if(
|
||||
sampled_values.begin(),
|
||||
sampled_values.end(),
|
||||
[&](ocpp1_6::SampledValue const& value) {
|
||||
auto measurand = value.measurand.value_or(ocpp1_6::Measurand::kEnergyActiveImportRegister);
|
||||
return enabled.find(measurand) == enabled.end();
|
||||
}
|
||||
),
|
||||
sampled_values.end()
|
||||
);
|
||||
|
||||
// Update the context
|
||||
for (auto& value : sampled_values)
|
||||
value.context = context;
|
||||
|
||||
// TODO: It may be helpful to batch these while offline so that fewer requests are sent when the station
|
||||
// reconnects instead of persisting individual samples.
|
||||
pending_messages_module_->sendRequest1_6(
|
||||
ocpp1_6::MeterValuesReq {
|
||||
entry.connector_id,
|
||||
entry.transaction_id.value_or(0),
|
||||
{
|
||||
ocpp1_6::MeterValue {
|
||||
platform_->systemClockNow(),
|
||||
std::move(sampled_values)
|
||||
}
|
||||
}
|
||||
},
|
||||
PendingMessagePolicy {
|
||||
PendingMessageType::kTransactionEvent,
|
||||
entry.group_id,
|
||||
settings_->TransactionMessageAttempts.getValue(),
|
||||
settings_->TransactionMessageRetryInterval.getValue(),
|
||||
kPriorityMeterValue,
|
||||
false,
|
||||
false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
bool shouldTakeReading(std::optional<SteadyPointMillis>& last, int interval_seconds) {
|
||||
// Treat a configured interval of 0 (or negative) as disabled
|
||||
if (interval_seconds <= 0)
|
||||
return false;
|
||||
|
||||
auto const now = platform_->steadyClockNow();
|
||||
if (last.has_value()) {
|
||||
auto const delta = now - last.value();
|
||||
if (delta < interval_seconds*1000)
|
||||
return false;
|
||||
}
|
||||
|
||||
last = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool treatAsConnected() {
|
||||
if (!boot_notification_module_->registrationComplete())
|
||||
return false;
|
||||
|
||||
auto const websocket = platform_->ocppConnection();
|
||||
if (websocket == nullptr)
|
||||
return false;
|
||||
|
||||
return websocket->isConnected();
|
||||
}
|
||||
|
||||
static std::unordered_set<ocpp1_6::Measurand::Value> parseMeasurandsString(std::string const& measurands) {
|
||||
// comma separated list, e.g. "Current.Import,Energy.Active.Import.Register"
|
||||
std::unordered_set<ocpp1_6::Measurand::Value> ret;
|
||||
string::SplitVisitor(measurands, ",", [&](std::string const& value) {
|
||||
auto value_as_enum = ocpp1_6::Measurand::from_string(value);
|
||||
if (value_as_enum == ocpp1_6::Measurand::kValueNotFoundInEnum) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected measurand: " << value;
|
||||
} else {
|
||||
ret.insert(value_as_enum);
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<PlatformInterface> platform_;
|
||||
std::shared_ptr<BootNotificationModule> boot_notification_module_;
|
||||
std::shared_ptr<PowerManagementModule1_6> power_management_module_;
|
||||
std::shared_ptr<PendingMessagesModule> pending_messages_module_;
|
||||
std::shared_ptr<ConnectorStatusModule> connector_status_module_;
|
||||
std::shared_ptr<StationInterface> station_;
|
||||
std::shared_ptr<PendingMessagesModule::saved_message_supplier> stop_transaction_supplier_;
|
||||
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::atomic<uint64_t> unique_index_;
|
||||
// key: connector ID
|
||||
std::unordered_map<int, std::optional<transaction_module1_6::PendingStartRequest>> pending_start_req_;
|
||||
// key: connector ID
|
||||
std::unordered_map<int, bool> last_plugged_in_state_;
|
||||
std::optional<ocpp1_6::IdToken> last_rfid_tag_id_ = std::nullopt;
|
||||
|
||||
// Note: a transaction will remain here until the connector is unplugged or a new transaction starts
|
||||
// key: connector ID
|
||||
std::unordered_map<int, std::optional<transaction_module1_6::TransactionContainer>> active_transactions_;
|
||||
|
||||
std::optional<int> connector_hold_id_ {std::nullopt};
|
||||
std::optional<int> force_stop_transaction_ = std::nullopt;
|
||||
|
||||
#if 0 // For OCTT _012
|
||||
std::unordered_map<int, SteadyPointMillis> pending_stop_transaction_times_;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_TRANSACTION_MODULE1_6_H
|
||||
1685
tools/openocpp/include/openocpp/module/transaction_module2_0.h
Normal file
1685
tools/openocpp/include/openocpp/module/transaction_module2_0.h
Normal file
File diff suppressed because it is too large
Load Diff
324
tools/openocpp/include/openocpp/protocol/common/date_time.h
Normal file
324
tools/openocpp/include/openocpp/protocol/common/date_time.h
Normal file
@@ -0,0 +1,324 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_DATE_TIME_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_DATE_TIME_H
|
||||
|
||||
#include "openocpp/model/system_types.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <ctime>
|
||||
#include <stdio.h>
|
||||
#include <chrono>
|
||||
|
||||
#if defined (USE_HH_DATE)
|
||||
#include <date/date.h>
|
||||
#endif
|
||||
|
||||
namespace chargelab::common {
|
||||
class DateTime {
|
||||
static constexpr int kMilliSecondSize = 3;
|
||||
public:
|
||||
DateTime() = default;
|
||||
DateTime(const DateTime& other) = default;
|
||||
DateTime& operator=(const DateTime& other) = default;
|
||||
|
||||
DateTime(SystemTimeMillis timestamp) : text_(timestampToText(timestamp)), timestamp_(timestamp) {
|
||||
}
|
||||
DateTime(std::string text) : text_(text), timestamp_(textToTimestamp(text)) {
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::string> getText() const {
|
||||
return text_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<SystemTimeMillis> getTimestamp() const {
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
static void write_json(json::JsonWriter& writer, DateTime const& value) {
|
||||
if (value.text_.has_value()) {
|
||||
writer.String(value.text_.value());
|
||||
} else {
|
||||
writer.Null();
|
||||
}
|
||||
}
|
||||
|
||||
static bool read_json(json::JsonReader& reader, DateTime& value) {
|
||||
auto const token = reader.nextToken();
|
||||
if (!token.has_value())
|
||||
return false;
|
||||
if (!std::holds_alternative<json::StringType>(token.value()))
|
||||
return false;
|
||||
|
||||
auto const& text = std::get<json::StringType>(token.value());
|
||||
value.text_ = std::string{text.str, text.length};
|
||||
value.timestamp_ = textToTimestamp(value.text_.value());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator==(DateTime const& rhs) const {
|
||||
return getTimestamp() == rhs.getTimestamp();
|
||||
}
|
||||
|
||||
bool operator!=(DateTime const& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isAfter(SystemTimeMillis ts, bool default_value) const {
|
||||
if (!timestamp_.has_value())
|
||||
return default_value;
|
||||
|
||||
return timestamp_.value() - ts > 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isBefore(SystemTimeMillis ts, bool default_value) const {
|
||||
if (!timestamp_.has_value())
|
||||
return default_value;
|
||||
|
||||
return timestamp_.value() - ts < 0;
|
||||
}
|
||||
|
||||
#if !defined(USE_HH_DATE)
|
||||
static std::optional<std::string> timestampToText(SystemTimeMillis const& timestamp) {
|
||||
time_t time = timestamp/1000;
|
||||
int millisecond = timestamp%1000;
|
||||
if (millisecond < 0) { // adjust the minus value to be positive, borrow 1 second
|
||||
time -= 1;
|
||||
millisecond += 1000;
|
||||
}
|
||||
|
||||
auto tm = gmtime(&time);
|
||||
char buf[100]{};
|
||||
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
static std::optional<SystemTimeMillis> textToTimestamp(std::string const& text) {
|
||||
auto text2 = removeExcessiveFractionDigits(text);
|
||||
auto timezone_minutes = retrieveTimezoneMinutes(text2);
|
||||
if (!timezone_minutes.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "wrong time zone format:" << text;
|
||||
return std::nullopt;
|
||||
}
|
||||
// "2022-12-17T14:37:09.894Z"
|
||||
auto tm = parseTm(text2);
|
||||
if (tm) {
|
||||
std::int64_t ret = timegm2(&tm.value()); // timegm() is not supported by ESP32
|
||||
ret -= timezone_minutes.value() * 60;
|
||||
return static_cast<SystemTimeMillis>(ret * 1000 + retrieveMillisecond(text2));
|
||||
}
|
||||
CHARGELAB_LOG_MESSAGE(error) << "wrong date time format:" << text;
|
||||
return std::nullopt;
|
||||
}
|
||||
#else
|
||||
static std::optional<std::string> timestampToText(SystemTimeMillis const& timestamp) {
|
||||
date::sys_time<std::chrono::milliseconds> ts {std::chrono::milliseconds(timestamp)};
|
||||
|
||||
CHARGELAB_TRY {
|
||||
std::ostringstream out;
|
||||
|
||||
// TODO: Need to confirm this *always* uses UTC timezone
|
||||
out << date::format("%FT%TZ", ts);
|
||||
if (!out.fail()) {
|
||||
return out.str();
|
||||
}
|
||||
} CHARGELAB_CATCH {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected exception converting timestamp to date/time string: " << e.what();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::optional<SystemTimeMillis> textToTimestamp(std::string const& text) {
|
||||
auto text2 = removeExcessiveFractionDigits(text);
|
||||
|
||||
date::sys_time<std::chrono::milliseconds> ts;
|
||||
|
||||
CHARGELAB_TRY {
|
||||
std::istringstream in{text2};
|
||||
in >> date::parse("%FT%TZ", ts);
|
||||
if (!in.fail()) {
|
||||
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(ts.time_since_epoch()).count();
|
||||
return static_cast<SystemTimeMillis> (millis);
|
||||
}
|
||||
} CHARGELAB_CATCH {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected exception converting date/time string to timestamp: " << e.what();
|
||||
}
|
||||
|
||||
CHARGELAB_TRY {
|
||||
std::istringstream in{text2};
|
||||
in >> date::parse("%FT%T%Ez", ts);
|
||||
if (!in.fail()) {
|
||||
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(ts.time_since_epoch()).count();
|
||||
return static_cast<SystemTimeMillis> (millis);
|
||||
}
|
||||
} CHARGELAB_CATCH {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected exception converting date/time string to timestamp: " << e.what();
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
private:
|
||||
static std::string removeExcessiveFractionDigits(std::string const& time_text) {
|
||||
int pos = time_text.find('.');
|
||||
if (pos == -1) return time_text;
|
||||
int decimal_digit_count = 0;
|
||||
unsigned int i = pos + 1;
|
||||
while (i < time_text.length() && decimal_digit_count < kMilliSecondSize) {
|
||||
if (!std::isdigit(time_text[i])) break;
|
||||
++decimal_digit_count;
|
||||
++i;
|
||||
}
|
||||
if (decimal_digit_count == kMilliSecondSize) {
|
||||
auto start = i;
|
||||
while (i < time_text.length()) {
|
||||
if (!std::isdigit(time_text[i])) break;
|
||||
++i;
|
||||
}
|
||||
if (start != i) {
|
||||
return time_text.substr(0, start) + time_text.substr(i);
|
||||
}
|
||||
}
|
||||
return time_text;
|
||||
}
|
||||
|
||||
static int retrieveMillisecond(std::string const& timestamp) {
|
||||
auto find = timestamp.find('.');
|
||||
if (find == std::string::npos) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int milliseconds = 0;
|
||||
unsigned int multiplier = 100;
|
||||
for (unsigned int i = find+1; i < timestamp.length(); ++i) {
|
||||
if (!std::isdigit(timestamp[i])) break;
|
||||
milliseconds += (timestamp[i] - '0') * multiplier;
|
||||
multiplier /= 10;
|
||||
}
|
||||
|
||||
return milliseconds;
|
||||
}
|
||||
|
||||
// timestamp: e.g. "2022-10-17T14:37:09.894+03:00" https://www.w3.org/TR/NOTE-datetime
|
||||
static std::optional<int> retrieveTimezoneMinutes(std::string & timestamp) {
|
||||
auto find = timestamp.find('Z');
|
||||
if (find != std::string::npos) {
|
||||
timestamp = timestamp.substr(0, find); // ignore anything after 'Z'
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool negative = false;
|
||||
find = timestamp.find('+');
|
||||
if (find == std::string::npos) {
|
||||
find = timestamp.rfind('-');
|
||||
negative = true;
|
||||
}
|
||||
if (find == std::string::npos) return std::nullopt;
|
||||
|
||||
int hour = 0;
|
||||
int minute = 0;
|
||||
|
||||
auto find2 = timestamp.find(':', find);
|
||||
if (find2 == std::string::npos) { // only have hours
|
||||
if (sscanf(timestamp.c_str() + find + 1, "%2d", &hour) != 1) return std::nullopt;
|
||||
} else {
|
||||
if (sscanf(timestamp.c_str() + find + 1, "%2d:%2d", &hour, &minute) != 2) return std::nullopt;
|
||||
}
|
||||
|
||||
timestamp = timestamp.substr(0, find); // remove timezone from the timestamp for future process
|
||||
return (hour*60 + minute)*(negative ? -1 : 1);
|
||||
}
|
||||
|
||||
static std::optional<std::tm> parseTm(std::string const& timestamp) {
|
||||
int year, month, day, hour, minute, second;
|
||||
|
||||
if (sscanf(timestamp.c_str(), "%4d-%2d-%2dT%2d:%2d:%2d", &year, &month, &day, &hour, &minute, &second) != 6) {
|
||||
return std::nullopt;
|
||||
}
|
||||
//
|
||||
if (month < 1 || month > 12 || day < 1 || day > getDaysOfMonth(year, month) || hour < 0 || hour >= 24 ||
|
||||
minute < 0 || minute >= 60 || second < 0 || second >= 60) { // no leap seconds supported
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::tm tm{};
|
||||
|
||||
tm.tm_sec = second;
|
||||
tm.tm_min = minute;
|
||||
tm.tm_hour = hour;
|
||||
tm.tm_mday = day;
|
||||
tm.tm_mon = month-1;
|
||||
tm.tm_year = year-1900;
|
||||
|
||||
return tm;
|
||||
};
|
||||
|
||||
static std::time_t timegm2(std::tm const *tm) {
|
||||
if (tm == nullptr) return -1;
|
||||
if (tm->tm_mon >= 12 || tm->tm_hour >= 24 || tm->tm_min >= 60 || tm->tm_sec >= 61) {
|
||||
return -1;
|
||||
}
|
||||
if (getDaysOfMonth(tm->tm_year + 1900, tm->tm_mon + 1) == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// get the day to the tm date since 1900-01-01 00:00:00 +0000, UTC instead of the Epoch (1970-01-01 00:00:00 +0000, UTC)
|
||||
std::int64_t total_days = 0; //
|
||||
int year = tm->tm_year + 1900;
|
||||
|
||||
auto const year_since_epch = year - 1970;
|
||||
|
||||
int leap_years = totalLeapYears(year - 1) - totalLeapYears(1970);
|
||||
|
||||
total_days = year_since_epch*365 + leap_years;
|
||||
|
||||
for (int i = 0; i < tm->tm_mon; ++i) {
|
||||
total_days += getDaysOfMonth(year, i+1);
|
||||
}
|
||||
total_days += tm->tm_mday - 1; // tm_mday is 1-31
|
||||
std::int64_t total_hours = total_days*24 + tm->tm_hour;
|
||||
std::time_t total_seconds = (total_hours*60 + tm->tm_min)*60 + tm->tm_sec;
|
||||
|
||||
return total_seconds /*+ kSecondsFrom1990*/;
|
||||
}
|
||||
|
||||
static bool isLeapYear(unsigned int year) {
|
||||
return (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0));
|
||||
}
|
||||
// month: 1-based
|
||||
static int getDaysOfMonth(unsigned int year, int month) {
|
||||
switch (month) {
|
||||
case 1:
|
||||
case 3:
|
||||
case 5:
|
||||
case 7:
|
||||
case 8:
|
||||
case 10:
|
||||
case 12:
|
||||
return 31;
|
||||
case 4:
|
||||
case 6:
|
||||
case 9:
|
||||
case 11:
|
||||
return 30;
|
||||
case 2:
|
||||
return isLeapYear(year) ? 29 : 28;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int totalLeapYears(int year) {
|
||||
return year/4 - year/100 + year/400;
|
||||
}
|
||||
private:
|
||||
std::optional<std::string> text_ = std::nullopt;
|
||||
std::optional<SystemTimeMillis> timestamp_ = std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_DATE_TIME_H
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_PROTOCOL_CONSTANTS_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_PROTOCOL_CONSTANTS_H
|
||||
|
||||
namespace chargelab {
|
||||
class ProtocolConstants {
|
||||
public:
|
||||
static constexpr char const* kProtocolOcpp1_6 = "ocpp1.6";
|
||||
static constexpr char const* kProtocolOcpp2_0_1 = "ocpp2.0.1";
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_PROTOCOL_CONSTANTS_H
|
||||
211
tools/openocpp/include/openocpp/protocol/common/raw_json.h
Normal file
211
tools/openocpp/include/openocpp/protocol/common/raw_json.h
Normal file
@@ -0,0 +1,211 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_RAW_JSON_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_RAW_JSON_H
|
||||
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace chargelab::common {
|
||||
namespace detail {
|
||||
class RawJsonInterface {
|
||||
public:
|
||||
virtual ~RawJsonInterface() = default;
|
||||
virtual std::string data() const = 0;
|
||||
virtual void write_json(json::JsonWriter& writer) const = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class RawJsonLazy : public RawJsonInterface {
|
||||
public:
|
||||
RawJsonLazy(T value) : value_(std::move(value)) {}
|
||||
|
||||
std::string data() const override {
|
||||
return write_json_to_string(value_);
|
||||
}
|
||||
|
||||
void write_json(json::JsonWriter& writer) const override {
|
||||
json::WriteValue<T>::write_json(writer, value_);
|
||||
}
|
||||
|
||||
private:
|
||||
T value_;
|
||||
};
|
||||
}
|
||||
|
||||
class RawJson {
|
||||
private:
|
||||
using text_type = std::string;
|
||||
using lazy_type = std::shared_ptr<detail::RawJsonInterface>;
|
||||
using data_type = std::variant<text_type, lazy_type>;
|
||||
|
||||
public:
|
||||
RawJson() = default;
|
||||
RawJson(std::string data) : data_(std::move(data)) {}
|
||||
RawJson(std::shared_ptr<detail::RawJsonInterface> data) : data_(std::move(data)) {}
|
||||
|
||||
bool operator==(RawJson const& rhs) const {
|
||||
return data() == rhs.data();
|
||||
}
|
||||
|
||||
bool operator!=(RawJson const& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
std::string data() const {
|
||||
if (std::holds_alternative<text_type>(data_)) {
|
||||
return std::get<text_type>(data_);
|
||||
} else {
|
||||
return std::get<lazy_type>(data_)->data();
|
||||
}
|
||||
}
|
||||
|
||||
static bool read_json(json::JsonReader& reader, RawJson& value) {
|
||||
stream::StringWriter stream;
|
||||
json::JsonWriter writer {stream};
|
||||
|
||||
int object_stack = 0;
|
||||
int array_stack = 0;
|
||||
do {
|
||||
auto const& next = reader.nextToken();
|
||||
if (!next.has_value())
|
||||
return false;
|
||||
|
||||
auto const& token = next.value();
|
||||
if (std::holds_alternative<json::StartObjectType>(token)) {
|
||||
writer.StartObject();
|
||||
object_stack++;
|
||||
} else if (std::holds_alternative<json::EndObjectType>(token)) {
|
||||
writer.EndObject();
|
||||
object_stack--;
|
||||
} else if (std::holds_alternative<json::StartArrayType>(token)) {
|
||||
writer.StartArray();
|
||||
array_stack++;
|
||||
} else if (std::holds_alternative<json::EndArrayType>(token)) {
|
||||
writer.EndArray();
|
||||
array_stack--;
|
||||
} else if (std::holds_alternative<json::NullType>(token)) {
|
||||
writer.Null();
|
||||
} else if (std::holds_alternative<json::BoolType>(token)) {
|
||||
auto const& x = std::get<json::BoolType>(token);
|
||||
writer.Bool(x.value);
|
||||
} else if (std::holds_alternative<json::IntType>(token)) {
|
||||
auto const& x = std::get<json::IntType>(token);
|
||||
writer.Int(x.value);
|
||||
} else if (std::holds_alternative<json::UintType>(token)) {
|
||||
auto const& x = std::get<json::UintType>(token);
|
||||
writer.Uint(x.value);
|
||||
} else if (std::holds_alternative<json::Int64Type>(token)) {
|
||||
auto const& x = std::get<json::Int64Type>(token);
|
||||
writer.Int64(x.value);
|
||||
} else if (std::holds_alternative<json::Uint64Type>(token)) {
|
||||
auto const& x = std::get<json::Uint64Type>(token);
|
||||
writer.Uint64(x.value);
|
||||
} else if (std::holds_alternative<json::DoubleType>(token)) {
|
||||
auto const& x = std::get<json::DoubleType>(token);
|
||||
writer.Double(x.value);
|
||||
} else if (std::holds_alternative<json::RawNumberType>(token)) {
|
||||
auto const& x = std::get<json::RawNumberType>(token);
|
||||
writer.RawNumber(x.str, x.length);
|
||||
} else if (std::holds_alternative<json::StringType>(token)) {
|
||||
auto const& x = std::get<json::StringType>(token);
|
||||
writer.String(x.str, x.length);
|
||||
} else if (std::holds_alternative<json::KeyType>(token)) {
|
||||
auto const& x = std::get<json::KeyType>(token);
|
||||
writer.Key(x.str, x.length);
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected JSON token";
|
||||
}
|
||||
} while (object_stack > 0 || array_stack > 0);
|
||||
|
||||
value.data_ = stream.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void write_json(json::JsonWriter& writer, RawJson const& value) {
|
||||
if (std::holds_alternative<lazy_type>(value.data_)) {
|
||||
std::get<lazy_type>(value.data_)->write_json(writer);
|
||||
return;
|
||||
}
|
||||
|
||||
stream::StringReader stream {std::get<text_type>(value.data_)};
|
||||
json::JsonReader reader {stream};
|
||||
while (true) {
|
||||
auto const& next = reader.nextToken();
|
||||
if (!next.has_value())
|
||||
break;
|
||||
|
||||
auto const& token = next.value();
|
||||
if (std::holds_alternative<json::StartObjectType>(token)) {
|
||||
writer.StartObject();
|
||||
} else if (std::holds_alternative<json::EndObjectType>(token)) {
|
||||
writer.EndObject();
|
||||
} else if (std::holds_alternative<json::StartArrayType>(token)) {
|
||||
writer.StartArray();
|
||||
} else if (std::holds_alternative<json::EndArrayType>(token)) {
|
||||
writer.EndArray();
|
||||
} else if (std::holds_alternative<json::NullType>(token)) {
|
||||
writer.Null();
|
||||
} else if (std::holds_alternative<json::BoolType>(token)) {
|
||||
auto const& x = std::get<json::BoolType>(token);
|
||||
writer.Bool(x.value);
|
||||
} else if (std::holds_alternative<json::IntType>(token)) {
|
||||
auto const& x = std::get<json::IntType>(token);
|
||||
writer.Int(x.value);
|
||||
} else if (std::holds_alternative<json::UintType>(token)) {
|
||||
auto const& x = std::get<json::UintType>(token);
|
||||
writer.Uint(x.value);
|
||||
} else if (std::holds_alternative<json::Int64Type>(token)) {
|
||||
auto const& x = std::get<json::Int64Type>(token);
|
||||
writer.Int64(x.value);
|
||||
} else if (std::holds_alternative<json::Uint64Type>(token)) {
|
||||
auto const& x = std::get<json::Uint64Type>(token);
|
||||
writer.Uint64(x.value);
|
||||
} else if (std::holds_alternative<json::DoubleType>(token)) {
|
||||
auto const& x = std::get<json::DoubleType>(token);
|
||||
writer.Double(x.value);
|
||||
} else if (std::holds_alternative<json::RawNumberType>(token)) {
|
||||
auto const& x = std::get<json::RawNumberType>(token);
|
||||
writer.RawNumber(x.str, x.length);
|
||||
} else if (std::holds_alternative<json::StringType>(token)) {
|
||||
auto const& x = std::get<json::StringType>(token);
|
||||
writer.String(x.str, x.length);
|
||||
} else if (std::holds_alternative<json::KeyType>(token)) {
|
||||
auto const& x = std::get<json::KeyType>(token);
|
||||
writer.Key(x.str, x.length);
|
||||
} else {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected JSON token";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static RawJson from_value(T const& value) {
|
||||
RawJson result {};
|
||||
|
||||
stream::StringReader stream {write_json_to_string(value)};
|
||||
json::JsonReader reader {stream};
|
||||
|
||||
if (!read_json(reader, result)) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed creating RawJson from record";
|
||||
return RawJson {};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static RawJson from_value_lazy(T const& value) {
|
||||
return RawJson{std::shared_ptr<detail::RawJsonInterface> {new detail::RawJsonLazy<T> {value}}};
|
||||
}
|
||||
|
||||
static RawJson empty_object() {
|
||||
return RawJson{};
|
||||
}
|
||||
|
||||
private:
|
||||
data_type data_ = "{}";
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_RAW_JSON_H
|
||||
124
tools/openocpp/include/openocpp/protocol/common/small_string.h
Normal file
124
tools/openocpp/include/openocpp/protocol/common/small_string.h
Normal file
@@ -0,0 +1,124 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_SMALL_STRING_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_SMALL_STRING_H
|
||||
|
||||
#include "openocpp/common/logging.h"
|
||||
|
||||
#include <string>
|
||||
#include <exception>
|
||||
|
||||
namespace chargelab {
|
||||
class SmallString {
|
||||
public:
|
||||
~SmallString() {
|
||||
if (allocated_) {
|
||||
delete [] str_;
|
||||
str_ = nullptr;
|
||||
allocated_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
SmallString() {
|
||||
str_ = nullptr;
|
||||
size_ = 0;
|
||||
allocated_ = false;
|
||||
}
|
||||
|
||||
// TODO: This can be improved with various overrides, wrappers, and constexpr to restrict this constructor to
|
||||
// string literals more reliably, but this is likely sufficient for now.
|
||||
template <std::size_t N>
|
||||
SmallString(char const (&literal)[N]) {
|
||||
str_ = literal;
|
||||
size_ = N-1;
|
||||
allocated_ = false;
|
||||
}
|
||||
|
||||
SmallString(std::string const& text) {
|
||||
size_ = text.size();
|
||||
allocated_ = true;
|
||||
|
||||
auto block = new char[size_];
|
||||
std::memcpy(block, text.data(), size_);
|
||||
str_ = block;
|
||||
}
|
||||
|
||||
SmallString(SmallString const& other) {
|
||||
if (other.allocated_) {
|
||||
size_ = other.size_;
|
||||
allocated_ = true;
|
||||
|
||||
auto block = new char[size_];
|
||||
std::memcpy(block, other.str_, other.size_);
|
||||
str_ = block;
|
||||
} else {
|
||||
str_ = other.str_;
|
||||
size_ = other.size_;
|
||||
allocated_ = other.allocated_;
|
||||
}
|
||||
}
|
||||
|
||||
SmallString(SmallString&& other) noexcept {
|
||||
std::swap(str_, other.str_);
|
||||
std::swap(size_, other.size_);
|
||||
std::swap(allocated_, other.allocated_);
|
||||
}
|
||||
|
||||
SmallString& operator=(SmallString const& other) {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
if (allocated_) {
|
||||
delete str_;
|
||||
str_ = nullptr;
|
||||
allocated_ = false;
|
||||
}
|
||||
|
||||
if (other.allocated_) {
|
||||
size_ = other.size_;
|
||||
allocated_ = true;
|
||||
|
||||
auto block = new char[size_];
|
||||
std::memcpy(block, other.str_, other.size_);
|
||||
str_ = block;
|
||||
} else {
|
||||
str_ = other.str_;
|
||||
size_ = other.size_;
|
||||
allocated_ = other.allocated_;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
SmallString& operator=(SmallString&& other) noexcept {
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
std::swap(str_, other.str_);
|
||||
std::swap(size_, other.size_);
|
||||
std::swap(allocated_, other.allocated_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string value() const {
|
||||
if (str_ != nullptr) {
|
||||
return {str_, size_};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(SmallString const& rhs) const {
|
||||
return value() == rhs.value();
|
||||
}
|
||||
|
||||
bool operator!=(SmallString const& rhs) const {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
char const* str_ = nullptr;
|
||||
std::size_t size_ = 0;
|
||||
bool allocated_ = false;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_SMALL_STRING_H
|
||||
137
tools/openocpp/include/openocpp/protocol/common/vector.h
Normal file
137
tools/openocpp/include/openocpp/protocol/common/vector.h
Normal file
@@ -0,0 +1,137 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_VECTOR_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_VECTOR_H
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
namespace chargelab {
|
||||
template <typename T, bool Optional = false, int MinElements = 0, int MaxElements = -1>
|
||||
struct Vector {
|
||||
public:
|
||||
using this_type = Vector<T, Optional, MinElements, MaxElements>;
|
||||
using data_type = std::vector<T>;
|
||||
using visitor_type = std::function<void(std::function<void(T const&)>)>;
|
||||
|
||||
public:
|
||||
Vector() {}
|
||||
Vector(data_type data) : supplier_(std::move(data)) {}
|
||||
Vector(visitor_type visitor) : supplier_(std::move(visitor)) {}
|
||||
Vector(const this_type& other) = default;
|
||||
this_type& operator=(const this_type& other) = default;
|
||||
|
||||
data_type value() const {
|
||||
if (std::holds_alternative<data_type>(supplier_)) {
|
||||
return std::get<data_type>(supplier_);
|
||||
} else {
|
||||
std::vector<T> data;
|
||||
std::get<visitor_type>(supplier_)([&](auto const& x) {
|
||||
data.push_back(x);
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Visitor>
|
||||
void visit(Visitor&& visitor) const {
|
||||
if (std::holds_alternative<data_type>(supplier_)) {
|
||||
auto const& data = std::get<data_type>(supplier_);
|
||||
for (auto const& x : data)
|
||||
visitor(x);
|
||||
} else {
|
||||
std::get<visitor_type>(supplier_)(std::forward<Visitor>(visitor));
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t size() const {
|
||||
std::size_t result = 0;
|
||||
visit([&](auto const&) {
|
||||
result++;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool read_json(json::JsonReader& reader, this_type& value) {
|
||||
auto next = reader.peekToken();
|
||||
if (!next.has_value())
|
||||
return false;
|
||||
|
||||
if (std::holds_alternative<json::NullType>(next.value())) {
|
||||
value.supplier_ = data_type {};
|
||||
} else {
|
||||
data_type data;
|
||||
json::ReadValue<data_type>::read_json(reader, data);
|
||||
value.supplier_ = std::move(data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void write_json(json::JsonWriter& writer, this_type const& value) {
|
||||
if (std::holds_alternative<data_type>(value.supplier_)) {
|
||||
json::WriteValue<data_type>::write_json(writer, std::get<data_type>(value.supplier_));
|
||||
} else {
|
||||
writer.StartArray();
|
||||
std::get<visitor_type>(value.supplier_)([&](auto const& element) {
|
||||
json::WriteValue<T>::write_json(writer, element);
|
||||
});
|
||||
writer.EndArray();
|
||||
}
|
||||
}
|
||||
|
||||
static bool include_field(this_type const& value) {
|
||||
return !Optional || value.size() > 0;
|
||||
}
|
||||
|
||||
static bool is_required() {
|
||||
return !Optional;
|
||||
}
|
||||
|
||||
static bool validate(this_type const& value) {
|
||||
auto const size = value.size();
|
||||
if (MinElements >= 0) {
|
||||
if (size < MinElements)
|
||||
return false;
|
||||
}
|
||||
if (MaxElements >= 0) {
|
||||
if (size > MaxElements)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
value.visit([&](auto const& element) {
|
||||
if (!valid)
|
||||
return;
|
||||
|
||||
valid = json::Validate<T>::validate(element);
|
||||
});
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
friend bool operator==(this_type const& lhs, this_type const& rhs) {
|
||||
auto const& lhs_data = lhs.value();
|
||||
auto const& rhs_data = rhs.value();
|
||||
if (lhs_data.size() != rhs_data.size())
|
||||
return false;
|
||||
|
||||
for (auto it_lhs=lhs_data.begin(), it_rhs=rhs_data.begin(); it_lhs != lhs_data.end(); ++it_lhs, ++it_rhs) {
|
||||
if (*it_lhs != *it_rhs)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
friend bool operator!=(this_type const& lhs, this_type const& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<data_type, visitor_type> supplier_ = data_type {};
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_VECTOR_H
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_REQUEST_HANDLER_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_REQUEST_HANDLER_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/messages/authorize.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/boot_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/cancel_reservation.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/change_availability.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/change_configuration.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/clear_cache.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/clear_charging_profile.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/data_transfer.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/diagnostics_status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/firmware_status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_composite_schedule.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_configuration.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_diagnostics.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_local_list_version.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/heartbeat.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/meter_values.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/remote_start_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/remote_stop_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/reserve_now.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/reset.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/send_local_list.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/start_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/stop_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/trigger_message.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/unlock_connector.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/update_firmware.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_remote.h"
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
class AbstractRequestHandler {
|
||||
public:
|
||||
#define CHARGELAB_REQUEST_HANDLER_TEMPLATE(type) \
|
||||
virtual std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::type ## Rsp>> on ## type ## Req(ocpp1_6::type ## Req const&) { \
|
||||
return std::nullopt; \
|
||||
}
|
||||
CHARGELAB_PASTE(CHARGELAB_REQUEST_HANDLER_TEMPLATE, CHARGELAB_OCPP_1_6_ACTION_IDS)
|
||||
#undef CHARGELAB_REQUEST_HANDLER_TEMPLATE
|
||||
|
||||
virtual std::optional<ocpp1_6::ResponseToRequest<common::RawJson>> onCall(
|
||||
ocpp1_6::ActionId const& actionId,
|
||||
common::RawJson const& payload
|
||||
) {
|
||||
(void)actionId;
|
||||
(void)payload;
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_REQUEST_HANDLER_H
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_RESPONSE_HANDLER_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_RESPONSE_HANDLER_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/messages/authorize.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/boot_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/cancel_reservation.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/change_availability.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/change_configuration.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/clear_cache.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/clear_charging_profile.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/data_transfer.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/diagnostics_status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/firmware_status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_composite_schedule.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_configuration.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_diagnostics.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_local_list_version.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/heartbeat.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/meter_values.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/remote_start_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/remote_stop_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/reserve_now.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/reset.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/send_local_list.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/start_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/stop_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/trigger_message.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/unlock_connector.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/update_firmware.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
class AbstractResponseHandler {
|
||||
public:
|
||||
#define CHARGELAB_RESPONSE_HANDLER_TEMPLATE(type) \
|
||||
virtual void on ## type ## Rsp(std::string const&, ocpp1_6::ResponseMessage<type ## Rsp> const&) {}
|
||||
CHARGELAB_PASTE(CHARGELAB_RESPONSE_HANDLER_TEMPLATE, CHARGELAB_OCPP_1_6_ACTION_IDS)
|
||||
#undef CHARGELAB_RESPONSE_HANDLER_TEMPLATE
|
||||
|
||||
virtual void onCallRsp(std::string const&, ocpp1_6::ResponseMessage<common::RawJson> const&) {}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_RESPONSE_HANDLER_H
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_SERVICE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_SERVICE_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_remote.h"
|
||||
#include "openocpp/protocol/common/raw_json.h"
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
class AbstractService {
|
||||
public:
|
||||
virtual void runStep(OcppRemote& remote) {
|
||||
(void)remote;
|
||||
};
|
||||
|
||||
virtual void onUnmanagedMessage(std::string const& message) {
|
||||
(void)message;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_SERVICE_H
|
||||
@@ -0,0 +1,537 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_MESSAGE_HANDLER_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_MESSAGE_HANDLER_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_service.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_request_handler.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/abstract_response_handler.h"
|
||||
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_remote.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/message_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/error_code.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
|
||||
#include "openocpp/interface/element/websocket_interface.h"
|
||||
#include "openocpp/interface/component/system_interface.h"
|
||||
#include "openocpp/module/abstract_module.h"
|
||||
#include "openocpp/common/logging.h"
|
||||
#include "openocpp/common/ring_buffer.h"
|
||||
#include "openocpp/helpers/string.h"
|
||||
#include "openocpp/common/settings.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <random>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
namespace detail {
|
||||
struct PendingCall {
|
||||
std::string unique_id;
|
||||
ActionId action_id;
|
||||
SteadyPointMillis timestamp;
|
||||
|
||||
CHARGELAB_JSON_INTRUSIVE(PendingCall, unique_id, action_id, timestamp)
|
||||
};
|
||||
}
|
||||
|
||||
class OcppMessageHandler {
|
||||
private:
|
||||
static constexpr const int kMaxMessageProcessedPerStep = 4;
|
||||
|
||||
public:
|
||||
OcppMessageHandler(
|
||||
std::shared_ptr<Settings> settings,
|
||||
std::shared_ptr<SystemInterface> system,
|
||||
std::vector<std::shared_ptr<AbstractModuleInterface>> modules,
|
||||
std::function<bool()> registrationComplete
|
||||
)
|
||||
: settings_(std::move(settings)),
|
||||
system_(std::move(system)),
|
||||
modules_(std::move(modules)),
|
||||
registration_complete_(std::move(registrationComplete))
|
||||
{
|
||||
updatePointers();
|
||||
}
|
||||
|
||||
void runStep(std::shared_ptr<WebsocketInterface> const& websocket) {
|
||||
for (auto& x : pure_services_)
|
||||
x->runUnconditionally();
|
||||
|
||||
if (websocket == nullptr) {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected null websocket pointer provided";
|
||||
return;
|
||||
}
|
||||
|
||||
auto const subprotocol = websocket->getSubprotocol();
|
||||
if (!subprotocol.has_value() || !string::EqualsIgnoreCaseAscii(subprotocol.value(), "ocpp1.6")) {
|
||||
CHARGELAB_LOG_MESSAGE(trace) << "Skipping OCPP 1.6 message handler based on websocket subprotocol: " << subprotocol;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0; i < kMaxMessageProcessedPerStep; i++) {
|
||||
auto message = websocket->pollMessages();
|
||||
if (!message.has_value())
|
||||
break;
|
||||
|
||||
CHARGELAB_TRY {
|
||||
onMessage(*websocket, message.value());
|
||||
} CHARGELAB_CATCH {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Unexpected failed processing message: " << e.what();
|
||||
dispatchUnexpectedMessage(message.value());
|
||||
}
|
||||
}
|
||||
|
||||
OcppRemote remote {
|
||||
*websocket,
|
||||
registration_complete_,
|
||||
[this](std::string const& unique_id, ActionId const& action) {
|
||||
auto const now = system_->steadyClockNow();
|
||||
if (last_call_.has_value()) {
|
||||
auto const elapsed_seconds = (now - last_call_->timestamp)/1000;
|
||||
if (elapsed_seconds < settings_->DefaultMessageTimeout.getValue()) {
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Call already in process - blocking call '" << action << "' waiting for: " << last_call_;
|
||||
return false;
|
||||
}
|
||||
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Call already in process but timeout elapsed - allowing call '" << action << "' waiting for: " << last_call_;
|
||||
}
|
||||
|
||||
last_call_ = detail::PendingCall {unique_id, action, now};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto& x : services_)
|
||||
x->runStep(remote);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
void addAfter(std::shared_ptr<T> module, Args&&... args) {
|
||||
auto index = getMaxIndex(std::forward<Args>(args)...);
|
||||
if (index.has_value()) {
|
||||
modules_.insert(modules_.begin()+index.value()+1, std::move(module));
|
||||
} else {
|
||||
modules_.push_back(std::move(module));
|
||||
}
|
||||
|
||||
updatePointers();
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
void addBefore(std::shared_ptr<T> module, Args&&... args) {
|
||||
auto index = getMinIndex(std::forward<Args>(args)...);
|
||||
if (index.has_value()) {
|
||||
modules_.insert(modules_.begin()+index.value(), std::move(module));
|
||||
} else {
|
||||
modules_.push_back(std::move(module));
|
||||
}
|
||||
|
||||
updatePointers();
|
||||
}
|
||||
|
||||
private:
|
||||
void updatePointers() {
|
||||
services_.clear();
|
||||
request_handlers_.clear();
|
||||
response_handlers_.clear();
|
||||
pure_services_.clear();
|
||||
|
||||
for (auto const& x : modules_) {
|
||||
if (x != nullptr) {
|
||||
auto implementations = x->getImplementations();
|
||||
addIfNotNull(services_, implementations.ocpp1_6.service);
|
||||
addIfNotNull(request_handlers_, implementations.ocpp1_6.request_handler);
|
||||
addIfNotNull(response_handlers_, implementations.ocpp1_6.response_handler);
|
||||
addIfNotNull(pure_services_, implementations.pure_service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::size_t> getMinIndex() {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename Head, typename... Args>
|
||||
std::optional<std::size_t> getMinIndex(Head&& head, Args&&... args) {
|
||||
auto const next = getMinIndex(std::forward<Args>(args)...);
|
||||
for (std::size_t i=0; i < modules_.size(); i++) {
|
||||
if (head == modules_[i]) {
|
||||
if (next.has_value()) {
|
||||
return std::min(i, next.value());
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> getMaxIndex() {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename Head, typename... Args>
|
||||
std::optional<std::size_t> getMaxIndex(Head&& head, Args&&... args) {
|
||||
auto const next = getMaxIndex(std::forward<Args>(args)...);
|
||||
for (std::size_t i=0; i < modules_.size(); i++) {
|
||||
if (head == modules_[i]) {
|
||||
if (next.has_value()) {
|
||||
return std::max(i, next.value());
|
||||
} else {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
private:
|
||||
void onMessage(WebsocketInterface& websocket, std::string const& message) {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Received OCPP message: " << message;
|
||||
|
||||
stream::StringReader stream {message};
|
||||
json::JsonReader reader {stream};
|
||||
if (!json::expect_type<json::StartArrayType>(reader)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - expected array: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageType message_type;
|
||||
if (!json::ReadValue<MessageType>::read_json(reader, message_type)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad message type: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message_type) {
|
||||
case MessageType::kValueNotFoundInEnum:
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
|
||||
case MessageType::kCall:
|
||||
{
|
||||
std::string unique_id;
|
||||
if (!json::ReadValue<std::string>::read_json(reader, unique_id)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad unique ID: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
ActionId action_id;
|
||||
if (!json::ReadValue<ActionId>::read_json(reader, action_id)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad action ID: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action_id) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - bad action ID: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
|
||||
#define CHARGELAB_ON_CALL_IMPL(TYPE) \
|
||||
case ActionId::k ## TYPE: \
|
||||
{ \
|
||||
TYPE ## Req parsed {}; \
|
||||
if (!json::ReadValue<TYPE ## Req>::read_json(reader, parsed)) { \
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed deserializing message payload: " << message; \
|
||||
dispatchUnexpectedMessage(message); \
|
||||
break; \
|
||||
} \
|
||||
\
|
||||
dispatchCall( \
|
||||
websocket, \
|
||||
action_id, \
|
||||
unique_id, \
|
||||
common::RawJson::from_value_lazy(parsed), \
|
||||
[]( \
|
||||
AbstractRequestHandler* handler, \
|
||||
bool first, \
|
||||
TYPE ## Req const& req, \
|
||||
WebsocketInterface& websocket, \
|
||||
std::string const& unique_id \
|
||||
) { \
|
||||
auto const response = handler->on ## TYPE ## Req(req); \
|
||||
if (response.has_value()) { \
|
||||
if (first) { \
|
||||
sendResponse(websocket, unique_id, response.value()); \
|
||||
} \
|
||||
\
|
||||
return true; \
|
||||
} else { \
|
||||
return false; \
|
||||
} \
|
||||
}, \
|
||||
parsed, \
|
||||
websocket, \
|
||||
unique_id \
|
||||
); \
|
||||
break; \
|
||||
}
|
||||
CHARGELAB_PASTE(CHARGELAB_ON_CALL_IMPL, CHARGELAB_OCPP_1_6_ACTION_IDS)
|
||||
#undef CHARGELAB_ON_CALL_IMPL
|
||||
}
|
||||
|
||||
if (!json::expect_type<json::EndArrayType>(reader))
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected elements in OCPP call: " << message;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
case MessageType::kCallResult:
|
||||
{
|
||||
std::string unique_id;
|
||||
if (!json::ReadValue<std::string>::read_json(reader, unique_id)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad unique ID: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<ActionId> action_id;
|
||||
if (last_call_.has_value() && string::EqualsIgnoreCaseAscii(last_call_->unique_id, unique_id)) {
|
||||
action_id = last_call_->action_id;
|
||||
last_call_ = std::nullopt;
|
||||
}
|
||||
|
||||
if (!action_id.has_value()) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Response ID not found in pending call list - treating as unexpected message: " << unique_id;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action_id.value()) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - bad action ID: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
|
||||
#define CHARGELAB_ON_CALL_RESPONSE_IMPL(TYPE) \
|
||||
case ActionId::k ## TYPE: \
|
||||
{ \
|
||||
TYPE ## Rsp parsed {}; \
|
||||
if (!json::ReadValue<TYPE ## Rsp>::read_json(reader, parsed)) { \
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed deserializing message payload: " << message; \
|
||||
dispatchUnexpectedMessage(message); \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
for (auto& ptr : response_handlers_) { \
|
||||
if (ptr != nullptr) { \
|
||||
ptr->on ## TYPE ## Rsp(unique_id, parsed); \
|
||||
ptr->onCallRsp(unique_id, common::RawJson::from_value_lazy(parsed)); \
|
||||
} \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
CHARGELAB_PASTE(CHARGELAB_ON_CALL_RESPONSE_IMPL, CHARGELAB_OCPP_1_6_ACTION_IDS)
|
||||
#undef CHARGELAB_ON_CALL_RESPONSE_IMPL
|
||||
}
|
||||
|
||||
// Note: ignoring and allowing
|
||||
if (!json::expect_type<json::EndArrayType>(reader))
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected elements in OCPP call: " << message;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
case MessageType::kCallError:
|
||||
{
|
||||
std::string unique_id;
|
||||
if (!json::ReadValue<std::string>::read_json(reader, unique_id)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad unique ID: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode error_code;
|
||||
if (!json::ReadValue<ErrorCode>::read_json(reader, error_code)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad error code: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string description;
|
||||
if (!json::ReadValue<std::string>::read_json(reader, description)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad description: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
common::RawJson details;
|
||||
if (!json::ReadValue<common::RawJson>::read_json(reader, details)) {
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad details: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: ignoring and allowing
|
||||
if (!json::expect_type<json::EndArrayType>(reader))
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected elements in OCPP call: " << message;
|
||||
|
||||
auto const error = CallError {error_code, description, details};
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Received OCPP error: id=" << unique_id << ", error=" << error;
|
||||
|
||||
std::optional<ActionId> action_id;
|
||||
if (last_call_.has_value() && string::EqualsIgnoreCaseAscii(last_call_->unique_id, unique_id)) {
|
||||
action_id = last_call_->action_id;
|
||||
last_call_ = std::nullopt;
|
||||
}
|
||||
|
||||
if (!action_id.has_value()) {
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action_id.value()) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - bad action ID: " << message;
|
||||
dispatchUnexpectedMessage(message);
|
||||
return;
|
||||
|
||||
#define CHARGELAB_ON_CALL_ERROR_IMPL(TYPE) \
|
||||
case ActionId::k ## TYPE: \
|
||||
{ \
|
||||
for (auto& ptr : response_handlers_) { \
|
||||
if (ptr != nullptr) { \
|
||||
ptr->on ## TYPE ## Rsp(unique_id, error); \
|
||||
} \
|
||||
} \
|
||||
break; \
|
||||
}
|
||||
CHARGELAB_PASTE(CHARGELAB_ON_CALL_ERROR_IMPL, CHARGELAB_OCPP_1_6_ACTION_IDS)
|
||||
#undef CHARGELAB_ON_CALL_ERROR_IMPL
|
||||
}
|
||||
|
||||
for (auto& ptr : response_handlers_) {
|
||||
if (ptr != nullptr) {
|
||||
ptr->onCallRsp(unique_id, error);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void sendResponse(WebsocketInterface& websocket, std::string const& unique_id, ResponseToRequest<T> const& result) {
|
||||
if (std::holds_alternative<T>(result)) {
|
||||
websocket.sendCustom([&](ByteWriterInterface& stream) {
|
||||
json::JsonWriter writer {stream};
|
||||
writer.StartArray();
|
||||
writer.Int((int)MessageType::kCallResult);
|
||||
writer.String(unique_id);
|
||||
json::WriteValue<T>::write_json(writer, std::get<T>(result));
|
||||
writer.EndArray();
|
||||
});
|
||||
} else if (std::holds_alternative<CallError>(result)) {
|
||||
sendError(websocket, unique_id, std::get<CallError>(result));
|
||||
} else {
|
||||
std::get<CustomResponse>(result)(websocket, unique_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void sendError(WebsocketInterface& websocket, std::string const& unique_id, CallError const& error) {
|
||||
websocket.sendCustom([&](ByteWriterInterface& stream) {
|
||||
json::JsonWriter writer {stream};
|
||||
writer.StartArray();
|
||||
writer.Int((int)MessageType::kCallError);
|
||||
writer.String(unique_id);
|
||||
json::WriteValue<ErrorCode>::write_json(writer, error.code);
|
||||
writer.String(error.description);
|
||||
json::WriteValue<common::RawJson>::write_json(writer, error.details);
|
||||
writer.EndArray();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
template <typename F, typename... Args>
|
||||
void dispatchCall(
|
||||
WebsocketInterface& websocket,
|
||||
ActionId const& action_id,
|
||||
std::string const& unique_id,
|
||||
common::RawJson const& payload,
|
||||
F&& function,
|
||||
Args&&... args
|
||||
) {
|
||||
bool responded = false;
|
||||
for (auto& ptr : request_handlers_) {
|
||||
if (ptr != nullptr) {
|
||||
if (!responded) {
|
||||
responded = function(ptr, true, std::forward<Args>(args)...);
|
||||
} else if (allowFallthrough(action_id)) {
|
||||
function(ptr, false, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
if (!responded) {
|
||||
auto response = ptr->onCall(action_id, payload);
|
||||
if (response.has_value()) {
|
||||
sendResponse(websocket, unique_id, response.value());
|
||||
responded = true;
|
||||
}
|
||||
} else if (allowFallthrough(action_id)) {
|
||||
ptr->onCall(action_id, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!responded) {
|
||||
sendError(websocket, unique_id, CallError {
|
||||
ErrorCode::kNotImplemented,
|
||||
"Not implemented",
|
||||
common::RawJson::empty_object()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool allowFallthrough(ActionId const& action_id) {
|
||||
switch (action_id) {
|
||||
default:
|
||||
return false;
|
||||
|
||||
case ActionId::kTriggerMessage:
|
||||
case ActionId::kChangeConfiguration:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchUnexpectedMessage(std::string const& message) {
|
||||
CHARGELAB_LOG_MESSAGE(debug) << "Dispatching unexpected message: " << message;
|
||||
|
||||
for (auto& ptr : services_) {
|
||||
if (ptr != nullptr)
|
||||
ptr->onUnmanagedMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void addIfNotNull(std::vector<T*>& container, T* x) {
|
||||
if (x != nullptr) {
|
||||
container.push_back(x);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Settings> settings_;
|
||||
std::shared_ptr<SystemInterface> system_;
|
||||
std::vector<std::shared_ptr<AbstractModuleInterface>> modules_;
|
||||
std::function<bool()> registration_complete_;
|
||||
|
||||
std::vector<std::shared_ptr<void>> wrappers_;
|
||||
std::vector<AbstractService*> services_;
|
||||
std::vector<AbstractRequestHandler*> request_handlers_;
|
||||
std::vector<AbstractResponseHandler*> response_handlers_;
|
||||
std::vector<chargelab::detail::PureServiceInterface*> pure_services_;
|
||||
|
||||
std::optional<detail::PendingCall> last_call_ = std::nullopt;
|
||||
};
|
||||
}
|
||||
|
||||
#undef CHARGELAB_MERGE_NAMES
|
||||
#undef CHARGELAB_CALL_FUNCTION_TEMPLATE
|
||||
#undef CHARGELAB_CALL_RESPONSE_TEMPLATE
|
||||
#undef CHARGELAB_CALL_ERROR_TEMPLATE
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_MESSAGE_HANDLER_H
|
||||
@@ -0,0 +1,172 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_REMOTE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_REMOTE_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/messages/authorize.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/boot_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/cancel_reservation.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/change_availability.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/change_configuration.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/clear_cache.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/clear_charging_profile.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/data_transfer.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/diagnostics_status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/firmware_status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_composite_schedule.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_configuration.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_diagnostics.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/get_local_list_version.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/heartbeat.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/meter_values.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/remote_start_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/remote_stop_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/reserve_now.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/reset.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/send_local_list.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/start_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/status_notification.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/stop_transaction.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/trigger_message.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/unlock_connector.h"
|
||||
#include "openocpp/protocol/ocpp1_6/messages/update_firmware.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/message_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/interface/element/websocket_interface.h"
|
||||
|
||||
#include <utility>
|
||||
#include <random>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
class OcppRemote {
|
||||
private:
|
||||
using OnManagedCall = std::function<bool(std::string const&, ActionId const&)>;
|
||||
|
||||
public:
|
||||
explicit OcppRemote(
|
||||
WebsocketInterface& websocket_interface,
|
||||
std::function<bool()> registration_complete,
|
||||
OnManagedCall on_managed_message
|
||||
)
|
||||
: websocket_interface_(websocket_interface),
|
||||
registration_complete_(std::move(registration_complete)),
|
||||
on_managed_message_(std::move(on_managed_message))
|
||||
{
|
||||
std::random_device random_device;
|
||||
std::default_random_engine random_engine {random_device()};
|
||||
std::uniform_int_distribution<unsigned long> distribution(
|
||||
std::numeric_limits<unsigned long>::min(),
|
||||
std::numeric_limits<unsigned long>::max()
|
||||
);
|
||||
request_id_ = distribution(random_engine);
|
||||
}
|
||||
|
||||
public:
|
||||
#define CHARGELAB_REQUEST_HANDLER_TEMPLATE(type) \
|
||||
std::optional<std::string> send ## type ## Req(const type ## Req &req) { \
|
||||
return executeCall(ActionId::k ## type, req); \
|
||||
}
|
||||
CHARGELAB_PASTE(CHARGELAB_REQUEST_HANDLER_TEMPLATE, CHARGELAB_OCPP_1_6_ACTION_IDS)
|
||||
#undef CHARGELAB_REQUEST_HANDLER_TEMPLATE
|
||||
|
||||
template <typename T>
|
||||
std::optional<std::string> sendCall(T const& req) {
|
||||
return executeCall(T::kActionId, req);
|
||||
}
|
||||
|
||||
bool sendCall(std::string const& unique_id, ActionId const& action, std::string const& payload) {
|
||||
if (!websocket_interface_.isConnected())
|
||||
return false;
|
||||
|
||||
if (!registration_complete_()) {
|
||||
switch (action) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Registration not complete - blocking call: " << action;
|
||||
return false;
|
||||
|
||||
// Allow these requests to be sent while registration is pending
|
||||
case ActionId::kBootNotification:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!on_managed_message_(unique_id, action))
|
||||
return false;
|
||||
|
||||
websocket_interface_.sendCustom([&](ByteWriterInterface& stream) {
|
||||
json::JsonWriter writer {stream};
|
||||
writer.StartArray();
|
||||
writer.Int((int)MessageType::kCall);
|
||||
writer.String(unique_id);
|
||||
writer.String(action.to_string()),
|
||||
json::WriteValue<common::RawJson>::write_json(writer, {payload});
|
||||
writer.EndArray();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool sendUnmanagedMessage(std::function<void(ByteWriterInterface&)> payload) {
|
||||
if (!websocket_interface_.isConnected())
|
||||
return false;
|
||||
|
||||
websocket_interface_.sendCustom(std::move(payload));
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long getRequestId() {
|
||||
return request_id_++;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
std::optional<std::string> executeCall(ActionId const& action, T const& payload) {
|
||||
if (!websocket_interface_.isConnected())
|
||||
return std::nullopt;
|
||||
|
||||
if (!registration_complete_()) {
|
||||
switch (action) {
|
||||
default:
|
||||
CHARGELAB_LOG_MESSAGE(info) << "Registration not complete - blocking call: " << action;
|
||||
return std::nullopt;
|
||||
|
||||
// Allow these requests to be sent while registration is pending
|
||||
case ActionId::kBootNotification:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CHARGELAB_TRY {
|
||||
auto unique_id = std::to_string(request_id_++);
|
||||
if (!on_managed_message_(unique_id, action))
|
||||
return std::nullopt;
|
||||
|
||||
websocket_interface_.sendCustom([&](ByteWriterInterface& stream) {
|
||||
json::JsonWriter writer {stream};
|
||||
writer.StartArray();
|
||||
writer.Int((int)MessageType::kCall);
|
||||
writer.String(unique_id);
|
||||
writer.String(action.to_string()),
|
||||
json::WriteValue<T>::write_json(writer, payload);
|
||||
writer.EndArray();
|
||||
});
|
||||
|
||||
return unique_id;
|
||||
} CHARGELAB_CATCH {
|
||||
CHARGELAB_LOG_MESSAGE(error) << "Failed sending call with: " << e.what();
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
WebsocketInterface& websocket_interface_;
|
||||
std::function<bool()> registration_complete_;
|
||||
OnManagedCall on_managed_message_;
|
||||
|
||||
std::atomic<unsigned long> request_id_;
|
||||
};
|
||||
}
|
||||
|
||||
#undef CHARGELAB_MERGE_NAMES
|
||||
#undef CHARGELAB_REQUEST_HANDLER_TEMPLATE
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_REMOTE_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZE_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/id_token.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/id_tag_info.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct AuthorizeReq {
|
||||
IdToken idTag;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(AuthorizeReq, kAuthorize, idTag)
|
||||
};
|
||||
|
||||
struct AuthorizeRsp {
|
||||
IdTagInfo idTagInfo;
|
||||
CHARGELAB_JSON_INTRUSIVE(AuthorizeRsp, idTagInfo)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZE_H
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_BOOT_NOTIFICATION_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_BOOT_NOTIFICATION_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/registration_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct BootNotificationReq {
|
||||
std::optional<CiString25Type> chargeBoxSerialNumber = std::nullopt;
|
||||
CiString20Type chargePointModel {};
|
||||
std::optional<CiString25Type> chargePointSerialNumber = std::nullopt;
|
||||
CiString20Type chargePointVendor {};
|
||||
std::optional<CiString50Type> firmwareVersion = std::nullopt;
|
||||
std::optional<CiString20Type> iccid = std::nullopt;
|
||||
std::optional<CiString20Type> imsi = std::nullopt;
|
||||
std::optional<CiString25Type> meterSerialNumber = std::nullopt;
|
||||
std::optional<CiString25Type> meterType = std::nullopt;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(BootNotificationReq, kBootNotification, chargeBoxSerialNumber, chargePointModel, chargePointSerialNumber, chargePointVendor, firmwareVersion, iccid, imsi, meterSerialNumber, meterType)
|
||||
};
|
||||
|
||||
struct BootNotificationRsp {
|
||||
DateTime currentTime;
|
||||
int interval;
|
||||
RegistrationStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE(BootNotificationRsp, currentTime, interval, status)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_BOOT_NOTIFICATION_H
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CANCEL_RESERVATION_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_CANCEL_RESERVATION_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/cancel_reservation_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct CancelReservationReq {
|
||||
int reservationId;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(CancelReservationReq, kCancelReservation, reservationId)
|
||||
};
|
||||
|
||||
struct CancelReservationRsp {
|
||||
CancelReservationStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE(CancelReservationRsp, status)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CANCEL_RESERVATION_H
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_AVAILABILITY_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_AVAILABILITY_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/availability_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/availability_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct ChangeAvailabilityReq {
|
||||
int connectorId;
|
||||
AvailabilityType type;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(ChangeAvailabilityReq, kChangeAvailability, connectorId, type)
|
||||
};
|
||||
|
||||
struct ChangeAvailabilityRsp {
|
||||
AvailabilityStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE(ChangeAvailabilityRsp, status)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_AVAILABILITY_H
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_CONFIGURATION_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_CONFIGURATION_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/configuration_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct ChangeConfigurationReq {
|
||||
CiString50Type key;
|
||||
CiString500Type value;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(ChangeConfigurationReq, kChangeConfiguration, key, value)
|
||||
};
|
||||
|
||||
struct ChangeConfigurationRsp {
|
||||
ConfigurationStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE(ChangeConfigurationRsp, status)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_CONFIGURATION_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CACHE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CACHE_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/clear_cache_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct ClearCacheReq {
|
||||
CHARGELAB_JSON_INTRUSIVE_EMPTY_CALL(ClearCacheReq, kClearCache)
|
||||
};
|
||||
|
||||
struct ClearCacheRsp {
|
||||
ClearCacheStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE(ClearCacheRsp, status)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CACHE_H
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CHARGING_PROFILE_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CHARGING_PROFILE_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/charging_profile_purpose_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/clear_charging_profile_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct ClearChargingProfileReq {
|
||||
std::optional<int> id;
|
||||
std::optional<int> connectorId;
|
||||
std::optional<ChargingProfilePurposeType> chargingProfilePurpose;
|
||||
std::optional<int> stackLevel;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(ClearChargingProfileReq, kClearChargingProfile, id, connectorId, chargingProfilePurpose, stackLevel)
|
||||
};
|
||||
|
||||
struct ClearChargingProfileRsp {
|
||||
ClearChargingProfileStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE(ClearChargingProfileRsp, status)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CHARGING_PROFILE_H
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_DATA_TRANSFER_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_DATA_TRANSFER_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/data_transfer_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct DataTransferReq {
|
||||
CiString255Type vendorId;
|
||||
std::optional<CiString50Type> messageId;
|
||||
std::optional<std::string> data;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(DataTransferReq, kDataTransfer, vendorId, messageId, data)
|
||||
};
|
||||
|
||||
struct DataTransferRsp {
|
||||
DataTransferStatus status {};
|
||||
std::optional<std::string> data {};
|
||||
CHARGELAB_JSON_INTRUSIVE(DataTransferRsp, status, data)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_DATA_TRANSFER_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_DIAGNOSTICS_STATUS_NOTIFICATION_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_DIAGNOSTICS_STATUS_NOTIFICATION_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/diagnostics_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct DiagnosticsStatusNotificationReq {
|
||||
DiagnosticsStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(DiagnosticsStatusNotificationReq, kDiagnosticsStatusNotification, status)
|
||||
};
|
||||
|
||||
struct DiagnosticsStatusNotificationRsp {
|
||||
CHARGELAB_JSON_INTRUSIVE_EMPTY(DiagnosticsStatusNotificationRsp)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_DIAGNOSTICS_STATUS_NOTIFICATION_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_FIRMWARE_STATUS_NOTIFICATION_H
|
||||
#define CHARGELAB_OPEN_FIRMWARE_1_6_FIRMWARE_STATUS_NOTIFICATION_H
|
||||
|
||||
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
|
||||
#include "openocpp/protocol/ocpp1_6/types/firmware_status.h"
|
||||
#include "openocpp/helpers/json.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace chargelab::ocpp1_6 {
|
||||
struct FirmwareStatusNotificationReq {
|
||||
FirmwareStatus status;
|
||||
CHARGELAB_JSON_INTRUSIVE_CALL(FirmwareStatusNotificationReq, kFirmwareStatusNotification, status)
|
||||
};
|
||||
|
||||
struct FirmwareStatusNotificationRsp {
|
||||
CHARGELAB_JSON_INTRUSIVE_EMPTY(FirmwareStatusNotificationRsp)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_FIRMWARE_STATUS_NOTIFICATION_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user