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:
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
|
||||
Reference in New Issue
Block a user