diff --git a/.gitignore b/.gitignore index 70850d8c4..ffa0d9303 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ workspace.yaml .vscode/ /bazel-* /modules/target +CMakeLists.txt.user +.idea/ diff --git a/config/config-sil-ocpp.yaml b/config/config-sil-ocpp.yaml index 98120a2d8..765bcd801 100644 --- a/config/config-sil-ocpp.yaml +++ b/config/config-sil-ocpp.yaml @@ -156,6 +156,15 @@ active_modules: security: - module_id: evse_security implementation_id: main + display_message: + - module_id: display_message + implementation_id: display_message + display_message: + module: TerminalCostAndPriceMessage + connections: + session_cost: + - module_id: ocpp + implementation_id: session_cost evse_security: module: EvseSecurity config_module: diff --git a/dependencies.yaml b/dependencies.yaml index 5bb27d9e5..6baa500ba 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -60,7 +60,7 @@ libevse-security: # OCPP libocpp: git: https://github.com/EVerest/libocpp.git - git_tag: 398f0c5e3465d49a27524d708a8e8281461e69fa + git_tag: v0.15.1 cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBOCPP" # Josev Josev: diff --git a/interfaces/display_message.yaml b/interfaces/display_message.yaml new file mode 100644 index 000000000..de651bde7 --- /dev/null +++ b/interfaces/display_message.yaml @@ -0,0 +1,46 @@ +description: >- + A module that implements this interface should be able to: + - store (add, remove, change) and retrieve predefined messages + - show messages on a display + + When a display message contains a session id, the display message must be removed once the session has ended. +cmds: + set_display_message: + description: >- + Command to set or replace a display message. + arguments: + request: + description: >- + Request to set a display message + type: array + items: + description: The display messages to set + type: object + $ref: /display_message#/DisplayMessage + result: + description: >- + Response to the set display message request. + type: object + $ref: /display_message#/SetDisplayMessageResponse + get_display_messages: + description: Command to get one or more display messages. + arguments: + request: + description: The request for display messages + type: object + $ref: /display_message#/GetDisplayMessageRequest + result: + description: The display messages or an empty array if there are none + type: object + $ref: /display_message#/GetDisplayMessageResponse + clear_display_message: + description: Command to remove a display message + arguments: + request: + description: The request to clear a message + type: object + $ref: /display_message#/ClearDisplayMessageRequest + result: + description: Response on the clear message request + type: object + $ref: /display_message#/ClearDisplayMessageResponse diff --git a/modules/OCPP/CMakeLists.txt b/modules/OCPP/CMakeLists.txt index de0104107..ab7357569 100644 --- a/modules/OCPP/CMakeLists.txt +++ b/modules/OCPP/CMakeLists.txt @@ -27,6 +27,7 @@ target_sources(${MODULE_NAME} "auth_provider/auth_token_providerImpl.cpp" "data_transfer/ocpp_data_transferImpl.cpp" "ocpp_generic/ocppImpl.cpp" + "session_cost/session_costImpl.cpp" "conversions.cpp" ) diff --git a/modules/OCPP/OCPP.cpp b/modules/OCPP/OCPP.cpp index 8a6dc0972..1b4a38f7d 100644 --- a/modules/OCPP/OCPP.cpp +++ b/modules/OCPP/OCPP.cpp @@ -755,6 +755,38 @@ void OCPP::ready() { this->p_ocpp_generic->publish_boot_notification_response(everest_boot_notification_response); }); + this->charge_point->register_session_cost_callback( + [this](const ocpp::RunningCost& session_cost, + const uint32_t number_of_decimals) -> ocpp::v16::DataTransferResponse { + const types::session_cost::SessionCost cost = + conversions::create_session_cost(session_cost, number_of_decimals, {}); + ocpp::v16::DataTransferResponse response; + this->p_session_cost->publish_session_cost(cost); + response.status = ocpp::v16::DataTransferStatus::Accepted; + return response; + }); + + this->charge_point->register_set_display_message_callback( + [this](const std::vector& messages) -> ocpp::v16::DataTransferResponse { + ocpp::v16::DataTransferResponse response; + if (this->r_display_message.empty()) { + EVLOG_warning << "No display message handler registered, dropping data transfer message"; + response.status = ocpp::v16::DataTransferStatus::Rejected; + return response; + } + std::vector display_messages; + for (const ocpp::DisplayMessage& message : messages) { + const types::display_message::DisplayMessage m = conversions::to_everest_display_message(message); + display_messages.push_back(m); + } + + const types::display_message::SetDisplayMessageResponse display_message_response = + this->r_display_message.at(0)->call_set_display_message(display_messages); + response = conversions::to_ocpp_data_transfer_response(display_message_response); + + return response; + }); + if (!this->r_data_transfer.empty()) { this->charge_point->register_data_transfer_callback([this](const ocpp::v16::DataTransferRequest& request) { types::ocpp::DataTransferRequest data_transfer_request; diff --git a/modules/OCPP/OCPP.hpp b/modules/OCPP/OCPP.hpp index ead6a60f0..deb467907 100644 --- a/modules/OCPP/OCPP.hpp +++ b/modules/OCPP/OCPP.hpp @@ -16,9 +16,11 @@ #include #include #include +#include // headers for required interface implementations #include +#include #include #include #include @@ -71,11 +73,13 @@ class OCPP : public Everest::ModuleBase { std::unique_ptr p_auth_validator, std::unique_ptr p_auth_provider, std::unique_ptr p_data_transfer, std::unique_ptr p_ocpp_generic, + std::unique_ptr p_session_cost, std::vector> r_evse_manager, std::vector> r_connector_zero_sink, std::unique_ptr r_reservation, std::unique_ptr r_auth, std::unique_ptr r_system, std::unique_ptr r_security, - std::vector> r_data_transfer, Conf& config) : + std::vector> r_data_transfer, + std::vector> r_display_message, Conf& config) : ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), @@ -83,6 +87,7 @@ class OCPP : public Everest::ModuleBase { p_auth_provider(std::move(p_auth_provider)), p_data_transfer(std::move(p_data_transfer)), p_ocpp_generic(std::move(p_ocpp_generic)), + p_session_cost(std::move(p_session_cost)), r_evse_manager(std::move(r_evse_manager)), r_connector_zero_sink(std::move(r_connector_zero_sink)), r_reservation(std::move(r_reservation)), @@ -90,6 +95,7 @@ class OCPP : public Everest::ModuleBase { r_system(std::move(r_system)), r_security(std::move(r_security)), r_data_transfer(std::move(r_data_transfer)), + r_display_message(std::move(r_display_message)), config(config){}; Everest::MqttProvider& mqtt; @@ -98,6 +104,7 @@ class OCPP : public Everest::ModuleBase { const std::unique_ptr p_auth_provider; const std::unique_ptr p_data_transfer; const std::unique_ptr p_ocpp_generic; + const std::unique_ptr p_session_cost; const std::vector> r_evse_manager; const std::vector> r_connector_zero_sink; const std::unique_ptr r_reservation; @@ -105,6 +112,7 @@ class OCPP : public Everest::ModuleBase { const std::unique_ptr r_system; const std::unique_ptr r_security; const std::vector> r_data_transfer; + const std::vector> r_display_message; const Conf& config; // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 diff --git a/modules/OCPP/conversions.cpp b/modules/OCPP/conversions.cpp index 4b7cd7fab..a984393eb 100644 --- a/modules/OCPP/conversions.cpp +++ b/modules/OCPP/conversions.cpp @@ -454,5 +454,356 @@ to_everest_registration_status(const ocpp::v16::RegistrationStatus& registration } } +types::display_message::MessagePriorityEnum +to_everest_display_message_priority(const ocpp::v201::MessagePriorityEnum& priority) { + switch (priority) { + case ocpp::v201::MessagePriorityEnum::AlwaysFront: + return types::display_message::MessagePriorityEnum::AlwaysFront; + case ocpp::v201::MessagePriorityEnum::InFront: + return types::display_message::MessagePriorityEnum::InFront; + case ocpp::v201::MessagePriorityEnum::NormalCycle: + return types::display_message::MessagePriorityEnum::NormalCycle; + default: + throw std::out_of_range( + "Could not convert ocpp::v201::MessagePriorityEnum to types::display_message::MessagePriorityEnum"); + } +} + +ocpp::v201::MessagePriorityEnum +to_ocpp_201_message_priority(const types::display_message::MessagePriorityEnum& priority) { + switch (priority) { + case types::display_message::MessagePriorityEnum::AlwaysFront: + return ocpp::v201::MessagePriorityEnum::AlwaysFront; + case types::display_message::MessagePriorityEnum::InFront: + return ocpp::v201::MessagePriorityEnum::InFront; + case types::display_message::MessagePriorityEnum::NormalCycle: + return ocpp::v201::MessagePriorityEnum::NormalCycle; + default: + throw std::out_of_range( + "Could not convert types::display_message::MessagePriorityEnum to ocpp::v201::MessagePriorityEnum"); + } +} + +types::display_message::MessageStateEnum to_everest_display_message_state(const ocpp::v201::MessageStateEnum& state) { + switch (state) { + case ocpp::v201::MessageStateEnum::Charging: + return types::display_message::MessageStateEnum::Charging; + case ocpp::v201::MessageStateEnum::Faulted: + return types::display_message::MessageStateEnum::Faulted; + case ocpp::v201::MessageStateEnum::Idle: + return types::display_message::MessageStateEnum::Idle; + case ocpp::v201::MessageStateEnum::Unavailable: + return types::display_message::MessageStateEnum::Unavailable; + default: + throw std::out_of_range( + "Could not convert ocpp::v201::MessageStateEnum to types::display_message::MessageStateEnum"); + } +} + +ocpp::v201::MessageStateEnum to_ocpp_201_display_message_state(const types::display_message::MessageStateEnum& state) { + switch (state) { + case types::display_message::MessageStateEnum::Charging: + return ocpp::v201::MessageStateEnum::Charging; + case types::display_message::MessageStateEnum::Faulted: + return ocpp::v201::MessageStateEnum::Faulted; + case types::display_message::MessageStateEnum::Idle: + return ocpp::v201::MessageStateEnum::Idle; + case types::display_message::MessageStateEnum::Unavailable: + return ocpp::v201::MessageStateEnum::Unavailable; + default: + throw std::out_of_range( + "Could not convert types::display_message::MessageStateEnum to ocpp::v201::MessageStateEnum"); + } +} + +types::display_message::MessageFormat +to_everest_display_message_format(const ocpp::v201::MessageFormatEnum& message_format) { + switch (message_format) { + case ocpp::v201::MessageFormatEnum::ASCII: + return types::display_message::MessageFormat::ASCII; + case ocpp::v201::MessageFormatEnum::HTML: + return types::display_message::MessageFormat::HTML; + case ocpp::v201::MessageFormatEnum::URI: + return types::display_message::MessageFormat::URI; + case ocpp::v201::MessageFormatEnum::UTF8: + return types::display_message::MessageFormat::UTF8; + default: + throw std::out_of_range( + "Could not convert ocpp::v201::MessageFormat to types::display_message::MessageFormatEnum"); + } +} + +ocpp::v201::MessageFormatEnum to_ocpp_201_message_format_enum(const types::display_message::MessageFormat& format) { + switch (format) { + case types::display_message::MessageFormat::ASCII: + return ocpp::v201::MessageFormatEnum::ASCII; + case types::display_message::MessageFormat::HTML: + return ocpp::v201::MessageFormatEnum::HTML; + case types::display_message::MessageFormat::URI: + return ocpp::v201::MessageFormatEnum::URI; + case types::display_message::MessageFormat::UTF8: + return ocpp::v201::MessageFormatEnum::UTF8; + } + + throw std::out_of_range("Could not convert types::display_message::MessageFormat to ocpp::v201::MessageFormatEnum"); +} + +types::display_message::MessageContent +to_everest_display_message_content(const ocpp::DisplayMessageContent& message_content) { + types::display_message::MessageContent message; + message.content = message_content.message; + if (message_content.message_format.has_value()) { + message.format = to_everest_display_message_format(message_content.message_format.value()); + } + message.language = message_content.language; + + return message; +} + +ocpp::v16::DataTransferResponse +to_ocpp_data_transfer_response(const types::display_message::SetDisplayMessageResponse& set_display_message_response) { + ocpp::v16::DataTransferResponse response; + switch (set_display_message_response.status) { + case types::display_message::DisplayMessageStatusEnum::Accepted: + response.status = ocpp::v16::DataTransferStatus::Accepted; + break; + case types::display_message::DisplayMessageStatusEnum::NotSupportedMessageFormat: + response.status = ocpp::v16::DataTransferStatus::Rejected; + break; + case types::display_message::DisplayMessageStatusEnum::Rejected: + response.status = ocpp::v16::DataTransferStatus::Rejected; + break; + case types::display_message::DisplayMessageStatusEnum::NotSupportedPriority: + response.status = ocpp::v16::DataTransferStatus::Rejected; + break; + case types::display_message::DisplayMessageStatusEnum::NotSupportedState: + response.status = ocpp::v16::DataTransferStatus::Rejected; + break; + case types::display_message::DisplayMessageStatusEnum::UnknownTransaction: + response.status = ocpp::v16::DataTransferStatus::Rejected; + break; + default: + throw std::out_of_range( + "Could not convert types::display_message::DisplayMessageStatusEnum to ocpp::v16::DataTransferStatus"); + } + + response.data = set_display_message_response.status_info; + return response; +} + +types::display_message::DisplayMessage to_everest_display_message(const ocpp::DisplayMessage& display_message) { + types::display_message::DisplayMessage m; + m.id = display_message.id; + m.message.content = display_message.message.message; + if (display_message.message.message_format.has_value()) { + m.message.format = to_everest_display_message_format(display_message.message.message_format.value()); + } + m.message.language = display_message.message.language; + + if (display_message.priority.has_value()) { + m.priority = to_everest_display_message_priority(display_message.priority.value()); + } + + m.qr_code = display_message.qr_code; + m.session_id = display_message.transaction_id; + if (display_message.state.has_value()) { + m.state = to_everest_display_message_state(display_message.state.value()); + } + + if (display_message.timestamp_from.has_value()) { + m.timestamp_from = display_message.timestamp_from.value().to_rfc3339(); + } + + if (display_message.timestamp_to.has_value()) { + m.timestamp_to = display_message.timestamp_to.value().to_rfc3339(); + } + + return m; +} + +ocpp::DisplayMessage to_ocpp_display_message(const types::display_message::DisplayMessage& display_message) { + ocpp::DisplayMessage m; + m.id = display_message.id; + m.message.message = display_message.message.content; + m.message.language = display_message.message.language; + if (display_message.message.format.has_value()) { + m.message.message_format = to_ocpp_201_message_format_enum(display_message.message.format.value()); + } + + if (display_message.priority.has_value()) { + m.priority = to_ocpp_201_message_priority(display_message.priority.value()); + } + + m.qr_code = display_message.qr_code; + m.transaction_id = display_message.session_id; + + if (display_message.state.has_value()) { + m.state = to_ocpp_201_display_message_state(display_message.state.value()); + } + + m.timestamp_from = display_message.timestamp_from; + m.timestamp_to = display_message.timestamp_to; + + return m; +} + +types::session_cost::SessionStatus to_everest_running_cost_state(const ocpp::RunningCostState& state) { + switch (state) { + case ocpp::RunningCostState::Charging: + return types::session_cost::SessionStatus::Running; + case ocpp::RunningCostState::Idle: + return types::session_cost::SessionStatus::Idle; + case ocpp::RunningCostState::Finished: + return types::session_cost::SessionStatus::Finished; + default: + throw std::out_of_range("Could not convert ocpp::RunningCostState to types::session_cost::SessionStatus"); + } +} + +types::session_cost::SessionCostChunk create_session_cost_chunk(const double& price, const uint32_t& number_of_decimals, + const std::optional& timestamp, + const std::optional& meter_value) { + types::session_cost::SessionCostChunk chunk; + chunk.cost = types::money::MoneyAmount(); + chunk.cost->value = static_cast(price * (pow(10, number_of_decimals))); + if (timestamp.has_value()) { + chunk.timestamp_to = timestamp.value().to_rfc3339(); + } + chunk.metervalue_to = meter_value; + return chunk; +} + +types::money::Price create_price(const double& price, const uint32_t& number_of_decimals, + std::optional currency_code) { + types::money::Price p; + types::money::Currency currency; + currency.code = currency_code; + currency.decimals = number_of_decimals; + p.currency = currency; + p.value.value = static_cast(price * (pow(10, number_of_decimals))); + return p; +} + +types::session_cost::ChargingPriceComponent +create_charging_price_component(const double& price, const uint32_t& number_of_decimals, + const types::session_cost::CostCategory category, + std::optional currency_code) { + types::session_cost::ChargingPriceComponent c; + c.category = category; + c.price = create_price(price, number_of_decimals, currency_code); + return c; +} + +types::session_cost::SessionCost create_session_cost(const ocpp::RunningCost& running_cost, + const uint32_t number_of_decimals, + std::optional currency_code) { + types::session_cost::SessionCost cost; + cost.session_id = running_cost.transaction_id; + cost.currency.code = currency_code; + cost.currency.decimals = static_cast(number_of_decimals); + cost.status = to_everest_running_cost_state(running_cost.state); + cost.qr_code = running_cost.qr_code_text; + if (running_cost.cost_messages.has_value()) { + cost.message = std::vector(); + for (const ocpp::DisplayMessageContent& message : running_cost.cost_messages.value()) { + types::display_message::MessageContent m = to_everest_display_message_content(message); + cost.message->push_back(m); + } + } + + types::session_cost::SessionCostChunk chunk = create_session_cost_chunk( + running_cost.cost, number_of_decimals, running_cost.timestamp, running_cost.meter_value); + cost.cost_chunks = std::vector(); + cost.cost_chunks->push_back(chunk); + + if (running_cost.charging_price.has_value()) { + cost.charging_price = std::vector(); + const ocpp::RunningCostChargingPrice& price = running_cost.charging_price.value(); + if (price.hour_price.has_value()) { + types::session_cost::ChargingPriceComponent hour_price = create_charging_price_component( + price.hour_price.value(), number_of_decimals, types::session_cost::CostCategory::Time, currency_code); + cost.charging_price->push_back(hour_price); + } + if (price.kWh_price.has_value()) { + types::session_cost::ChargingPriceComponent energy_price = create_charging_price_component( + price.kWh_price.value(), number_of_decimals, types::session_cost::CostCategory::Energy, currency_code); + cost.charging_price->push_back(energy_price); + } + if (price.flat_fee.has_value()) { + types::session_cost::ChargingPriceComponent flat_fee_price = create_charging_price_component( + price.flat_fee.value(), number_of_decimals, types::session_cost::CostCategory::FlatFee, currency_code); + cost.charging_price->push_back(flat_fee_price); + } + } + + if (running_cost.idle_price.has_value()) { + types::session_cost::IdlePrice idle_price; + const ocpp::RunningCostIdlePrice& ocpp_idle_price = running_cost.idle_price.value(); + if (ocpp_idle_price.idle_hour_price.has_value()) { + idle_price.hour_price = + create_price(ocpp_idle_price.idle_hour_price.value(), number_of_decimals, currency_code); + } + + if (ocpp_idle_price.idle_grace_minutes.has_value()) { + idle_price.grace_minutes = ocpp_idle_price.idle_grace_minutes.value(); + } + + cost.idle_price = idle_price; + } + + if (running_cost.next_period_at_time.has_value() || running_cost.next_period_charging_price.has_value() || + running_cost.next_period_idle_price.has_value()) { + types::session_cost::NextPeriodPrice next_period; + if (running_cost.next_period_at_time.has_value()) { + next_period.timestamp_from = running_cost.next_period_at_time.value().to_rfc3339(); + } + if (running_cost.next_period_idle_price.has_value()) { + types::session_cost::IdlePrice next_period_idle_price; + const ocpp::RunningCostIdlePrice& ocpp_next_period_idle_price = running_cost.next_period_idle_price.value(); + if (ocpp_next_period_idle_price.idle_hour_price.has_value()) { + next_period_idle_price.hour_price = create_price(ocpp_next_period_idle_price.idle_hour_price.value(), + number_of_decimals, currency_code); + } + + if (ocpp_next_period_idle_price.idle_grace_minutes.has_value()) { + next_period_idle_price.grace_minutes = ocpp_next_period_idle_price.idle_grace_minutes.value(); + } + + next_period.idle_price = next_period_idle_price; + } + if (running_cost.next_period_charging_price.has_value()) { + const ocpp::RunningCostChargingPrice& next_period_charging_price = + running_cost.next_period_charging_price.value(); + + next_period.charging_price = std::vector(); + + if (next_period_charging_price.hour_price.has_value()) { + types::session_cost::ChargingPriceComponent hour_price = + create_charging_price_component(next_period_charging_price.hour_price.value(), number_of_decimals, + types::session_cost::CostCategory::Time, currency_code); + next_period.charging_price.push_back(hour_price); + } + + if (next_period_charging_price.kWh_price.has_value()) { + types::session_cost::ChargingPriceComponent energy_price = + create_charging_price_component(next_period_charging_price.kWh_price.value(), number_of_decimals, + types::session_cost::CostCategory::Energy, currency_code); + next_period.charging_price.push_back(energy_price); + } + + if (next_period_charging_price.flat_fee.has_value()) { + types::session_cost::ChargingPriceComponent flat_fee_price = + create_charging_price_component(next_period_charging_price.flat_fee.value(), number_of_decimals, + types::session_cost::CostCategory::FlatFee, currency_code); + next_period.charging_price.push_back(flat_fee_price); + } + } + + cost.next_period = next_period; + } + + return cost; +} + } // namespace conversions } // namespace module diff --git a/modules/OCPP/conversions.hpp b/modules/OCPP/conversions.hpp index f374c208b..d4c590add 100644 --- a/modules/OCPP/conversions.hpp +++ b/modules/OCPP/conversions.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -110,6 +111,44 @@ to_everest_boot_notification_response(const ocpp::v16::BootNotificationResponse& types::ocpp::RegistrationStatus to_everest_registration_status(const ocpp::v16::RegistrationStatus& registration_status); +types::display_message::MessagePriorityEnum +to_everest_display_message_priority(const ocpp::v201::MessagePriorityEnum& priority); +ocpp::v201::MessagePriorityEnum to_ocpp_201_message_priority(const ocpp::v201::MessagePriorityEnum& priority); + +types::display_message::MessageStateEnum to_everest_display_message_state(const ocpp::v201::MessageStateEnum& state); +ocpp::v201::MessageStateEnum to_ocpp_201_display_message_state(const types::display_message::MessageStateEnum& state); + +types::display_message::MessageFormat +to_everest_display_message_format(const ocpp::v201::MessageFormatEnum& message_format); +ocpp::v201::MessageFormatEnum to_ocpp_201_message_format_enum(const types::display_message::MessageFormat& format); + +types::display_message::MessageContent +to_everest_display_message_content(const ocpp::DisplayMessageContent& message_content); + +ocpp::v16::DataTransferResponse +to_ocpp_data_transfer_response(const types::display_message::SetDisplayMessageResponse& set_display_message_response); + +types::display_message::DisplayMessage to_everest_display_message(const ocpp::DisplayMessage& display_message); +ocpp::DisplayMessage to_ocpp_display_message(const types::display_message::DisplayMessage& display_message); + +types::session_cost::SessionStatus to_everest_running_cost_state(const ocpp::RunningCostState& state); + +types::session_cost::SessionCostChunk create_session_cost_chunk(const double& price, const uint32_t& number_of_decimals, + const std::optional& timestamp, + const std::optional& meter_value); + +types::money::Price create_price(const double& price, const uint32_t& number_of_decimals, + std::optional currency_code); + +types::session_cost::ChargingPriceComponent +create_charging_price_component(const double& price, const uint32_t& number_of_decimals, + const types::session_cost::CostCategory category, + std::optional currency_code); + +types::session_cost::SessionCost create_session_cost(const ocpp::RunningCost& running_cost, + const uint32_t number_of_decimals, + std::optional currency_code); + } // namespace conversions } // namespace module diff --git a/modules/OCPP/manifest.yaml b/modules/OCPP/manifest.yaml index 6aa945776..c9ae86032 100644 --- a/modules/OCPP/manifest.yaml +++ b/modules/OCPP/manifest.yaml @@ -51,6 +51,9 @@ provides: ocpp_generic: description: Generic OCPP interface. interface: ocpp + session_cost: + description: Send session cost + interface: session_cost requires: evse_manager: interface: evse_manager @@ -80,6 +83,10 @@ requires: interface: ocpp_data_transfer min_connections: 0 max_connections: 1 + display_message: + interface: display_message + min_connections: 0 + max_connections: 1 enable_external_mqtt: true enable_global_errors: true metadata: diff --git a/modules/OCPP/session_cost/session_costImpl.cpp b/modules/OCPP/session_cost/session_costImpl.cpp new file mode 100644 index 000000000..bcbbb8197 --- /dev/null +++ b/modules/OCPP/session_cost/session_costImpl.cpp @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "session_costImpl.hpp" + +namespace module { +namespace session_cost { + +void session_costImpl::init() { +} + +void session_costImpl::ready() { +} + +} // namespace session_cost +} // namespace module diff --git a/modules/OCPP/session_cost/session_costImpl.hpp b/modules/OCPP/session_cost/session_costImpl.hpp new file mode 100644 index 000000000..4193f8f2e --- /dev/null +++ b/modules/OCPP/session_cost/session_costImpl.hpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef SESSION_COST_SESSION_COST_IMPL_HPP +#define SESSION_COST_SESSION_COST_IMPL_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 3 +// + +#include + +#include "../OCPP.hpp" + +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 +// insert your custom include headers here +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 + +namespace module { +namespace session_cost { + +struct Conf {}; + +class session_costImpl : public session_costImplBase { +public: + session_costImpl() = delete; + session_costImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, Conf& config) : + session_costImplBase(ev, "session_cost"), mod(mod), config(config){}; + + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + // insert your public definitions here + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + +protected: + // no commands defined for this interface + + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + // insert your protected definitions here + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + +private: + const Everest::PtrContainer& mod; + const Conf& config; + + virtual void init() override; + virtual void ready() override; + + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 + // insert your private definitions here + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 +}; + +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 +// insert other definitions here +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 + +} // namespace session_cost +} // namespace module + +#endif // SESSION_COST_SESSION_COST_IMPL_HPP diff --git a/modules/OCPP201/conversions.cpp b/modules/OCPP201/conversions.cpp index 464ca824a..3f2307f05 100644 --- a/modules/OCPP201/conversions.cpp +++ b/modules/OCPP201/conversions.cpp @@ -1056,23 +1056,23 @@ to_everest_ocpp_transaction_event(const ocpp::v201::TransactionEventRequest& tra return ocpp_transaction_event; } -types::ocpp::MessageFormat to_everest_message_format(const ocpp::v201::MessageFormatEnum& message_format) { +types::display_message::MessageFormat to_everest_message_format(const ocpp::v201::MessageFormatEnum& message_format) { switch (message_format) { case ocpp::v201::MessageFormatEnum::ASCII: - return types::ocpp::MessageFormat::ASCII; + return types::display_message::MessageFormat::ASCII; case ocpp::v201::MessageFormatEnum::HTML: - return types::ocpp::MessageFormat::HTML; + return types::display_message::MessageFormat::HTML; case ocpp::v201::MessageFormatEnum::URI: - return types::ocpp::MessageFormat::URI; + return types::display_message::MessageFormat::URI; case ocpp::v201::MessageFormatEnum::UTF8: - return types::ocpp::MessageFormat::UTF8; + return types::display_message::MessageFormat::UTF8; default: throw std::out_of_range("Could not convert ocpp::v201::MessageFormatEnum to types::ocpp::MessageFormat"); } } -types::ocpp::MessageContent to_everest_message_content(const ocpp::v201::MessageContent& message_content) { - types::ocpp::MessageContent everest_message_content; +types::display_message::MessageContent to_everest_message_content(const ocpp::v201::MessageContent& message_content) { + types::display_message::MessageContent everest_message_content; everest_message_content.format = to_everest_message_format(message_content.format); everest_message_content.content = message_content.content; everest_message_content.language = message_content.language; diff --git a/modules/OCPP201/conversions.hpp b/modules/OCPP201/conversions.hpp index ead03df95..920e2f287 100644 --- a/modules/OCPP201/conversions.hpp +++ b/modules/OCPP201/conversions.hpp @@ -169,11 +169,11 @@ to_everest_ocpp_transaction_event(const ocpp::v201::TransactionEventRequest& tra /// \brief Converts a given ocpp::v201::MessageFormat \p message_format to a /// types::ocpp::MessageFormat -types::ocpp::MessageFormat to_everest_message_format(const ocpp::v201::MessageFormatEnum& message_format); +types::display_message::MessageFormat to_everest_message_format(const ocpp::v201::MessageFormatEnum& message_format); /// \brief Converts a given ocpp::v201::MessageContent \p message_content to a /// types::ocpp::MessageContent -types::ocpp::MessageContent to_everest_message_content(const ocpp::v201::MessageContent& message_content); +types::display_message::MessageContent to_everest_message_content(const ocpp::v201::MessageContent& message_content); /// \brief Converts a given ocpp::v201::TransactionEventResponse \p transaction_event_response to a /// types::ocpp::OcppTransactionEventResponse diff --git a/modules/RsPaymentTerminal/src/main.rs b/modules/RsPaymentTerminal/src/main.rs index 0596eadba..ed75f09e8 100644 --- a/modules/RsPaymentTerminal/src/main.rs +++ b/modules/RsPaymentTerminal/src/main.rs @@ -207,8 +207,10 @@ impl PaymentTerminalModule { /// The implementation of the `SessionCostClientSubscriber::on_session_cost`, /// but here we can return errors. fn on_session_cost_impl(&self, context: &Context, value: SessionCost) -> Result<()> { + let Some(id_tag) = value.id_tag else { return Ok(()) }; + // We only care about bank cards. - match value.id_tag.authorization_type { + match id_tag.authorization_type { AuthorizationType::BankCard => (), _ => return Ok(()), } @@ -228,14 +230,14 @@ impl PaymentTerminalModule { let res = self .feig - .commit_transaction(&value.id_tag.id_token.value, total_cost as u64)?; + .commit_transaction(&id_tag.id_token.value, total_cost as u64)?; context .publisher .bank_transaction_summary .bank_transaction_summary(BankTransactionSummary { session_token: Some(BankSessionToken { - token: Some(value.id_tag.id_token.value.clone()), + token: Some(id_tag.id_token.value.clone()), }), transaction_data: Some(format!("{:06}", res.trace_number.unwrap_or_default())), })?; @@ -412,29 +414,47 @@ mod tests { SessionCost { cost_chunks: None, currency: Currency { - code: CurrencyCode::EUR, + code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: ProvidedIdToken::new(String::new(), AuthorizationType::OCPP), + id_tag: Some(ProvidedIdToken::new(String::new(), AuthorizationType::OCPP)), status: SessionStatus::Finished, + session_id: String::new(), + idle_price: None, + charging_price: None, + next_period: None, + message: None, + qr_code: None, }, SessionCost { cost_chunks: None, currency: Currency { - code: CurrencyCode::EUR, + code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: ProvidedIdToken::new(String::new(), AuthorizationType::RFID), + id_tag: Some(ProvidedIdToken::new(String::new(), AuthorizationType::RFID)), status: SessionStatus::Finished, + session_id: String::new(), + idle_price: None, + charging_price: None, + next_period: None, + message: None, + qr_code: None, }, SessionCost { cost_chunks: None, currency: Currency { - code: CurrencyCode::EUR, + code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: ProvidedIdToken::new(String::new(), AuthorizationType::BankCard), + id_tag: Some(ProvidedIdToken::new(String::new(), AuthorizationType::BankCard)), status: SessionStatus::Running, + session_id: String::new(), + idle_price: None, + charging_price: None, + next_period: None, + message: None, + qr_code: None, }, ]; @@ -464,11 +484,17 @@ mod tests { SessionCost { cost_chunks: None, currency: Currency { - code: CurrencyCode::EUR, + code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard), + id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), status: SessionStatus::Finished, + session_id: String::new(), + idle_price: None, + charging_price: None, + next_period: None, + message: None, + qr_code: None, }, 0, ), @@ -476,11 +502,17 @@ mod tests { SessionCost { cost_chunks: Some(Vec::new()), currency: Currency { - code: CurrencyCode::EUR, + code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard), + id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), status: SessionStatus::Finished, + session_id: String::new(), + idle_price: None, + charging_price: None, + next_period: None, + message: None, + qr_code: None, }, 0, ), @@ -491,13 +523,21 @@ mod tests { cost: None, timestamp_from: None, timestamp_to: None, + metervalue_from: None, + metervalue_to: None, }]), currency: Currency { - code: CurrencyCode::EUR, + code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard), + id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), status: SessionStatus::Finished, + session_id: String::new(), + idle_price: None, + charging_price: None, + next_period: None, + message: None, + qr_code: None, }, 0, ), @@ -509,20 +549,30 @@ mod tests { cost: Some(MoneyAmount { value: 1 }), timestamp_from: None, timestamp_to: None, + metervalue_from: None, + metervalue_to: None, }, SessionCostChunk { category: None, cost: Some(MoneyAmount { value: 2 }), timestamp_from: None, timestamp_to: None, + metervalue_from: None, + metervalue_to: None, }, ]), currency: Currency { - code: CurrencyCode::EUR, + code: Some(CurrencyCode::EUR), decimals: None, }, - id_tag: ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard), + id_tag: Some(ProvidedIdToken::new("token".to_string(), AuthorizationType::BankCard)), status: SessionStatus::Finished, + session_id: String::new(), + idle_price: None, + charging_price: None, + next_period: None, + message: None, + qr_code: None, }, 3, ), diff --git a/modules/examples/CMakeLists.txt b/modules/examples/CMakeLists.txt index a074c8ea6..3e8e01cbc 100644 --- a/modules/examples/CMakeLists.txt +++ b/modules/examples/CMakeLists.txt @@ -1,4 +1,5 @@ ev_add_module(Example) ev_add_module(ExampleUser) +ev_add_module(TerminalCostAndPriceMessage) add_subdirectory(error-framework) diff --git a/modules/examples/TerminalCostAndPriceMessage/CMakeLists.txt b/modules/examples/TerminalCostAndPriceMessage/CMakeLists.txt new file mode 100644 index 000000000..3ff32d50b --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# AUTO GENERATED - MARKED REGIONS WILL BE KEPT +# template version 3 +# + +# module setup: +# - ${MODULE_NAME}: module name +ev_setup_cpp_module() + +# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 +# insert your custom targets and additional config variables here +# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 + +target_sources(${MODULE_NAME} + PRIVATE + "display_message/display_messageImpl.cpp" +) + +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 +# insert other things like install cmds etc here +# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 diff --git a/modules/examples/TerminalCostAndPriceMessage/TerminalCostAndPriceMessage.cpp b/modules/examples/TerminalCostAndPriceMessage/TerminalCostAndPriceMessage.cpp new file mode 100644 index 000000000..c316d75a7 --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/TerminalCostAndPriceMessage.cpp @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#include "TerminalCostAndPriceMessage.hpp" + +namespace module { + +void TerminalCostAndPriceMessage::init() { + invoke_init(*p_display_message); + this->r_session_cost->subscribe_session_cost([](const types::session_cost::SessionCost& session_cost) { + if (!session_cost.cost_chunks.has_value()) { + EVLOG_warning << "No session cost chunks provided in session cost."; + return; + } + + uint32_t number_of_decimals = 0; + if (session_cost.currency.decimals.has_value()) { + if (session_cost.currency.decimals.value() < 0) { + EVLOG_warning << "Number of decimals for currency can not be negative."; + } else { + number_of_decimals = static_cast(session_cost.currency.decimals.value()); + } + } + + EVLOG_info << "Session cost status for session id " << session_cost.session_id << ": " + << session_status_to_string(session_cost.status); + for (const types::session_cost::SessionCostChunk& chunk : session_cost.cost_chunks.value()) { + if (chunk.cost.has_value()) { + EVLOG_info << "Session cost until now: " + << static_cast(chunk.cost.value().value) / (pow(10, number_of_decimals)); + } + } + + if (session_cost.charging_price.has_value()) { + for (const types::session_cost::ChargingPriceComponent& charging_price : + session_cost.charging_price.value()) { + std::string category; + double price = 0; + if (charging_price.category.has_value()) { + category = cost_category_to_string(charging_price.category.value()); + } + + if (charging_price.price.has_value()) { + int decimals = 0; + if (charging_price.price.value().currency.decimals.has_value()) { + decimals = charging_price.price.value().currency.decimals.value(); + } + price = static_cast(charging_price.price.value().value.value) / pow(10, decimals); + } + + EVLOG_info << "Charging price for category " << category << ": " << price << std::endl; + } + } + + if (session_cost.message.has_value()) { + for (const types::display_message::MessageContent& message : session_cost.message.value()) { + EVLOG_info << "Charging price message" + << (message.language.has_value() ? " (" + message.language.value() + ")" : "") << ": " + << message.content; + } + } + }); +} + +void TerminalCostAndPriceMessage::ready() { + invoke_ready(*p_display_message); +} + +} // namespace module diff --git a/modules/examples/TerminalCostAndPriceMessage/TerminalCostAndPriceMessage.hpp b/modules/examples/TerminalCostAndPriceMessage/TerminalCostAndPriceMessage.hpp new file mode 100644 index 000000000..93e7c04c5 --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/TerminalCostAndPriceMessage.hpp @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef DISPLAY_MESSAGE_HPP +#define DISPLAY_MESSAGE_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 2 +// + +#include "ld-ev.hpp" + +// headers for provided interface implementations +#include + +// headers for required interface implementations +#include + +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 +// insert your custom include headers here +// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1 + +namespace module { + +struct Conf {}; + +class TerminalCostAndPriceMessage : public Everest::ModuleBase { +public: + TerminalCostAndPriceMessage() = delete; + TerminalCostAndPriceMessage(const ModuleInfo& info, std::unique_ptr p_display_message, + std::unique_ptr r_session_cost, Conf& config) : + ModuleBase(info), + p_display_message(std::move(p_display_message)), + r_session_cost(std::move(r_session_cost)), + config(config){}; + + const std::unique_ptr p_display_message; + const std::unique_ptr r_session_cost; + const Conf& config; + + // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 + // insert your public definitions here + // ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1 + +protected: + // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 + // insert your protected definitions here + // ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1 + +private: + friend class LdEverest; + void init(); + void ready(); + + // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 + // insert your private definitions here + // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 +}; + +// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 +// insert other definitions here +// ev@087e516b-124c-48df-94fb-109508c7cda9:v1 + +} // namespace module + +#endif // DISPLAY_MESSAGE_HPP diff --git a/modules/examples/TerminalCostAndPriceMessage/display_message/display_messageImpl.cpp b/modules/examples/TerminalCostAndPriceMessage/display_message/display_messageImpl.cpp new file mode 100644 index 000000000..51f3a7f66 --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/display_message/display_messageImpl.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include "display_messageImpl.hpp" + +namespace module { +namespace display_message { + +void display_messageImpl::init() { +} + +void display_messageImpl::ready() { +} + +types::display_message::SetDisplayMessageResponse +display_messageImpl::handle_set_display_message(std::vector& request) { + types::display_message::SetDisplayMessageResponse response; + if (request.empty()) { + response.status = types::display_message::DisplayMessageStatusEnum::Rejected; + response.status_info = "No request sent"; + return response; + } + + for (const types::display_message::DisplayMessage& message : request) { + EVLOG_info << "New display message" + << (message.session_id.has_value() ? " for session id " + message.session_id.value() : "") << ": " + << message.message.content; + } + + response.status = types::display_message::DisplayMessageStatusEnum::Accepted; + return response; +} + +types::display_message::GetDisplayMessageResponse +display_messageImpl::handle_get_display_messages(types::display_message::GetDisplayMessageRequest& request) { + EVLOG_info << "Get display messages request received" + << (request.priority.has_value() ? " for display messages with priority " + + std::to_string(static_cast(request.priority.value())) + : "") + << (request.state.has_value() + ? " for display messages with state " + std::to_string(static_cast(request.state.value())) + : ""); + if (request.id.has_value()) { + std::string ids; + for (const int32_t& id : request.id.value()) { + ids += std::to_string(id); + ids += " "; + } + EVLOG_info << "Get display messages for specific id's: " << ids; + } + + types::display_message::GetDisplayMessageResponse response; + response.messages = std::vector(); + types::display_message::DisplayMessage test_message; + test_message.message.content = "This is a test message"; + test_message.message.format = types::display_message::MessageFormat::UTF8; + test_message.message.language = "en"; + response.messages->push_back(test_message); + types::display_message::DisplayMessage test_message_url; + test_message_url.message.content = "https://pionix.de"; + test_message_url.message.format = types::display_message::MessageFormat::URI; + response.messages->push_back(test_message_url); + + return response; +} + +types::display_message::ClearDisplayMessageResponse +display_messageImpl::handle_clear_display_message(types::display_message::ClearDisplayMessageRequest& request) { + EVLOG_info << "Clear display message request received for id: " << request.id; + + types::display_message::ClearDisplayMessageResponse response; + response.status = types::display_message::ClearMessageResponseEnum::Accepted; + response.status_info = "Yes it is done!"; + return response; +} + +} // namespace display_message +} // namespace module diff --git a/modules/examples/TerminalCostAndPriceMessage/display_message/display_messageImpl.hpp b/modules/examples/TerminalCostAndPriceMessage/display_message/display_messageImpl.hpp new file mode 100644 index 000000000..a3f375ff8 --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/display_message/display_messageImpl.hpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest +#ifndef DISPLAY_MESSAGE_DISPLAY_MESSAGE_IMPL_HPP +#define DISPLAY_MESSAGE_DISPLAY_MESSAGE_IMPL_HPP + +// +// AUTO GENERATED - MARKED REGIONS WILL BE KEPT +// template version 3 +// + +#include + +#include "../TerminalCostAndPriceMessage.hpp" + +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 +// insert your custom include headers here +// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1 + +namespace module { +namespace display_message { + +struct Conf {}; + +class display_messageImpl : public display_messageImplBase { +public: + display_messageImpl() = delete; + display_messageImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer& mod, + Conf& config) : + display_messageImplBase(ev, "display_message"), mod(mod), config(config){}; + + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + // insert your public definitions here + // ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1 + +protected: + // command handler functions (virtual) + virtual types::display_message::SetDisplayMessageResponse + handle_set_display_message(std::vector& request) override; + virtual types::display_message::GetDisplayMessageResponse + handle_get_display_messages(types::display_message::GetDisplayMessageRequest& request) override; + virtual types::display_message::ClearDisplayMessageResponse + handle_clear_display_message(types::display_message::ClearDisplayMessageRequest& request) override; + + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + // insert your protected definitions here + // ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1 + +private: + const Everest::PtrContainer& mod; + const Conf& config; + + virtual void init() override; + virtual void ready() override; + + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 + // insert your private definitions here + // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 +}; + +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 +// insert other definitions here +// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1 + +} // namespace display_message +} // namespace module + +#endif // DISPLAY_MESSAGE_DISPLAY_MESSAGE_IMPL_HPP diff --git a/modules/examples/TerminalCostAndPriceMessage/doc.rst b/modules/examples/TerminalCostAndPriceMessage/doc.rst new file mode 100644 index 000000000..6ed4d15dd --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/doc.rst @@ -0,0 +1,22 @@ +.. _everest_modules_handwritten_TerminalCostAndPriceMessage: + +.. This file is a placeholder for an optional single file + handwritten documentation for the DisplayMessage module. + Please decide whether you want to use this single file, + or a set of files in the doc/ directory. + In the latter case, you can delete this file. + In the former case, you can delete the doc/ directory. + +.. This handwritten documentation is optional. In case + you do not want to write it, you can delete this file + and the doc/ directory. + +.. The documentation can be written in reStructuredText, + and will be converted to HTML and PDF by Sphinx. + +******************************************* +TerminalCostAndPriceMessage +******************************************* + +:ref:`Link ` to the module's reference. +Example terminal cost and price message module diff --git a/modules/examples/TerminalCostAndPriceMessage/docs/index.rst b/modules/examples/TerminalCostAndPriceMessage/docs/index.rst new file mode 100644 index 000000000..61b2fcf06 --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/docs/index.rst @@ -0,0 +1,23 @@ +.. _everest_modules_handwritten_DisplayMessage: + +.. This file is a placeholder for optional multiple files + handwritten documentation for the DisplayMessage module. + Please decide whether you want to use the doc.rst file + or a set of files in the doc/ directory. + In the latter case, you can delete the doc.rst file. + In the former case, you can delete the doc/ directory. + +.. This handwritten documentation is optional. In case + you do not want to write it, you can delete this file + and the doc/ directory. + +.. The documentation can be written in reStructuredText, + and will be converted to HTML and PDF by Sphinx. + This index.rst file is the entry point for the module documentation. + +******************************************* +DisplayMessage +******************************************* + +:ref:`Link ` to the module's reference. +Example display message module diff --git a/modules/examples/TerminalCostAndPriceMessage/manifest.yaml b/modules/examples/TerminalCostAndPriceMessage/manifest.yaml new file mode 100644 index 000000000..ea406e6c9 --- /dev/null +++ b/modules/examples/TerminalCostAndPriceMessage/manifest.yaml @@ -0,0 +1,14 @@ +description: Example cost and price message module +provides: + display_message: + description: module to show a message + interface: display_message +requires: + session_cost: + interface: session_cost + min_connections: 1 + max_connections: 1 +metadata: + license: https://opensource.org/licenses/Apache-2.0 + authors: + - Maaike Zijderveld diff --git a/types/display_message.yaml b/types/display_message.yaml new file mode 100644 index 000000000..3e96d5642 --- /dev/null +++ b/types/display_message.yaml @@ -0,0 +1,179 @@ +description: >- + Message to show on a display +types: + MessagePriorityEnum: + description: >- + Priority of the message. + AlwaysFront is the highest priority: The Charging Station SHALL show this message at the configured moment, + regardless of other installed messages. Hence, it shall not cycle it with other messages and the Charging + Station’s own message shall not override this message. When a newer message with this MessagePriority is received, + this message is replaced. + InFront: The Charging Station SHALL show this message at the configured moment, regardless of the normal cycle of + messages. If there are multiple InFront message that must be shown at the same time, they must be cycled. + NormalCycle: Show this message in the cycle of messages. + type: string + enum: + - AlwaysFront + - InFront + - NormalCycle + MessageStateEnum: + description: During what state the message should be shown + type: string + enum: + - Charging + - Faulted + - Idle + - Unavailable + DisplayMessageStatusEnum: + description: Response on a display message request + type: string + enum: + - Accepted + - NotSupportedMessageFormat + - Rejected + - NotSupportedPriority + - NotSupportedState + - UnknownTransaction + ClearMessageResponseEnum: + description: Response on a clear display message request + type: string + enum: + - Accepted + - Unknown + MessageContent: + description: Contains message details + required: + - content + type: object + properties: + format: + type: string + $ref: /display_message#/MessageFormat + language: + type: string + content: + type: string + MessageFormat: + description: Format of the message to be displayed + type: string + enum: + - ASCII + - HTML + - URI + - UTF8 + DisplayMessage: + description: Message to show on a display + type: object + additionalProperties: false + properties: + id: + description: The message id + type: integer + priority: + description: >- + Priority of the message. For OCPP 2.0.1, this is a required property. But as we also use this interface + outside of ocpp, for other messages it is not required and if priority is not given, we assume it is the + lowest priority, 'NormalCycle'. When priority is 'AlwaysFront' and there already is a message with priority + 'AlwaysFront', the last received message shall replace the already existing message. + type: string + $ref: /display_message#/MessagePriorityEnum + state: + description: >- + During what state should this message be shown. When omitted, this message should be shown in any state of + the Charging Station + type: string + $ref: /display_message#/MessageStateEnum + timestamp_from: + description: >- + From what date-time should this message be shown. If omitted: directly. + type: string + format: date-time + timestamp_to: + description: >- + Until what date-time should this message be shown, after this date/time this message SHALL be removed. If + omitted, message can be shown 'forever' (until it is specifically removed). + type: string + format: date-time + session_id: + description: >- + During which session shall this message be shown. Message SHALL be removed by the Charging Station + after session has ended. If omitted, message is not tight to a session. + type: string + minLength: 0 + maxLength: 36 + message: + type: object + description: The message to show + $ref: /display_message#/MessageContent + qr_code: + description: >- + QR Code to scan for more information. + type: string + required: + - message + SetDisplayMessageResponse: + description: >- + Response to the set display message request. + type: object + properties: + status: + description: Whether the charging station is able to display the message + $ref: /display_message#/DisplayMessageStatusEnum + type: object + status_info: + description: Detailed status information + type: string + required: + - status + GetDisplayMessageRequest: + description: >- + Request display messages. The properties are filtered. If the properties are omitted, there will be no filter + applied (if all properties are omitted, all stored display messages will be returned). + type: object + properties: + id: + description: If provided the Charging Station shall return Display Messages of the given ids. + type: array + items: + type: integer + priority: + description: If provided the Charging Station shall return Display Messages with the given priority only. + $ref: /display_message#/MessagePriorityEnum + type: string + state: + description: If provided the Charging Station shall return Display Messages with the given state only. + $ref: /display_message#/MessageStateEnum + type: string + GetDisplayMessageResponse: + description: Reponse on the 'get display message' request. Will return the requested display messages. + type: object + properties: + status_info: + description: Detailed status information + type: string + messages: + description: Requested messages, if any + type: array + items: + type: object + $ref: /display_message#/DisplayMessage + ClearDisplayMessageRequest: + description: Request to remove a specific display message. + type: object + properties: + id: + description: Id of display message that should be removed from the charging station + type: integer + required: + - id + ClearDisplayMessageResponse: + description: Response on the clear display message request. + type: object + properties: + status: + type: object + $ref: /display_message#/ClearMessageResponseEnum + status_info: + type: string + required: + - status diff --git a/types/money.yaml b/types/money.yaml index 37b781dd1..d0d0f5910 100644 --- a/types/money.yaml +++ b/types/money.yaml @@ -456,8 +456,6 @@ types: unit according to ISO 4217. type: object additionalProperties: false - required: - - code properties: code: description: >- diff --git a/types/ocpp.yaml b/types/ocpp.yaml index 0e73a123b..0d11ab615 100644 --- a/types/ocpp.yaml +++ b/types/ocpp.yaml @@ -113,28 +113,6 @@ types: - Started - Updated - Ended - MessageContent: - description: Contains message details - required: - - format - - content - type: object - properties: - format: - type: string - $ref: /ocpp#/MessageFormat - language: - type: string - content: - type: string - MessageFormat: - description: Format of the message to be displayed - type: string - enum: - - ASCII - - HTML - - URI - - UTF8 OcppTransactionEventResponse: description: Information that can be returned with a OCPP TransactionEventResponse type: object @@ -154,7 +132,7 @@ types: personal_message: description: Personal message type: object - $ref: /ocpp#/MessageContent + $ref: /display_message#/MessageContent DataTransferStatus: description: Data Transfer Status enum type: string diff --git a/types/session_cost.yaml b/types/session_cost.yaml index e8aeb91b3..478e8481a 100644 --- a/types/session_cost.yaml +++ b/types/session_cost.yaml @@ -1,5 +1,65 @@ description: Types to provide the session price types: + IdlePrice: + description: >- + Price components when idle + type: object + additionalProperties: false + properties: + grace_minutes: + description: >- + Grace period in minutes before idle time is charged. + Grace minutes start counting from the timestamp of the message in which state + changed from "Charging" to "Idle". + type: integer + hour_price: + description: >- + Price per hour while idle + type: object + $ref: /money#/Price + CostCategory: + description: Cost category + type: string + enum: + - Energy + - Time + - FlatFee + - Other + ChargingPriceComponent: + description: >- + Charging price for a category + type: object + properties: + category: + description: >- + Component type for this charging price + type: string + $ref: /session_cost#/CostCategory + price: + description: >- + Price for this component + type: object + $ref: /money#/Price + NextPeriodPrice: + description: Pricing for the next period + type: object + properties: + timestamp_from: + description: Time when these prices become active. + type: string + charging_price: + description: Price components while charging + type: array + items: + type: object + $ref: /session_cost#/ChargingPriceComponent + idle_price: + description: Price components while idle. Optional if no idle fee charged. + type: object + $ref: /session_cost#/IdlePrice + required: + - timestamp_from + - charging_price SessionCostChunk: description: >- A chunk of the session cost. The total cost of the session is the sum of @@ -17,6 +77,14 @@ types: Absolute timestamp for the end of this chunk in RFC3339 UTC format type: string format: date-time + metervalue_from: + description: >- + Metervalue (energie Wh import) for the start of this chunk + type: integer + metervalue_to: + description: >- + Metervalue (energie Wh import) for the end of this chunk + type: integer cost: description: >- Cost of the energy (or other things like time, base price, ...) @@ -26,12 +94,9 @@ types: $ref: /money#/MoneyAmount category: description: >- - Cost category. Typically this is energy or time. + Cost category. Typically this is energy, time or a flat fee. type: string - enum: - - Energy - - Time - - Other + $ref: /session_cost#/CostCategory SessionStatus: description: >- Session status enum. Session can be running or finished. Costs of the @@ -39,6 +104,7 @@ types: type: string enum: - Running + - Idle - Finished SessionCost: description: >- @@ -47,7 +113,7 @@ types: type: object additionalProperties: false required: - - id_tag + - session_id - currency - status properties: @@ -55,6 +121,9 @@ types: description: The id tag assigned to this transaction type: object $ref: /authorization#/ProvidedIdToken + session_id: + description: The session id + type: string currency: description: >- Currency of the session cost. It applies to all chunks. @@ -68,7 +137,25 @@ types: items: description: One chunk of the session cost type: object - $ref: /session_cost#/SessionCostChunk + $ref: /session_cost#/SessionCostChunk + idle_price: + description: >- + Prices for when the EV is idle. + type: object + $ref: /session_cost#/IdlePrice + charging_price: + description: >- + Charging prices. + type: array + items: + description: Charging price per type + type: object + $ref: /session_cost#/ChargingPriceComponent + next_period: + description: >- + Charging prices for the next period, starting from the given date/time + type: object + $ref: /session_cost#/NextPeriodPrice status: description: >- Session status. Session can be running or finished. Costs of the @@ -77,3 +164,16 @@ types: to bank and/or backend. type: object $ref: /session_cost#/SessionStatus + message: + description: >- + Message to show to the user containing information about the price + type: array + items: + description: Message with pricing information in a specific language + type: object + $ref: /display_message#/MessageContent + qr_code: + description: >- + QR Code with more information. For example when the charging is finished, + to download the invoice from + type: string