-
Notifications
You must be signed in to change notification settings - Fork 58
/
Copy pathtariff_and_cost.cpp
279 lines (238 loc) · 13.8 KB
/
tariff_and_cost.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ocpp/v2/functional_blocks/tariff_and_cost.hpp>
#include <ocpp/v2/ctrlr_component_variables.hpp>
#include <ocpp/v2/device_model.hpp>
#include <ocpp/v2/evse_manager.hpp>
#include <ocpp/v2/functional_blocks/functional_block_context.hpp>
#include <ocpp/v2/functional_blocks/meter_values.hpp>
#include <ocpp/v2/messages/CostUpdated.hpp>
const auto DEFAULT_PRICE_NUMBER_OF_DECIMALS = 3;
namespace ocpp::v2 {
TariffAndCost::TariffAndCost(const FunctionalBlockContext& functional_block_context, MeterValuesInterface& meter_values,
std::optional<SessionCostMessageCallback>& session_cost_message_callback,
std::optional<SetRunningCostCallback>& set_running_cost_callback,
boost::asio::io_service& io_service) :
context(functional_block_context),
meter_values(meter_values),
session_cost_message_callback(session_cost_message_callback),
set_running_cost_callback(set_running_cost_callback),
io_service(io_service) {
}
void TariffAndCost::handle_message(const ocpp::EnhancedMessage<MessageType>& message) {
if (message.messageType == MessageType::CostUpdated) {
const auto& json_message = message.message;
this->handle_costupdated_req(json_message);
} else {
throw MessageTypeNotImplementedException(message.messageType);
}
}
void TariffAndCost::handle_cost_and_tariff(const TransactionEventResponse& response,
const TransactionEventRequest& original_message,
const json& original_transaction_event_response) {
const bool tariff_enabled = this->is_tariff_enabled();
const bool cost_enabled = this->is_cost_enabled();
std::vector<DisplayMessageContent> cost_messages;
// Check if there is a tariff message and if 'Tariff' is available and enabled
if (response.updatedPersonalMessage.has_value() and tariff_enabled) {
MessageContent personal_message = response.updatedPersonalMessage.value();
DisplayMessageContent message = message_content_to_display_message_content(personal_message);
cost_messages.push_back(message);
// If cost is enabled, the message will be sent to the running cost callback. But if it is not enabled, the
// tariff message will be sent using the session cost message callback.
if (!cost_enabled and this->session_cost_message_callback.has_value() and
this->session_cost_message_callback != nullptr) {
SessionCostMessage session_cost_message;
session_cost_message.message = cost_messages;
session_cost_message.ocpp_transaction_id = original_message.transactionInfo.transactionId;
session_cost_message.identifier_id = original_message.transactionInfo.transactionId;
session_cost_message.identifier_type = IdentifierType::TransactionId;
this->session_cost_message_callback.value()({session_cost_message});
}
}
// Check if cost is available and enabled, and if there is a totalcost message.
if (cost_enabled and response.totalCost.has_value() and this->set_running_cost_callback.has_value()) {
RunningCost running_cost;
// We use the original string and convert it to a double ourselves, as the nlohmann library converts it to a
// float first and then multiply by 10^5 for example (5 decimals) will give some rounding errors. With a initial
// double instead of float, we have (a bit) more accuracy.
if (original_transaction_event_response.contains("totalCost")) {
std::string total_cost = original_transaction_event_response.at("totalCost").dump();
running_cost.cost = stod(total_cost);
} else {
running_cost.cost = static_cast<double>(response.totalCost.value());
}
if (original_message.eventType == TransactionEventEnum::Ended) {
running_cost.state = RunningCostState::Finished;
} else {
running_cost.state = RunningCostState::Charging;
}
running_cost.transaction_id = original_message.transactionInfo.transactionId;
if (original_message.meterValue.has_value()) {
const auto& meter_value = original_message.meterValue.value();
std::optional<float> max_meter_value;
for (const MeterValue& mv : meter_value) {
auto it = std::find_if(mv.sampledValue.begin(), mv.sampledValue.end(), [](const SampledValue& value) {
return value.measurand == MeasurandEnum::Energy_Active_Import_Register and !value.phase.has_value();
});
if (it != mv.sampledValue.end()) {
// Found a sampled metervalue we are searching for!
if (!max_meter_value.has_value() or max_meter_value.value() < it->value) {
max_meter_value = it->value;
}
}
}
if (max_meter_value.has_value()) {
running_cost.meter_value = static_cast<int32_t>(max_meter_value.value());
}
}
running_cost.timestamp = original_message.timestamp;
if (response.customData.has_value()) {
// With the current spec, it is not possible to send a qr code as well as a multi language personal
// message, because there can only be one vendor id in custom data. If you not check the vendor id, it
// is just possible for a csms to include them both.
const json& custom_data = response.customData.value();
if (/*custom_data.contains("vendorId") and
(custom_data.at("vendorId").get<std::string>() == "org.openchargealliance.org.qrcode") and */
custom_data.contains("qrCodeText") and
this->context.device_model
.get_optional_value<bool>(ControllerComponentVariables::DisplayMessageQRCodeDisplayCapable)
.value_or(false)) {
running_cost.qr_code_text = custom_data.at("qrCodeText");
}
// Add multilanguage messages
if (custom_data.contains("updatedPersonalMessageExtra") and is_multilanguage_enabled()) {
// Get supported languages, which is stored in the values list of "Language" of
// "DisplayMessageCtrlr"
std::optional<VariableMetaData> metadata = this->context.device_model.get_variable_meta_data(
ControllerComponentVariables::DisplayMessageLanguage.component,
ControllerComponentVariables::DisplayMessageLanguage.variable.value());
std::vector<std::string> supported_languages;
if (metadata.has_value() and metadata.value().characteristics.valuesList.has_value()) {
supported_languages =
ocpp::split_string(metadata.value().characteristics.valuesList.value(), ',', true);
} else {
EVLOG_error << "DisplayMessageCtrlr variable Language should have a valuesList with supported "
"languages";
}
for (const auto& m : custom_data.at("updatedPersonalMessageExtra").items()) {
DisplayMessageContent c = message_content_to_display_message_content(m.value());
if (!c.language.has_value()) {
EVLOG_warning
<< "updated personal message extra sent but language unknown: Can not show message.";
continue;
}
if (supported_languages.empty()) {
EVLOG_warning << "Can not show personal message as the supported languages are unknown "
"(please set the `valuesList` of `DisplayMessageCtrlr` variable `Language` to "
"set the supported languages)";
// Break loop because the next iteration, the supported languages will also not be there.
break;
}
if (std::find(supported_languages.begin(), supported_languages.end(), c.language.value()) !=
supported_languages.end()) {
cost_messages.push_back(c);
} else {
EVLOG_warning << "Can not send a personal message text in language " << c.language.value()
<< " as it is not supported by the charging station.";
}
}
}
}
if (tariff_enabled and !cost_messages.empty()) {
running_cost.cost_messages = cost_messages;
}
const int number_of_decimals =
this->context.device_model
.get_optional_value<int>(ControllerComponentVariables::NumberOfDecimalsForCostValues)
.value_or(DEFAULT_PRICE_NUMBER_OF_DECIMALS);
uint32_t decimals =
(number_of_decimals < 0 ? DEFAULT_PRICE_NUMBER_OF_DECIMALS : static_cast<uint32_t>(number_of_decimals));
const std::optional<std::string> currency =
this->context.device_model.get_value<std::string>(ControllerComponentVariables::TariffCostCtrlrCurrency);
this->set_running_cost_callback.value()(running_cost, decimals, currency);
}
}
void TariffAndCost::handle_costupdated_req(const Call<CostUpdatedRequest> call) {
CostUpdatedResponse response;
ocpp::CallResult<CostUpdatedResponse> call_result(response, call.uniqueId);
if (!is_cost_enabled() or !this->set_running_cost_callback.has_value()) {
this->context.message_dispatcher.dispatch_call_result(call_result);
return;
}
RunningCost running_cost;
TriggerMeterValue triggers;
if (this->context.device_model
.get_optional_value<bool>(ControllerComponentVariables::CustomImplementationCaliforniaPricingEnabled)
.value_or(false) and
call.msg.customData.has_value()) {
const json running_cost_json = call.msg.customData.value();
// California pricing is enabled, which means we have to read the custom data.
running_cost = running_cost_json;
if (running_cost_json.contains("triggerMeterValue")) {
triggers = running_cost_json.at("triggerMeterValue");
}
} else {
running_cost.state = RunningCostState::Charging;
}
// In 2.0.1, the cost and transaction id are already part of the CostUpdatedRequest, so they need to be added to
// the 'RunningCost' struct.
running_cost.cost = static_cast<double>(call.msg.totalCost);
running_cost.transaction_id = call.msg.transactionId;
std::optional<int32_t> transaction_evse_id =
this->context.evse_manager.get_transaction_evseid(running_cost.transaction_id);
if (!transaction_evse_id.has_value()) {
// We just put an error in the log as the spec does not define what to do here. It is not possible to return
// a 'Rejected' or something in that manner.
EVLOG_error << "Received CostUpdatedRequest, but transaction id is not a valid transaction id.";
}
const int number_of_decimals =
this->context.device_model.get_optional_value<int>(ControllerComponentVariables::NumberOfDecimalsForCostValues)
.value_or(DEFAULT_PRICE_NUMBER_OF_DECIMALS);
uint32_t decimals =
(number_of_decimals < 0 ? DEFAULT_PRICE_NUMBER_OF_DECIMALS : static_cast<uint32_t>(number_of_decimals));
const std::optional<std::string> currency =
this->context.device_model.get_value<std::string>(ControllerComponentVariables::TariffCostCtrlrCurrency);
this->set_running_cost_callback.value()(running_cost, decimals, currency);
this->context.message_dispatcher.dispatch_call_result(call_result);
// In OCPP 2.0.1, the chargepoint status trigger is not used.
if (!triggers.at_energy_kwh.has_value() and !triggers.at_power_kw.has_value() and !triggers.at_time.has_value()) {
return;
}
const std::optional<int32_t> evse_id_opt =
this->context.evse_manager.get_transaction_evseid(running_cost.transaction_id);
if (!evse_id_opt.has_value()) {
EVLOG_warning << "Can not set running cost triggers as there is no evse id found with the transaction id from "
"the incoming CostUpdatedRequest";
return;
}
const int32_t evse_id = evse_id_opt.value();
auto& evse = this->context.evse_manager.get_evse(evse_id);
evse.set_meter_value_pricing_triggers(
triggers.at_power_kw, triggers.at_energy_kwh, triggers.at_time,
[this, evse_id](const std::vector<MeterValue>& meter_values) {
this->meter_values.meter_values_req(evse_id, meter_values, false);
},
this->io_service);
}
bool TariffAndCost::is_multilanguage_enabled() const {
return this->context.device_model
.get_optional_value<bool>(ControllerComponentVariables::CustomImplementationMultiLanguageEnabled)
.value_or(false);
}
bool TariffAndCost::is_tariff_enabled() const {
return this->context.device_model
.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrAvailableTariff)
.value_or(false) and
this->context.device_model
.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrEnabledTariff)
.value_or(false);
}
bool TariffAndCost::is_cost_enabled() const {
return this->context.device_model
.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrAvailableCost)
.value_or(false) and
this->context.device_model.get_optional_value<bool>(ControllerComponentVariables::TariffCostCtrlrEnabledCost)
.value_or(false);
}
} // namespace ocpp::v2