// SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest #include "protocol/cb_config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace charge_bridge { namespace { std::pair> make_interface_list(std::string const& str, std::string const& pattern) { if (str == pattern) { return {false, {}}; }; auto raw = utilities::string_after_pattern(str, pattern).substr(1); if (raw.size() <= 2) { return {false, {}}; } auto exclude = raw.substr(0, 1) == "!"; auto items = utilities::csv_to_set(raw.substr(exclude ? 1 : 0)); for (auto const& elem : items) { std::cout << elem << ", "; } std::cout << std::endl; return {exclude, items}; } } // namespace charge_bridge::charge_bridge(charge_bridge_config const& config) : m_config(config) { if (utilities::string_starts_with(config.cb_remote, "ANY_EVSE")) { auto params = make_interface_list(config.cb_remote, "ANY_EVSE"); init_discovery(discovery_device_type::CB_EVSE, params.second, params.first); } else if (utilities::string_starts_with(config.cb_remote, "ANY_EV")) { auto params = make_interface_list(config.cb_remote, "ANY_EV"); init_discovery(discovery_device_type::CB_EV, params.second, params.first); } else { init(); } } void charge_bridge::init_discovery(discovery_device_type type, std::set const& interfaces, bool excluding) { using namespace everest::lib::util; utilities::print_error(m_config.cb_name, "DISCOVERY", -1) << "Discovery pending" << std::endl; m_discovery = std::make_unique(type, interfaces, excluding); m_discovery->set_discovery_callback(bind_obj(&charge_bridge::handle_discovery, this)); { auto handle = m_cb_status.handle(); handle->discovery_pending = true; } m_cb_status.notify_one(); } void charge_bridge::handle_discovery(std::string const& ip) { utilities::print_error(m_config.cb_name, "DISCOVERY", 0) << "Discovered at: " + ip << std::endl; m_config.cb_remote = ip; if (m_config.can0) { m_config.can0->cb_remote = ip; } if (m_config.serial1) { m_config.serial1->cb_remote = ip; } if (m_config.serial2) { m_config.serial2->cb_remote = ip; } if (m_config.serial3) { m_config.serial3->cb_remote = ip; } if (m_config.plc) { m_config.plc->cb_remote = ip; } if (m_config.bsp) { m_config.bsp->cb_remote = ip; } if (m_config.heartbeat) { m_config.heartbeat->cb_remote = ip; } if (m_config.gpio) { m_config.gpio->cb_remote = ip; } m_config.firmware.cb_remote = ip; m_event_handler->add_action([this]() { std::unique_ptr tmp; std::swap(m_discovery, tmp); init(); { auto handle = m_cb_status.handle(); handle->discovery_pending = false; } m_cb_status.notify_one(); }); } void charge_bridge::init() { if (m_config.can0.has_value()) { m_can_0_client = std::make_unique(m_config.can0.value()); } if (m_config.serial1.has_value()) { m_pty_1 = std::make_unique(m_config.serial1.value()); } if (m_config.serial2.has_value()) { m_pty_2 = std::make_unique(m_config.serial2.value()); } if (m_config.serial3.has_value()) { m_pty_3 = std::make_unique(m_config.serial3.value()); } if (m_config.plc.has_value()) { m_plc = std::make_unique(m_config.plc.value()); } if (m_config.bsp.has_value()) { m_bsp = std::make_unique(m_config.bsp.value()); } if (m_config.heartbeat.has_value()) { m_heartbeat = std::make_unique(m_config.heartbeat.value(), [this](bool connected) { { auto handle = m_cb_status.handle(); handle->is_connected = connected; } m_cb_status.notify_one(); }); } if (m_config.gpio.has_value()) { m_gpio = std::make_unique(m_config.gpio.value()); } } charge_bridge::~charge_bridge() { m_cb_status.notify_one(); } void charge_bridge::manage(everest::lib::io::event::fd_event_handler& handler, std::atomic_bool const& run, bool force_update) { using namespace std::chrono_literals; m_event_handler = &handler; m_force_firmware_update = force_update; auto action = [this](bool is_connected, bool discovery_pending, int& error_count) { if (discovery_pending) { if (m_discovery_active) { return; } m_discovery_active = true; m_event_handler->add_action([this]() { register_events(*m_event_handler); }); return; } if (m_was_connected and not is_connected) { if (error_count > 1) { m_event_handler->add_action([this]() { unregister_events(*m_event_handler); }); m_was_connected = false; } else { error_count++; } } if (not m_was_connected) { if (update_firmware(m_force_firmware_update)) { m_event_handler->add_action([this]() { register_events(*m_event_handler); }); m_was_connected = true; error_count = 0; } } }; std::thread manager([&run, action, this]() { auto handle = m_cb_status.handle(); bool last_is_connected = handle->is_connected; bool last_discovery_pending = handle->discovery_pending; int error_count = 0; auto condition = [&] { if (handle->is_connected not_eq last_is_connected) { return true; } if (handle->discovery_pending not_eq last_discovery_pending) { return true; } if (not run.load()) { return true; } return false; }; while (run.load()) { action(handle->is_connected, handle->discovery_pending, error_count); handle.wait_for(condition, 10s); last_is_connected = handle->is_connected; last_discovery_pending = handle->discovery_pending; } }); manager.detach(); } bool charge_bridge::update_firmware(bool force) { firmware_update::sync_fw_updater updater(m_config.firmware); auto is_connected = updater.quick_check_connection(); if (not is_connected) { return false; } updater.print_fw_version(); auto do_update = force or (m_config.firmware.fw_update_on_start and not updater.check_if_correct_fw_installed()); if (not do_update) { return true; } auto result = updater.upload_fw() && updater.check_connection(); if (not result) { std::cout << "Error: could not install correct firmware version" << std::endl; } return result; } std::string charge_bridge::get_pty_1_slave_path() { if (m_pty_1) { return m_pty_1->get_slave_path(); } return ""; } std::string charge_bridge::get_pty_2_slave_path() { if (m_pty_2) { return m_pty_2->get_slave_path(); } return ""; } std::string charge_bridge::get_pty_3_slave_path() { if (m_pty_3) { return m_pty_3->get_slave_path(); } return ""; } bool charge_bridge::register_events(everest::lib::io::event::fd_event_handler& handler) { auto result = true; if (m_can_0_client) { result = handler.register_event_handler(m_can_0_client.get()) && result; } if (m_pty_1) { result = handler.register_event_handler(m_pty_1.get()) && result; } if (m_pty_2) { result = handler.register_event_handler(m_pty_2.get()) && result; } if (m_pty_3) { result = handler.register_event_handler(m_pty_3.get()) && result; } if (m_bsp) { result = handler.register_event_handler(m_bsp.get()) && result; } if (m_plc) { result = handler.register_event_handler(m_plc.get()) && result; } if (m_heartbeat) { result = handler.register_event_handler(m_heartbeat.get()) && result; } if (m_gpio) { result = handler.register_event_handler(m_gpio.get()) && result; } if (m_discovery) { result = handler.register_event_handler(m_discovery.get()) && result; } return result; } bool charge_bridge::unregister_events(everest::lib::io::event::fd_event_handler& handler) { auto result = true; if (m_can_0_client) { result = handler.unregister_event_handler(m_can_0_client.get()) && result; } if (m_pty_1) { result = handler.unregister_event_handler(m_pty_1.get()) && result; } if (m_pty_2) { result = handler.unregister_event_handler(m_pty_2.get()) && result; } if (m_pty_3) { result = handler.unregister_event_handler(m_pty_3.get()) && result; } if (m_bsp) { result = handler.unregister_event_handler(m_bsp.get()) && result; } if (m_plc) { result = handler.unregister_event_handler(m_plc.get()) && result; } if (m_heartbeat) { result = handler.unregister_event_handler(m_heartbeat.get()) && result; } if (m_gpio) { result = handler.unregister_event_handler(m_gpio.get()) && result; } if (m_discovery) { result = handler.unregister_event_handler(m_discovery.get()) && result; } return result; } void charge_bridge::print_config() { print_charge_bridge_config(m_config); } void print_charge_bridge_config(charge_bridge_config const& c) { using namespace utilities; std::cout << "ChargeBridge: " << c.cb_name << std::endl; std::cout << " * remote: " << c.cb_remote << std::endl; if (c.serial1) { std::cout << " * serial 1: " << c.serial1->serial_device; if (c.heartbeat.has_value() && CB_NUMBER_OF_UARTS >= 1) { std::cout << " " << to_string(c.heartbeat->cb_config.uarts[0]); } std::cout << std::endl; } if (c.serial2) { std::cout << " * serial 2: " << c.serial2->serial_device; if (c.heartbeat.has_value() && CB_NUMBER_OF_UARTS >= 2) { std::cout << " " << to_string(c.heartbeat->cb_config.uarts[1]); } std::cout << std::endl; } if (c.serial3) { std::cout << " * serial 3: " << c.serial3->serial_device; if (c.heartbeat.has_value() && CB_NUMBER_OF_UARTS >= 3) { std::cout << " " << to_string(c.heartbeat->cb_config.uarts[2]); } std::cout << std::endl; } if (c.can0) { std::cout << " * can 0: " << c.can0->can_device; if (c.heartbeat.has_value()) { std::cout << " " << to_string(c.heartbeat->cb_config.can.baudrate) << "bps" << std::endl; } } if (c.plc) { std::cout << " * plc: " << c.plc->plc_tap << std::flush; std::cout << " " << c.cb_remote << ":" << c.plc->cb_port; std::cout << " adress " << c.plc->plc_ip; std::cout << " netmask " << c.plc->plc_netmaks; std::cout << " MTU " << c.plc->plc_mtu << std::endl; } if (c.bsp) { if (c.bsp->api.evse.enabled) { std::cout << " * evse_bsp: "; } else if (c.bsp->api.ev.enabled) { std::cout << " * ev_bsp: "; } std::cout << c.bsp->cb_remote << ":" << c.bsp->cb_port; std::cout << " module " << c.bsp->api.evse.module_id; std::cout << " MQTT " << c.bsp->api.mqtt_remote << ":" << c.bsp->api.mqtt_port; if (not c.bsp->api.mqtt_bind.empty()) { std::cout << " on " << c.bsp->api.mqtt_bind; } std::cout << " ping " << c.bsp->api.mqtt_ping_interval_ms << "ms"; if (c.bsp->api.ovm.enabled) { std::cout << " OVM module " << c.bsp->api.ovm.module_id; } std::cout << std::endl; } if (c.heartbeat) { std::cout << " * heartbeat: " << c.cb_remote << ":" << c.cb_port; std::cout << " heartbeat interval " << c.heartbeat->interval_s << "s" << std::endl; } if (c.gpio) { std::cout << " * gpio: " << c.cb_remote << ":" << c.cb_port; std::cout << " MQTT " << c.gpio->mqtt_remote << ":" << c.gpio->mqtt_port; if (not c.gpio->mqtt_bind.empty()) { std::cout << " on " << c.gpio->mqtt_bind; } std::cout << " send interval " << c.gpio->interval_s << "s" << std::endl; } std::cout << "\n" << std::endl; } } // namespace charge_bridge