diff --git a/README.md b/README.md index a9b8856..ed7657d 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ cp_simu | |-status |--cps | |-simu_cp_XXX + | | |-cmd | | |-status | | |-connectors | | | |-1 @@ -197,6 +198,7 @@ cp_simu | | | | |-id_tag | | | | |-status | |-simu_cp_YYY + | | |-cmd | | |-status | | |-connectors | | | |-1 @@ -348,4 +350,17 @@ The expected command payload is : { "faulted": false } - ``` \ No newline at end of file + ``` + + +Each simulated Charge Point are listening to the following topic to execute a certain command: **cp_simu/cps/simu_cp_XXX/cmd**. + +The expected command payload is : + ``` + { + "type": "" + } + ``` +So far there are 2 commands: +* close: ask to end the application +* ocpp_config: ask to send on MQTT topic **cp_simu/cps/simu_cp_XXX/ocpp_config** all the OCPP config of the Charge Point \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 6bc39bd..0849faa 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,7 +16,7 @@ RUN apt-get update && apt-get install build-essential clang cmake python3 python RUN git clone https://github.com/c-jimenez/open-ocpp.git -RUN cd open-ocpp && git fetch --tags && git checkout v1.1.0 && make gcc && make install-gcc +RUN cd open-ocpp && git fetch --tags && git checkout v1.2.0 && make gcc && make install-gcc RUN apt-get update && apt-get install pkg-config && apt-get -q autoremove && rm -rf "/var/lib/apt/lists/*" diff --git a/docker/Dockerfile_cp_simulator b/docker/Dockerfile_cp_simulator index 14aa548..6367673 100644 --- a/docker/Dockerfile_cp_simulator +++ b/docker/Dockerfile_cp_simulator @@ -14,9 +14,10 @@ COPY gcc_native/config.ini /var/chargepoint/config.ini RUN chown ${simu_uid}:${simu_gid} /var/chargepoint/config.ini COPY gcc_native /cp_simulator +COPY cp_simu_entrypoint.sh /cp_simu_entrypoint.sh USER cp_simu WORKDIR "/var/chargepoint" -ENTRYPOINT ["/cp_simulator/chargepoint"] \ No newline at end of file +ENTRYPOINT ["/cp_simu_entrypoint.sh"] \ No newline at end of file diff --git a/docker/Entrypoint/cp_simu_entrypoint.sh b/docker/Entrypoint/cp_simu_entrypoint.sh new file mode 100755 index 0000000..16c09c0 --- /dev/null +++ b/docker/Entrypoint/cp_simu_entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +cp_args="" +diagnostic_file_size=0 + +for i in "$@"; do + case $i in + --diagnostic_file_size*) + diagnostic_file_size="$2" + shift + ;; + -*) + cp_args+=" $i $2" + shift + ;; + *) + shift + ;; + esac +done + +# create big file and add it in diagnostic zip of charge point +if [ $diagnostic_file_size -gt 0 ] +then + echo "generate $diagnostic_file_size random strings in cp config file" + echo "$(tr -dc A-Za-z0-9 < /dev/urandom | head -c $diagnostic_file_size)" >> /var/chargepoint/blob.txt + cp_args+=" -f blob.txt" +fi + +# start simulator +echo "start charpoint with arguments : $cp_args" +/cp_simulator/chargepoint $cp_args diff --git a/imgs/connect.png b/imgs/connect.png index 216b947..d0ec08c 100755 Binary files a/imgs/connect.png and b/imgs/connect.png differ diff --git a/imgs/instances.png b/imgs/instances.png index 4e311ed..9abe288 100755 Binary files a/imgs/instances.png and b/imgs/instances.png differ diff --git a/makefile b/makefile index 0902e4b..f947b92 100644 --- a/makefile +++ b/makefile @@ -104,11 +104,12 @@ docker-build-simu-compile: @${DOCKER_BUILD} -f docker/Dockerfile -t $(DOCKER_COMPILE_IMAGE) $(ROOT_DIR)/docker docker-build-cp-simulator: + cp docker/Entrypoint/cp_simu_entrypoint.sh $(ROOT_DIR)/bin/ @${DOCKER_BUILD} -f docker/Dockerfile_cp_simulator -t $(DOCKER_SIMULATOR_IMAGE) $(ROOT_DIR)/bin/ run-simu: - docker run $(DOCKER_INTERACTIVE) --rm --network=host --name ocpp-simu $(DOCKER_SIMULATOR_IMAGE) -c test_ocpp + docker run $(DOCKER_INTERACTIVE) --rm --network=host --name ocpp-simu $(DOCKER_SIMULATOR_IMAGE) run-launcher: docker run $(DOCKER_INTERACTIVE) --rm --network=host --name ocpp-launcher --entrypoint /cp_simulator/launcher $(DOCKER_SIMULATOR_IMAGE) diff --git a/src/chargepoint/Version.h b/src/chargepoint/Version.h index 34f7d10..fb9854b 100644 --- a/src/chargepoint/Version.h +++ b/src/chargepoint/Version.h @@ -26,6 +26,6 @@ SOFTWARE. #define VERSION_H /** @brief Firmware version of the simulated charge point */ -#define CHARGEPOINT_FW_VERSION "1.0.0" +#define CHARGEPOINT_FW_VERSION "1.1.0" #endif // VERSION_H diff --git a/src/chargepoint/config/SimulatedChargePointConfig.h b/src/chargepoint/config/SimulatedChargePointConfig.h index 5f8ee42..8734b7b 100644 --- a/src/chargepoint/config/SimulatedChargePointConfig.h +++ b/src/chargepoint/config/SimulatedChargePointConfig.h @@ -30,20 +30,29 @@ SOFTWARE. #include "OcppConfig.h" #include +#include /** @brief Configuration of the simulated charge point */ class SimulatedChargePointConfig { public: /** @brief Constructor */ - SimulatedChargePointConfig(const std::string& working_dir, const std::string& config_file) - : m_working_dir(working_dir), m_config(config_file), m_stack_config(m_config), m_ocpp_config(m_config), m_mqtt_config(m_config) + SimulatedChargePointConfig(const std::string& working_dir, const std::string& config_file, std::set& diag_files) + : m_working_dir(working_dir), + m_config(config_file), + m_diag_files(diag_files), + m_stack_config(m_config), + m_ocpp_config(m_config), + m_mqtt_config(m_config) { } /** @brief Working directory */ const std::string& workingDir() const { return m_working_dir; } + /** @brief files to put diagnostic zip */ + const std::set& diagFiles() const { return m_diag_files; } + /** @brief Stack internal configuration */ ocpp::config::IChargePointConfig& stackConfig() { return m_stack_config; } @@ -67,6 +76,8 @@ class SimulatedChargePointConfig std::string m_working_dir; /** @brief Configuration file */ ocpp::helpers::IniFile m_config; + /** @brief files to put diagnostic zip */ + std::set m_diag_files; /** @brief Stack internal configuration */ ChargePointConfig m_stack_config; diff --git a/src/chargepoint/main.cpp b/src/chargepoint/main.cpp index 87c9c7d..f0c3d31 100644 --- a/src/chargepoint/main.cpp +++ b/src/chargepoint/main.cpp @@ -33,6 +33,7 @@ SOFTWARE. #include #include #include +#include using namespace std; @@ -73,6 +74,7 @@ int main(int argc, char* argv[]) std::string mqtt_broker_url = "tcp://localhost:1883"; unsigned int max_charge_point_current = 32u; unsigned int max_connector_current = 32u; + std::set diag_files = {"ocpp.db"}; // Check parameters if (argc > 1) @@ -140,6 +142,19 @@ int main(int argc, char* argv[]) argc--; max_connector_current = static_cast(std::atoi(*argv)); } + else if ((strcmp(*argv, "-f") == 0) && (argc > 1)) + { + argv++; + argc--; + // add all files in diag file list: + diag_files.insert(*argv); + while((argc > 2) && (*argv[1] != '-')) + { + argv++; + argc--; + diag_files.insert(*argv); + } + } else { param = *argv; @@ -168,6 +183,8 @@ int main(int argc, char* argv[]) std::cout << " -b : URL of the MQTT broker (Default = tcp://localhost:1883)" << std::endl; std::cout << " -m : Max current in A for the whole Charge Point (Default = 32A)" << std::endl; std::cout << " -i : Max current in A for a connector of the Charge Point (Default = 32A)" << std::endl; + std::cout << " -f : Files to put in diagnostic zip. Absolute path or relative path from working directory. " << std::endl; + std::cout << "Default = [ocpp.db]" << std::endl; return 1; } } @@ -175,7 +192,7 @@ int main(int argc, char* argv[]) // Open configuration file std::filesystem::path path(working_dir); path /= "config.ini"; - SimulatedChargePointConfig config(working_dir, path.string()); + SimulatedChargePointConfig config(working_dir, path.string(), diag_files); // Update configuration file std::filesystem::path db_path(working_dir); diff --git a/src/chargepoint/mqtt/MqttManager.cpp b/src/chargepoint/mqtt/MqttManager.cpp index 018f092..c2de084 100644 --- a/src/chargepoint/mqtt/MqttManager.cpp +++ b/src/chargepoint/mqtt/MqttManager.cpp @@ -23,14 +23,14 @@ SOFTWARE. */ #include "MqttManager.h" +#include "MeterSimulator.h" #include "SimulatedChargePointConfig.h" #include "Topics.h" -#include "MeterSimulator.h" -#include #include #include #include +#include #include #include @@ -99,6 +99,10 @@ void MqttManager::mqttMessageReceived(const char* topic, const std::string& mess std::cout << "Close command received" << std::endl; m_end = true; } + else if (strcmp(type, "ocpp_config") == 0) + { + publishOcppConfig(); + } } else { @@ -205,6 +209,7 @@ void MqttManager::start(unsigned int nb_phases, unsigned int max_charge_point_cu std::string chargepoint_tag_topics = chargepoint_topic + "connectors/+/id_tag"; std::string chargepoint_faulted_topics = chargepoint_topic + "connectors/+/faulted"; m_status_topic = chargepoint_topic + "status"; + m_ocpp_config_topic = chargepoint_topic + "ocpp_config"; m_connectors_topic = chargepoint_topic + "connectors/"; // MQTT client @@ -225,8 +230,10 @@ void MqttManager::start(unsigned int nb_phases, unsigned int max_charge_point_cu std::cout << "Subscribing to charge point's command topic: " << chargepoint_cmd_topic << std::endl; if (m_mqtt->subscribe(chargepoint_cmd_topic)) { - std::cout << "Subscribing to charge point's connector topics: " << chargepoint_car_topics << " and " << chargepoint_tag_topics << " and " << chargepoint_faulted_topics << std::endl; - if (m_mqtt->subscribe(chargepoint_car_topics) && m_mqtt->subscribe(chargepoint_tag_topics) && m_mqtt->subscribe(chargepoint_faulted_topics)) + std::cout << "Subscribing to charge point's connector topics: " << chargepoint_car_topics << " and " + << chargepoint_tag_topics << " and " << chargepoint_faulted_topics << std::endl; + if (m_mqtt->subscribe(chargepoint_car_topics) && m_mqtt->subscribe(chargepoint_tag_topics) && + m_mqtt->subscribe(chargepoint_faulted_topics)) { // Wait for disconnection or end of application std::cout << "Ready!" << std::endl; @@ -326,6 +333,43 @@ bool MqttManager::publishStatus(const std::string& status, unsigned int nb_phase return ret; } +/** @brief Publish the ocpp config of the connectors */ +void MqttManager::publishOcppConfig() +{ + // Check connectivity + if (m_mqtt->isConnected()) + { + // Compute topic name + std::stringstream topic; + topic << m_ocpp_config_topic; + + // Get vector of key/value for ocpp config + std::vector> keys; + std::vector values; + std::vector> unknown_values; + m_config.ocppConfig().getConfiguration(keys, values, unknown_values); + + // Create the JSON message + rapidjson::Document msg; + msg.Parse("{}"); + for (const ocpp::types::KeyValue& keyValue : values) + { + if (!keyValue.value.value().empty()) + { + rapidjson::Value key(keyValue.key.c_str(), msg.GetAllocator()); + rapidjson::Value value(keyValue.value.value().c_str(), msg.GetAllocator()); + msg.AddMember(key, value, msg.GetAllocator()); + } + } + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + msg.Accept(writer); + + // Publish + m_mqtt->publish(topic.str(), buffer.GetString(), IMqttClient::QoS::QOS_0, true); + } +} + /** @brief Publish the data of the connectors */ void MqttManager::publishData(const std::vector& connectors) { @@ -358,9 +402,9 @@ void MqttManager::publishData(const std::vector& connectors) msg.AddMember(rapidjson::StringRef("car_ready"), rapidjson::Value(connector.car_ready), msg.GetAllocator()); static const char* consumption_str[] = {"consumption_l1", "consumption_l2", "consumption_l3"}; - std::vector currents = connector.meter->getCurrents(); - unsigned int nb_phases = connector.meter->getNumberOfPhases(); - for (unsigned int i = 0; i < 3 ; i++) + std::vector currents = connector.meter->getCurrents(); + unsigned int nb_phases = connector.meter->getNumberOfPhases(); + for (unsigned int i = 0; i < 3; i++) { if (i < nb_phases) { @@ -389,7 +433,7 @@ std::string MqttManager::buildStatusMessage(const char* status, unsigned int nb_ msg.Parse("{}"); #ifdef _MSC_VER msg.AddMember(rapidjson::StringRef("pid"), rapidjson::Value(static_cast(GetCurrentProcessId())), msg.GetAllocator()); -#else // _MSC_VER +#else // _MSC_VER msg.AddMember(rapidjson::StringRef("pid"), rapidjson::Value(getpid()), msg.GetAllocator()); #endif // _MSC_VER msg.AddMember(rapidjson::StringRef("status"), rapidjson::Value(status, msg.GetAllocator()).Move(), msg.GetAllocator()); diff --git a/src/chargepoint/mqtt/MqttManager.h b/src/chargepoint/mqtt/MqttManager.h index bb28920..1531795 100644 --- a/src/chargepoint/mqtt/MqttManager.h +++ b/src/chargepoint/mqtt/MqttManager.h @@ -77,6 +77,9 @@ class MqttManager : public IMqttClient::IListener /** @brief Publish the data of the connectors */ void publishData(const std::vector& connectors); + /** @brief Publish the ocpp config of the charge point */ + void publishOcppConfig(); + private: /** @brief Configuration */ SimulatedChargePointConfig& m_config; @@ -92,6 +95,8 @@ class MqttManager : public IMqttClient::IListener IMqttClient* m_mqtt; /** @brief Status topic */ std::string m_status_topic; + /** @brief Config topic */ + std::string m_ocpp_config_topic; /** @brief Connectors topic */ std::string m_connectors_topic; diff --git a/src/chargepoint/ocpp/ChargePointEventsHandler.cpp b/src/chargepoint/ocpp/ChargePointEventsHandler.cpp index 733445b..2e20ef3 100644 --- a/src/chargepoint/ocpp/ChargePointEventsHandler.cpp +++ b/src/chargepoint/ocpp/ChargePointEventsHandler.cpp @@ -360,7 +360,21 @@ std::string ChargePointEventsHandler::getDiagnostics(const ocpp::types::Optional std::string diag_file = "/tmp/diag.zip"; std::stringstream ss; - ss << "zip " << diag_file << " " << m_config.stackConfig().databasePath(); + ss << "zip " << diag_file; + + for (auto filename : m_config.diagFiles()) { + std::string filepath = filename; + if (filepath[0] != '/') { + filepath = m_config.workingDir() + "/" + filepath; + } + if (std::filesystem::exists(filepath)) + { + ss << " " << filepath; + } else { + cout << "Unable to add file " << filepath << " in diagnostic zip: not found" << endl; + } + } + int err = WEXITSTATUS(system(ss.str().c_str())); cout << "Command line : " << ss.str() << " => " << err << endl; diff --git a/src/supervisor/ChargePointManager.py b/src/supervisor/ChargePointManager.py index eef939c..8d091cb 100644 --- a/src/supervisor/ChargePointManager.py +++ b/src/supervisor/ChargePointManager.py @@ -192,6 +192,21 @@ def remove_charge_point(self, cp_id: str) -> bool: """ Send the command to remove the simulated charge point """ ret = self.__remove_charge_points([{"id": cp_id}]) + def restart_all_charge_points(self) -> bool: + """ send restart command to all simulated charge points """ + ret = True + + for cp_id in self.__cps.keys(): + ret = ret and self.restart_charge_point(cp_id) + + return ret + + def kill_all_charge_points(self): + """ send kill command to all simulated charge points """ + cp_ids = self.__cps.keys() + for cp_id in cp_ids: + self.kill_charge_point(cp_id) + def restart_charge_point(self, cp_id: str) -> bool: """ Send the command to restart the simulated charge point """ diff --git a/src/supervisor/ui/SupervisorScreen.py b/src/supervisor/ui/SupervisorScreen.py index a85a10c..bf42eda 100644 --- a/src/supervisor/ui/SupervisorScreen.py +++ b/src/supervisor/ui/SupervisorScreen.py @@ -185,6 +185,11 @@ def load_callback(filepath: str, filename: str): content.popup = popup popup.open() + def bt_restart_all_cp(self) -> None: + self.__cp_mgr.restart_all_charge_points() + def bt_kill_all_cp(self) -> None: + self.__cp_mgr.kill_all_charge_points() + def __on_connection_change(self, connected: bool) -> None: """ Called when MQTT connection state has changed """ if connected: diff --git a/src/supervisor/ui/supervisor.kv b/src/supervisor/ui/supervisor.kv index ddf1a70..cf11cde 100644 --- a/src/supervisor/ui/supervisor.kv +++ b/src/supervisor/ui/supervisor.kv @@ -174,6 +174,7 @@ SupervisorScreen: disabled: not self.active or not bt_connect.connected or not lbl_launcher_status.alive on_release: root.bt_kill_restart_cp_click() + Button: id: bt_save_setup text: 'Save setup' @@ -198,6 +199,30 @@ SupervisorScreen: disabled: not bt_connect.connected or not lbl_launcher_status.alive on_release: root.bt_load_setup_click() + Button: + id: bt_restart_all_cp + text: 'Restart all CP' + text_size: self.size + halign: 'center' + valign: 'middle' + size_hint: None, 1. + size: 100, 0 + + disabled: not bt_connect.connected or not lbl_launcher_status.alive + on_release: root.bt_restart_all_cp() + + Button: + id: bt_kill_all_cp + text: 'kill all CP' + text_size: self.size + halign: 'center' + valign: 'middle' + size_hint: None, 1. + size: 100, 0 + + disabled: not bt_connect.connected or not lbl_launcher_status.alive + on_release: root.bt_kill_all_cp() + BackgroundLabel: id: lbl_launcher_status text: 'LAUNCHER'