- 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
157 lines
5.1 KiB
C++
157 lines
5.1 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
|
|
|
#include <charge_bridge/gpio_bridge.hpp>
|
|
#include <charge_bridge/utilities/logging.hpp>
|
|
#include <charge_bridge/utilities/platform_utils.hpp>
|
|
#include <charge_bridge/utilities/string.hpp>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <everest/io/event/fd_event_handler.hpp>
|
|
#include <everest/io/mqtt/mqtt_client.hpp>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <protocol/cb_management.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
namespace charge_bridge {
|
|
using namespace std::chrono_literals;
|
|
namespace mqtt = everest::lib::io::mqtt;
|
|
|
|
namespace {
|
|
const int default_udp_timeout_ms = 1000;
|
|
const int mqtt_reconnect_timeout_ms = 1000;
|
|
} // namespace
|
|
|
|
gpio_bridge::gpio_bridge(gpio_config const& config) :
|
|
m_udp(config.cb_remote, config.cb_port, default_udp_timeout_ms),
|
|
m_mqtt(mqtt_reconnect_timeout_ms)
|
|
|
|
{
|
|
m_identifier = config.cb + "/" + config.item;
|
|
|
|
m_heartbeat_timer.set_timeout(std::chrono::seconds(config.interval_s));
|
|
|
|
m_udp.set_rx_handler([this](auto const& data, auto&) { handle_udp_rx(data); });
|
|
|
|
m_udp.set_error_handler([this](auto id, auto const& msg) {
|
|
utilities::print_error(m_identifier, "GPIO/UDP", id) << msg << std::endl;
|
|
m_udp_on_error = id not_eq 0;
|
|
});
|
|
|
|
m_receive_topic = "pionix/chargebridge/" + config.cb + "/gpio/output/";
|
|
m_send_topic = "pionix/chargebridge/" + config.cb + "/gpio/input/";
|
|
|
|
m_mqtt.set_error_handler([this, config](int id, std::string const& msg) {
|
|
utilities::print_error(m_identifier, "GPIO/MQTT", id) << msg << std::endl;
|
|
m_mqtt_on_error = id not_eq 0;
|
|
});
|
|
|
|
m_mqtt.set_callback_connect([this](auto&, auto, auto, auto const&) {
|
|
m_mqtt.subscribe(
|
|
m_receive_topic + "#", [this](auto&, auto const& payload) { dispatch(payload); },
|
|
everest::lib::io::mqtt::mqtt_client::QoS::at_most_once);
|
|
});
|
|
|
|
m_mqtt.connect(config.mqtt_bind, config.mqtt_remote, config.mqtt_port, config.mqtt_ping_interval_ms);
|
|
|
|
m_message.type = CbStructType::CST_HostToCb_Gpio;
|
|
m_message.data.number_of_gpios = CB_NUMBER_OF_GPIOS;
|
|
std::memset(m_message.data.gpio_values, 0, sizeof(m_message.data.gpio_values));
|
|
}
|
|
|
|
gpio_bridge::~gpio_bridge() {
|
|
}
|
|
|
|
bool gpio_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) {
|
|
auto result = handler.register_event_handler(&m_udp);
|
|
result = handler.register_event_handler(&m_mqtt) && result;
|
|
result = handler.register_event_handler(&m_heartbeat_timer, [this](auto&) { handle_heartbeat_timer(); }) && result;
|
|
return result;
|
|
}
|
|
|
|
bool gpio_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) {
|
|
auto result = handler.unregister_event_handler(&m_udp);
|
|
result = handler.unregister_event_handler(&m_mqtt) && result;
|
|
result = handler.unregister_event_handler(&m_heartbeat_timer) && result;
|
|
return result;
|
|
}
|
|
|
|
void gpio_bridge::dispatch(everest::lib::io::mqtt::mqtt_client::message const& data) {
|
|
auto& topic = data.topic;
|
|
auto& payload = data.payload;
|
|
auto operation = utilities::string_after_pattern(topic, m_receive_topic);
|
|
uint16_t value = 0;
|
|
int id = 0;
|
|
|
|
auto stous = [](std::string const& data) {
|
|
auto val = stoi(data);
|
|
if (val < 0 or val > std::numeric_limits<uint16_t>::max()) {
|
|
throw std::range_error("");
|
|
}
|
|
return static_cast<uint16_t>(val);
|
|
};
|
|
|
|
try {
|
|
value = stous(payload);
|
|
} catch (...) {
|
|
std::cout << "INVALID DATA on MQTT for GPIO DATA" << std::endl;
|
|
return;
|
|
}
|
|
try {
|
|
id = std::stoi(operation);
|
|
} catch (...) {
|
|
std::cout << "INVALID DATA on MQTT for GPIO ID" << std::endl;
|
|
return;
|
|
}
|
|
if (id < 0 or id >= CB_NUMBER_OF_GPIOS) {
|
|
std::cout << "INVALID GPIO ID" << std::endl;
|
|
return;
|
|
}
|
|
|
|
m_message.data.gpio_values[id] = value;
|
|
send_udp();
|
|
}
|
|
|
|
void gpio_bridge::send_mqtt(std::string const& topic, std::string const& message) {
|
|
everest::lib::io::mqtt::mqtt_client::message payload;
|
|
payload.topic = m_send_topic + topic;
|
|
payload.payload = message;
|
|
m_mqtt.publish(payload);
|
|
}
|
|
|
|
void gpio_bridge::send_udp() {
|
|
if (not m_udp_on_error) {
|
|
everest::lib::io::udp::udp_payload payload;
|
|
utilities::struct_to_vector(m_message, payload.buffer);
|
|
m_udp.tx(payload);
|
|
}
|
|
}
|
|
|
|
void gpio_bridge::handle_error_timer() {
|
|
if (m_udp_on_error) {
|
|
m_udp.reset();
|
|
}
|
|
}
|
|
|
|
void gpio_bridge::handle_heartbeat_timer() {
|
|
send_udp();
|
|
}
|
|
|
|
void gpio_bridge::handle_udp_rx(everest::lib::io::udp::udp_payload const& payload) {
|
|
CbManagementPacket<CbGpioPacket> data;
|
|
if (payload.size() == sizeof(data)) {
|
|
std::memcpy(&data, payload.buffer.data(), sizeof(data));
|
|
for (std::size_t i = 0; i < sizeof(data.data.gpio_values) / sizeof(data.data.gpio_values[0]); ++i) {
|
|
send_mqtt(std::to_string(i), std::to_string(data.data.gpio_values[i]));
|
|
}
|
|
} else {
|
|
std::cout << "INVALID DATA SIZE in UDP RX of GPIO: " << payload.size() << " vs " << sizeof(data) << std::endl;
|
|
}
|
|
}
|
|
|
|
} // namespace charge_bridge
|