Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- 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
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,40 @@
#
# 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
add_subdirectory(yeti_comms)
add_subdirectory(yeti_fwupdate)
target_include_directories(${MODULE_NAME}
PRIVATE
"common"
"yeti_comms"
"yeti_comms/nanopb"
"yeti_comms/protobuf"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
yeti_comms
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"powermeter/powermeterImpl.cpp"
"board_support/evse_board_supportImpl.cpp"
"rcd/ac_rcdImpl.cpp"
"connector_lock/connector_lockImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
install(FILES yetiR1_2.2_firmware.bin DESTINATION ${CMAKE_INSTALL_DATADIR}/everest/modules/YetiDriver/firmware)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,172 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#include "YetiDriver.hpp"
#include <fmt/core.h>
#include <utils/date.hpp>
namespace module {
void YetiDriver::init() {
// initialize serial driver
if (!serial.openDevice(config.serial_port.c_str(), config.baud_rate)) {
EVLOG_error << "Could not open serial port " << config.serial_port << " with baud rate " << config.baud_rate;
return;
}
telemetry_power_path_controller_version = {{"timestamp", ""},
{"type", "power_path_controller_version"},
{"hardware_version", 1},
{"software_version", "1.00"},
{"date_manufactured", "N/A"},
{"operating_time_h", 5},
{"operating_time_h_warning", 5000},
{"operating_time_h_error", 6000},
{"software_version", "1.00"},
{"error", false}};
telemetry_power_path_controller = {{"timestamp", ""},
{"type", "power_path_controller"},
{"cp_voltage_high", 0.0},
{"cp_voltage_low", 0.0},
{"cp_pwm_duty_cycle", 0.0},
{"cp_state", "A1"},
{"pp_ohm", 0.0},
{"supply_voltage_12V", 0.0},
{"supply_voltage_minus_12V", 0.0},
{"temperature_controller", 0.0},
{"temperature_car_connector", 0.0},
{"watchdog_reset_count", 0.0},
{"error", false}};
telemetry_power_switch = {{"timestamp", ""},
{"type", "power_switch"},
{"switching_count", 0},
{"switching_count_warning", 30000},
{"switching_count_error", 50000},
{"is_on", false},
{"time_to_switch_on_ms", 100},
{"time_to_switch_off_ms", 100},
{"temperature_C", 0.0},
{"error", false},
{"error_over_current", false}};
telemetry_rcd = {{"timestamp", ""}, //
{"type", "rcd"}, //
{"enabled", true}, //
{"current_mA", 0.0}, //
{"triggered", false}, //
{"error", false}}; //
invoke_init(*p_powermeter);
invoke_init(*p_board_support);
invoke_init(*p_connector_lock);
invoke_init(*p_rcd);
}
void YetiDriver::ready() {
serial.run();
if (!serial.reset(config.reset_gpio_chip, config.reset_gpio)) {
EVLOG_error << "Yeti reset not successful.";
}
serial.signalSpuriousReset.connect([this]() { EVLOG_error << "Yeti uC spurious reset!"; });
serial.signalConnectionTimeout.connect([this]() { EVLOG_error << "Yeti UART timeout!"; });
invoke_ready(*p_powermeter);
invoke_ready(*p_board_support);
invoke_ready(*p_connector_lock);
invoke_ready(*p_rcd);
telemetryThreadHandle = std::thread([this]() {
while (!telemetryThreadHandle.shouldExit()) {
sleep(10);
{
std::scoped_lock lock(telemetry_mutex);
publish_external_telemetry_livedata("power_path_controller", telemetry_power_path_controller);
publish_external_telemetry_livedata("rcd", telemetry_rcd);
publish_external_telemetry_livedata("power_path_controller_version",
telemetry_power_path_controller_version);
}
}
});
serial.signalErrorFlags.connect([this](ErrorFlags e) { error_handling(e); });
if (not serial.is_open()) {
auto err = p_board_support->error_factory->create_error("evse_board_support/CommunicationFault", "",
"Could not open serial port.");
p_board_support->raise_error(err);
}
}
void YetiDriver::publish_external_telemetry_livedata(const std::string& topic, const Everest::TelemetryMap& data) {
if (info.telemetry_enabled) {
telemetry.publish("livedata", topic, data);
}
}
bool rcd_selftest_failed;
bool connector_lock_failed;
bool cp_signal_fault;
void YetiDriver::clear_errors_on_unplug() {
if (error_MREC2GroundFailure) {
p_board_support->clear_error("evse_board_support/MREC2GroundFailure");
}
error_MREC2GroundFailure = false;
if (error_MREC1ConnectorLockFailure) {
p_connector_lock->clear_error("connector_lock/MREC1ConnectorLockFailure");
}
error_MREC1ConnectorLockFailure = false;
}
void YetiDriver::error_handling(ErrorFlags e) {
if (e.diode_fault and not last_error_flags.diode_fault) {
Everest::error::Error error_object = p_board_support->error_factory->create_error(
"evse_board_support/DiodeFault", "", "Diode Fault", Everest::error::Severity::High);
p_board_support->raise_error(error_object);
} else if (not e.diode_fault and last_error_flags.diode_fault) {
p_board_support->clear_error("evse_board_support/DiodeFault");
}
if (e.rcd_triggered and not last_error_flags.rcd_triggered) {
Everest::error::Error error_object = p_board_support->error_factory->create_error(
"evse_board_support/MREC2GroundFailure", "", "Onboard RCD triggered", Everest::error::Severity::High);
p_board_support->raise_error(error_object);
error_MREC2GroundFailure = true;
}
if (e.ventilation_not_available and not last_error_flags.ventilation_not_available) {
Everest::error::Error error_object =
p_board_support->error_factory->create_error("evse_board_support/VentilationNotAvailable", "",
"State D is not supported", Everest::error::Severity::High);
p_board_support->raise_error(error_object);
} else if (not e.ventilation_not_available and last_error_flags.ventilation_not_available) {
p_board_support->clear_error("evse_board_support/VentilationNotAvailable");
}
if (e.connector_lock_failed and not last_error_flags.connector_lock_failed) {
Everest::error::Error error_object = p_connector_lock->error_factory->create_error(
"connector_lock/MREC1ConnectorLockFailure", "", "Lock motor failure", Everest::error::Severity::High);
error_MREC1ConnectorLockFailure = true;
p_connector_lock->raise_error(error_object);
}
if (e.cp_signal_fault and not last_error_flags.cp_signal_fault) {
Everest::error::Error error_object = p_board_support->error_factory->create_error(
"evse_board_support/MREC14PilotFault", "", "CP error", Everest::error::Severity::High);
p_board_support->raise_error(error_object);
} else if (not e.cp_signal_fault and last_error_flags.cp_signal_fault) {
p_board_support->clear_error("evse_board_support/MREC14PilotFault");
}
last_error_flags = e;
}
} // namespace module

View File

@@ -0,0 +1,96 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef YETI_DRIVER_HPP
#define YETI_DRIVER_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/ac_rcd/Implementation.hpp>
#include <generated/interfaces/connector_lock/Implementation.hpp>
#include <generated/interfaces/evse_board_support/Implementation.hpp>
#include <generated/interfaces/powermeter/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
#include "yeti_comms/evSerial.h"
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string serial_port;
int baud_rate;
std::string reset_gpio_chip;
int reset_gpio;
int caps_min_current_A;
int caps_max_current_A;
};
class YetiDriver : public Everest::ModuleBase {
public:
YetiDriver() = delete;
YetiDriver(const ModuleInfo& info, Everest::TelemetryProvider& telemetry,
std::unique_ptr<powermeterImplBase> p_powermeter,
std::unique_ptr<evse_board_supportImplBase> p_board_support, std::unique_ptr<ac_rcdImplBase> p_rcd,
std::unique_ptr<connector_lockImplBase> p_connector_lock, Conf& config) :
ModuleBase(info),
telemetry(telemetry),
p_powermeter(std::move(p_powermeter)),
p_board_support(std::move(p_board_support)),
p_rcd(std::move(p_rcd)),
p_connector_lock(std::move(p_connector_lock)),
config(config){};
Everest::TelemetryProvider& telemetry;
const std::unique_ptr<powermeterImplBase> p_powermeter;
const std::unique_ptr<evse_board_supportImplBase> p_board_support;
const std::unique_ptr<ac_rcdImplBase> p_rcd;
const std::unique_ptr<connector_lockImplBase> p_connector_lock;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
void publish_external_telemetry_livedata(const std::string& topic, const Everest::TelemetryMap& data);
evSerial serial;
void clear_errors_on_unplug();
// 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
Everest::TelemetryMap telemetry_power_path_controller_version;
Everest::TelemetryMap telemetry_power_path_controller;
Everest::TelemetryMap telemetry_power_switch;
Everest::TelemetryMap telemetry_rcd;
std::mutex telemetry_mutex;
Everest::Thread telemetryThreadHandle;
void error_handling(ErrorFlags e);
ErrorFlags last_error_flags;
std::atomic_bool error_MREC2GroundFailure{false};
std::atomic_bool error_MREC1ConnectorLockFailure{false};
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
};
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
Everest::json power_meter_data_to_json(const PowerMeter& p);
Everest::json keep_alive_lo_to_json(const KeepAliveLo& k);
std::string error_type_to_string(ErrorFlags s);
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
} // namespace module
#endif // YETI_DRIVER_HPP

View File

@@ -0,0 +1,200 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "evse_board_supportImpl.hpp"
namespace module {
namespace board_support {
static types::board_support_common::BspEvent cast_event_type(CpState cp_state) {
types::board_support_common::BspEvent event;
switch (cp_state) {
case CpState_STATE_A:
event.event = types::board_support_common::Event::A;
break;
case CpState_STATE_B:
event.event = types::board_support_common::Event::B;
break;
case CpState_STATE_C:
event.event = types::board_support_common::Event::C;
break;
case CpState_STATE_D:
event.event = types::board_support_common::Event::D;
break;
case CpState_STATE_E:
event.event = types::board_support_common::Event::E;
break;
case CpState_STATE_F:
event.event = types::board_support_common::Event::F;
break;
}
return event;
}
static types::board_support_common::BspEvent cast_event_type(bool relais_state) {
types::board_support_common::BspEvent event;
if (relais_state) {
event.event = types::board_support_common::Event::PowerOn;
} else {
event.event = types::board_support_common::Event::PowerOff;
}
return event;
}
static types::board_support_common::ProximityPilot cast_pp_type(PpState pp_state) {
types::board_support_common::ProximityPilot pp;
switch (pp_state) {
case PpState_STATE_13A:
pp.ampacity = types::board_support_common::Ampacity::A_13;
break;
case PpState_STATE_20A:
pp.ampacity = types::board_support_common::Ampacity::A_20;
break;
case PpState_STATE_32A:
pp.ampacity = types::board_support_common::Ampacity::A_32;
break;
case PpState_STATE_70A:
pp.ampacity = types::board_support_common::Ampacity::A_63_3ph_70_1ph;
break;
case PpState_STATE_FAULT:
pp.ampacity = types::board_support_common::Ampacity::None;
break;
case PpState_STATE_NC:
pp.ampacity = types::board_support_common::Ampacity::None;
break;
}
return pp;
}
void evse_board_supportImpl::init() {
{
std::lock_guard<std::mutex> lock(capsMutex);
caps.min_current_A_import = 6;
caps.max_current_A_import = 16;
caps.min_phase_count_import = 1;
caps.max_phase_count_import = 3;
caps.supports_changing_phases_during_charging = false;
caps.supports_cp_state_E = false;
caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Cable;
caps.min_current_A_export = 6;
caps.max_current_A_export = 16;
caps.min_phase_count_export = 1;
caps.max_phase_count_export = 3;
}
mod->serial.signalCPState.connect([this](CpState cp_state) {
if (cp_state not_eq last_cp_state) {
auto event_cp_state = cast_event_type(cp_state);
EVLOG_info << "CP state changed: "
<< types::board_support_common::event_to_string(cast_event_type(last_cp_state).event) << " -> "
<< types::board_support_common::event_to_string(event_cp_state.event);
if (enabled) {
publish_event(event_cp_state);
}
if (cp_state == CpState_STATE_A) {
mod->clear_errors_on_unplug();
}
last_cp_state = cp_state;
}
});
mod->serial.signalRelaisState.connect([this](bool relais_state) {
if (last_relais_state not_eq relais_state) {
publish_event(cast_event_type(relais_state));
last_relais_state = relais_state;
}
});
mod->serial.signalPPState.connect([this](PpState pp_state) {
last_pp = cast_pp_type(pp_state);
publish_ac_pp_ampacity(last_pp);
});
mod->serial.signalKeepAliveLo.connect([this](KeepAliveLo l) {
std::lock_guard<std::mutex> lock(capsMutex);
caps.min_current_A_import =
(mod->config.caps_min_current_A >= 0 ? mod->config.caps_min_current_A : l.hwcap_min_current);
caps.max_current_A_import =
(mod->config.caps_max_current_A >= 0 ? mod->config.caps_max_current_A : l.hwcap_max_current);
caps.min_phase_count_import = l.hwcap_min_phase_count;
caps.max_phase_count_import = l.hwcap_max_phase_count;
caps.min_current_A_export =
(mod->config.caps_min_current_A >= 0 ? mod->config.caps_min_current_A : l.hwcap_min_current);
caps.max_current_A_export =
(mod->config.caps_max_current_A >= 0 ? mod->config.caps_max_current_A : l.hwcap_max_current);
caps.min_phase_count_export = l.hwcap_min_phase_count;
caps.max_phase_count_export = l.hwcap_max_phase_count;
caps.supports_changing_phases_during_charging = l.supports_changing_phases_during_charging;
if (not caps_received) {
EVLOG_info << "Yeti Controller Configuration:";
EVLOG_info << " Hardware revision: " << l.hw_revision;
EVLOG_info << " Firmware version: " << l.sw_version_string;
EVLOG_info << " Current Limit: " << l.hwcap_max_current;
}
caps_received = true;
});
}
void evse_board_supportImpl::ready() {
wait_for_caps();
{
// Publish caps once in the beginning
std::lock_guard<std::mutex> lock(capsMutex);
publish_capabilities(caps);
}
}
void evse_board_supportImpl::wait_for_caps() {
// Wait for caps to be received at least once
int i;
for (i = 0; i < 50; i++) {
if (caps_received)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (i == 50) {
EVLOG_error << "Did not receive hardware capabilities from Yeti hardware, using defaults.";
}
}
void evse_board_supportImpl::handle_pwm_on(double& value) {
mod->serial.setPWM(value * 100);
}
void evse_board_supportImpl::handle_cp_state_X1() {
mod->serial.setPWM(10001);
}
void evse_board_supportImpl::handle_cp_state_F() {
mod->serial.setPWM(0);
}
void evse_board_supportImpl::handle_cp_state_E() {
EVLOG_warning << "Command cp_state_E is not supported. Ignoring command.";
}
void evse_board_supportImpl::handle_allow_power_on(types::evse_board_support::PowerOnOff& value) {
mod->serial.allowPowerOn(value.allow_power_on);
}
void evse_board_supportImpl::handle_ac_set_overcurrent_limit_A(double& value) {
// your code for cmd ac_set_overcurrent_limit_A goes here
}
void evse_board_supportImpl::handle_ac_switch_three_phases_while_charging(bool& value) {
mod->serial.set_number_of_phases(value);
}
void evse_board_supportImpl::handle_enable(bool& value) {
enabled = true;
// Publish CP state once on enable
publish_event(cast_event_type(last_cp_state));
}
} // namespace board_support
} // namespace module

View File

@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP
#define BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/evse_board_support/Implementation.hpp>
#include "../YetiDriver.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace board_support {
struct Conf {};
class evse_board_supportImpl : public evse_board_supportImplBase {
public:
evse_board_supportImpl() = delete;
evse_board_supportImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<YetiDriver>& mod, Conf& config) :
evse_board_supportImplBase(ev, "board_support"), 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 void handle_enable(bool& value) override;
virtual void handle_pwm_on(double& value) override;
virtual void handle_cp_state_X1() override;
virtual void handle_cp_state_F() override;
virtual void handle_cp_state_E() override;
virtual void handle_allow_power_on(types::evse_board_support::PowerOnOff& value) override;
virtual void handle_ac_switch_three_phases_while_charging(bool& value) override;
virtual void handle_ac_set_overcurrent_limit_A(double& value) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<YetiDriver>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
types::evse_board_support::HardwareCapabilities caps;
std::atomic_bool caps_received{false};
std::mutex capsMutex;
CpState last_cp_state{CpState::CpState_STATE_F};
bool last_relais_state{false};
types::board_support_common::ProximityPilot last_pp{types::board_support_common::Ampacity::None};
void wait_for_caps();
std::atomic_bool enabled{false};
// 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 board_support
} // namespace module
#endif // BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "connector_lockImpl.hpp"
namespace module {
namespace connector_lock {
void connector_lockImpl::init() {
mod->serial.signalLockState.connect([this](bool l) { lock_state = l; });
}
void connector_lockImpl::ready() {
}
void connector_lockImpl::handle_lock() {
}
void connector_lockImpl::handle_unlock() {
mod->serial.forceUnlock();
}
} // namespace connector_lock
} // namespace module

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef CONNECTOR_LOCK_CONNECTOR_LOCK_IMPL_HPP
#define CONNECTOR_LOCK_CONNECTOR_LOCK_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/connector_lock/Implementation.hpp>
#include "../YetiDriver.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace connector_lock {
struct Conf {};
class connector_lockImpl : public connector_lockImplBase {
public:
connector_lockImpl() = delete;
connector_lockImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<YetiDriver>& mod, Conf& config) :
connector_lockImplBase(ev, "connector_lock"), 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 void handle_lock() override;
virtual void handle_unlock() override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<YetiDriver>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
std::atomic_bool lock_state{false};
// 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 connector_lock
} // namespace module
#endif // CONNECTOR_LOCK_CONNECTOR_LOCK_IMPL_HPP

View File

@@ -0,0 +1,171 @@
.. _everest_modules_handwritten_YetiDriver:
.. **********
.. YetiDriver
.. **********
The module ``YetiDriver`` is a board support driver for Pionix Yeti Power
Board.
Communication between the Yeti microcontroller and this driver module
=====================================================================
The hardware connection between Yeti and Yak (the board running EVerest and
this module) is 3.3V TTL UART plus 2 GPIOs (one to reset the microcontroller
from Linux and one to wakeup Linux from the microcontroller, which is
currrently unused).
The default configuration is 115200 bps 8N1.
Protocol
========
EVerest can send commands to Yeti and Yeti publishes data and events back
to EVerest. The packets are defined with protobuf to serialize the C structs
into a binary representation that is transferred over the serial wire in a
stream:
https://developers.google.com/protocol-buffers
To be able to split the stream back into packets all data is encoded using COBS
before it is transmitted on the UART:
https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
COBS
----
COBS is implemented in ``yeti_comms/evSerial.cpp``. Whenever a new packet
was extracted from the stream ``handlePacket()`` is called to decode protobuf
and generate the corresponding signals.
Other parts of the module subscribe to these signals to handle the incoming
packets.
For TX ``linkWrite`` encodes the packet with COBS and outputs it to the UART.
Protobuf
--------
The actual packet definitions are located under ``yeti_comms/protobuf``.
``hi2lo.proto`` contains all messages that can be sent from EVerest to Yeti
while ``lo2hi.proto`` defines all messages that Yeti sends to EVerest.
Refer to these files for an up to date definition as they may change
frequently.
To generate the C code nanopb is used:
``nanopb_generator -I . -D . *.proto``
The output should also be manually copied to Yeti Firmware to ensure the same
definition is used on both sides when making changes.
EVerest to Yeti
---------------
The most important commands that EVerest sends to Yeti are the following:
``SetControlMode(mode)``: Yeti firmware can operate in different modes:
``Mode NONE = 0``: In this mode Yeti does not allow control over UART. It will
still send telemetry data. Yeti operates as a standalone non-smart AC charger
and EVerest does not need to be running.
``HIGH = 1``: In this mode high level control is possible.
Yeti operates as a standalone AC charger and EVerest does not need to be
running, but it does allow certain control such as setMaxCurrent from EVerest.
This mode is not documented here as it is not used by EVerest anymore.
``LOW = 2``: In this mode Yeti allows low level control. Yeti does not act
as a standalone charger, it needs to be controlled by EVerest. It does however
still run the very basic state machine to follow the car's states A-F and
switches relais on and off accordingly. This ensures that basic electrical
safety remains within the microcontroller and not within EVerest.
It generates more human readable events from state A-F transitions.
PWM is directly controlled from EVerest in this mode.
Low control mode:
_________________
The following commands describe the Low level control mode only:
``AllowPowerOn(bool)``: Inform yeti that it is allowed to switch on the power
relais/contactors to the car on (true) or must switch off now (false). The
final decision remains with Yeti in case of power on, it should only power on
after all other requirements are met (such as RCD current is below limit,
car is in CP state B etc). On power off Yeti must switch off immediately.
``SetPWM(mode, duty_cycle)``: mode 0: OFF (+12V), 1: ON (PWM with duty_cycle),
2: F (-12V). Yeti sets the PWM immediately.
Other commands for all modes:
_____________________________
``FirmwareUpdate(bool)``: Send true to reboot Yeti into ROM boot loader.
After that stm32flash tool can be used to flash any firmware binary to it.
Note that this is a dev kit and for a real product this needs to be implemented
differently.
``KeepAliveHi``: Send this packet to Yeti at 1Hz. If no heartbeat is received
for a longer amount of time Yeti may fall back to control mode NONE to act
as a stand alone emergency backup charger or go into failure mode (can be
modified in the firmware).
``SetThreePhases``: true: switches to 3ph on next switch on, else single phase.
Only works on hardware configurations with dual relais. Does not switch while
charging session is running, Yeti firmware will delay the change to the next
charging session.
``EnableRCD``: enable or disable the onboard RCD. Some cars generate quite high
residual current spikes and may not charge properly if RCD is enabled.
``Enable``: Enable CP output
``Disable``: Disable CP output (goes to floating/high impedance)
``Reset``: Reset yeti firmware
``Replug``: Initiate special virtual replug sequence without starting a new
charging session.
``SwitchThreePhasesWhileCharging``: Change between 1 and 3 phases while
charging. This is currently not implemented in yeti firmware and will need
special precautions because some cars may be destroyed by switching from one
phase to three phase while charging is running (E.g. Zoe 1)
``ForceUnlock``: Force unlock motor lock now regardless of state.
Yeti to EVerest
---------------
The following messages are relevant for LOW control mode:
``Event``: This is the most important message from Yeti. It will send an event
on CP transitions:
* ``CAR_PLUGGED_IN``: CP State A -> B
* ``CAR_REQUESTED_POWER``: CP State B->C or B->D
* ``POWER_ON``: Relais switched on succesfully (i.e. after mirror contact check)
* ``POWER_OFF``: Relais switched off succesfully
* ``CAR_REQUESTED_STOP_POWER``: CP State C/D -> any other state
* ``CAR_UNPLUGGED``: any other state -> A
* ``ERROR_E``: any other state -> E
* ``ERROR_DF``: Car diode failure detected
* ``ERROR_RELAIS``: Relais error (mirror contact check failed)
* ``ERROR_RCD``:: RCD over current event
* ``ERROR_VENTILATION_NOT_AVAILABLE``: Car requested D but no ventilation available
* ``ERROR_OVER_CURRENT``: Yeti detected quick over current on AC lines
* ``ENTER_BCD``: any other state -> B/C/D. Used to start SLAC
* ``LEAVE_BCD``: B/C/D -> any other state. Stops SLAC.
* ``PERMANENT_FAULT``: Permanent fault that cannot be cleared by unplugging car
``PowerMeter``: Contains all data from the power measurement, sent at roughly
1Hz
``KeepAliveLo``: Yeti sends this at 1Hz to keep up connection.
``ResetDone``: Sent once on boot of yeti firmware.

View File

@@ -0,0 +1,50 @@
description: Driver module for the YETI hardware v1.0
config:
serial_port:
description: Serial port the Yeti hardware is connected to
type: string
default: /dev/ttyUSB0
baud_rate:
description: Serial baud rate to use when communicating with Yeti hardware
type: integer
minimum: 9600
maximum: 230400
default: 115200
reset_gpio_chip:
description: >-
Reset GPIO chip to use to HW reset Yeti. If set to empty string, it is disabled.
type: string
default: 'gpiochip0'
reset_gpio:
description: GPIO line to use to reset Yeti
type: integer
default: 27
caps_min_current_A:
description: Minimal current on AC side. For AC this is typically 6, but for HLC this can be less. -1 means use limit reported by HW.
type: integer
default: -1
caps_max_current_A:
description: Maximum current on AC side. For AC this is typically 16 or 32, but for HLC this can be less. -1 means use limit reported by HW.
type: integer
default: -1
provides:
powermeter:
interface: powermeter
description: provides the Yeti Internal Power Meter
board_support:
interface: evse_board_support
description: provides the board support Interface to low level control control pilot, relais, motor lock
rcd:
interface: ac_rcd
description: RCD interface of the onboard RCD
connector_lock:
interface: connector_lock
description: Interface for the motor lock
enable_telemetry: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Cornelius Claussen
- Kai-Uwe Hermann
- Thilo Molitor
- Anton Wöllert

View File

@@ -0,0 +1,71 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#include "powermeterImpl.hpp"
#include <utils/date.hpp>
namespace module {
namespace powermeter {
static types::powermeter::Powermeter yeti_to_everest(const PowerMeter& p) {
types::powermeter::Powermeter j;
j.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
j.meter_id = "YETI_POWERMETER";
j.phase_seq_error = p.phaseSeqError;
j.energy_Wh_import.total = p.totalWattHr;
j.energy_Wh_import.L1 = p.wattHrL1;
j.energy_Wh_import.L2 = p.wattHrL2;
j.energy_Wh_import.L3 = p.wattHrL3;
types::units::Power pwr;
pwr.total = p.wattL1 + p.wattL2 + p.wattL3;
pwr.L1 = p.wattL1;
pwr.L2 = p.wattL2;
pwr.L3 = p.wattL3;
j.power_W = pwr;
types::units::Voltage volt;
volt.L1 = p.vrmsL1;
volt.L2 = p.vrmsL2;
volt.L3 = p.vrmsL3;
j.voltage_V = volt;
types::units::Current amp;
amp.L1 = p.irmsL1;
amp.L2 = p.irmsL2;
amp.L3 = p.irmsL3;
amp.N = p.irmsN;
j.current_A = amp;
types::units::Frequency freq;
freq.L1 = p.freqL1;
freq.L2 = p.freqL2;
freq.L3 = p.freqL3;
j.frequency_Hz = freq;
return j;
}
void powermeterImpl::init() {
mod->serial.signalPowerMeter.connect([this](const PowerMeter& p) { publish_powermeter(yeti_to_everest(p)); });
}
void powermeterImpl::ready() {
}
types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) {
return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED,
{},
{},
"YetiDriver powermeter does not support the stop_transaction command"};
};
types::powermeter::TransactionStartResponse
powermeterImpl::handle_start_transaction(types::powermeter::TransactionReq& value) {
return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED,
"YetiDriver powermeter does not support the start_transaction command"};
}
} // namespace powermeter
} // namespace module

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef POWERMETER_POWERMETER_IMPL_HPP
#define POWERMETER_POWERMETER_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/powermeter/Implementation.hpp>
#include "../YetiDriver.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace powermeter {
struct Conf {};
class powermeterImpl : public powermeterImplBase {
public:
powermeterImpl() = delete;
powermeterImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<YetiDriver>& mod, Conf& config) :
powermeterImplBase(ev, "powermeter"), 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::powermeter::TransactionStartResponse
handle_start_transaction(types::powermeter::TransactionReq& value) override;
virtual types::powermeter::TransactionStopResponse handle_stop_transaction(std::string& transaction_id) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<YetiDriver>& 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 powermeter
} // namespace module
#endif // POWERMETER_POWERMETER_IMPL_HPP

View File

@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "ac_rcdImpl.hpp"
namespace module {
namespace rcd {
void ac_rcdImpl::init() {
}
void ac_rcdImpl::ready() {
}
void ac_rcdImpl::handle_self_test() {
// your code for cmd self_test goes here
}
bool ac_rcdImpl::handle_reset() {
// your code for cmd reset goes here
return true;
}
} // namespace rcd
} // namespace module

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef RCD_AC_RCD_IMPL_HPP
#define RCD_AC_RCD_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/ac_rcd/Implementation.hpp>
#include "../YetiDriver.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace rcd {
struct Conf {};
class ac_rcdImpl : public ac_rcdImplBase {
public:
ac_rcdImpl() = delete;
ac_rcdImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<YetiDriver>& mod, Conf& config) :
ac_rcdImplBase(ev, "rcd"), 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 void handle_self_test() override;
virtual bool handle_reset() override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<YetiDriver>& 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 rcd
} // namespace module
#endif // RCD_AC_RCD_IMPL_HPP

View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.10)
# set the project name
project(yeti_comms VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# add the executable
add_library(yeti_comms STATIC)
ev_register_library_target(yeti_comms)
target_sources(yeti_comms
PRIVATE
evSerial.cpp
protobuf/yeti.pb.c
)
target_include_directories(yeti_comms
PUBLIC
"${PROJECT_BINARY_DIR}"
protobuf
)
target_link_libraries(yeti_comms
PUBLIC
date::date-tz
everest::nanopb
PRIVATE
Pal::Sigslot
everest::framework
everest::gpio
)

View File

@@ -0,0 +1,410 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include "evSerial.h"
#include <cerrno>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <fcntl.h>
#include <unistd.h>
#include <date/date.h>
#include <date/tz.h>
#include <everest/3rd_party/nanopb/pb_decode.h>
#include <everest/3rd_party/nanopb/pb_encode.h>
#include <everest/gpio/gpio.hpp>
#include "yeti.pb.h"
evSerial::evSerial() {
fd = 0;
baud = 0;
reset_done_flag = false;
forced_reset = false;
cobsDecodeReset();
}
evSerial::~evSerial() {
if (fd)
close(fd);
}
bool evSerial::openDevice(const char* device, int _baud) {
fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
printf("Serial: error %d opening %s: %s\n", errno, device, strerror(errno));
return false;
} // else printf ("Serial: opened %s as %i\n", device, fd);
cobsDecodeReset();
switch (_baud) {
case 9600:
baud = B9600;
break;
case 19200:
baud = B19200;
break;
case 38400:
baud = B38400;
break;
case 57600:
baud = B57600;
break;
case 115200:
baud = B115200;
break;
case 230400:
baud = B230400;
break;
default:
baud = 0;
return false;
}
return setSerialAttributes();
}
bool evSerial::setSerialAttributes() {
struct termios tty;
if (tcgetattr(fd, &tty) != 0) {
printf("Serial: error %d from tcgetattr\n", errno);
return false;
}
cfsetospeed(&tty, baud);
cfsetispeed(&tty, baud);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as \000 chars
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY);
tty.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
tty.c_oflag = 0; // no remapping, no delays
tty.c_cc[VMIN] = 0; // read blocks
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
// enable reading
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Serial: error %d from tcsetattr\n", errno);
return false;
}
// printf ("Success setting tcsetattr\n");
return true;
}
void evSerial::cobsDecodeReset() {
code = 0xff;
block = 0;
decode = msg;
}
uint32_t evSerial::crc32(uint8_t* buf, int len) {
int i, j;
uint32_t b, crc, msk;
i = 0;
crc = 0xFFFFFFFF;
while (i < len) {
b = buf[i];
crc = crc ^ b;
for (j = 7; j >= 0; j--) {
msk = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & msk);
}
i = i + 1;
}
// printf("%X",crc);
return crc;
}
void evSerial::handlePacket(uint8_t* buf, int len) {
// printf ("packet received len %u\n", len);
// Check CRC32 (last 4 bytes)
// uint32_t crc = calculateCrc(rx_packet_buf, rx_packet_len);
if (crc32(buf, len)) {
printf("CRC mismatch\n");
return;
}
len -= 4;
McuToEverest msg_in;
pb_istream_t istream = pb_istream_from_buffer(buf, len);
if (pb_decode(&istream, McuToEverest_fields, &msg_in))
switch (msg_in.which_payload) {
case McuToEverest_keep_alive_tag:
// printf("Received keep_alive_lo\n");
signalKeepAliveLo(msg_in.payload.keep_alive);
// detect connection timeout if keep_alive packets stop coming...
last_keep_alive_lo_timestamp = date::utc_clock::now();
break;
case McuToEverest_power_meter_tag: {
auto unix_timestamp = std::chrono::seconds(std::time(NULL));
msg_in.payload.power_meter.time_stamp = unix_timestamp.count();
signalPowerMeter(msg_in.payload.power_meter);
} break;
case McuToEverest_cp_state_tag:
signalCPState(msg_in.payload.cp_state);
break;
case McuToEverest_pp_state_tag:
signalPPState(msg_in.payload.pp_state);
break;
case McuToEverest_relais_state_tag:
signalRelaisState(msg_in.payload.relais_state);
break;
case McuToEverest_lock_state_tag:
signalLockState(msg_in.payload.lock_state);
break;
case McuToEverest_error_flags_tag:
signalErrorFlags(msg_in.payload.error_flags);
break;
case McuToEverest_reset_tag:
// printf("Received reset_done\n");
reset_done_flag = true;
if (!forced_reset)
signalSpuriousReset();
break;
}
}
void evSerial::cobsDecode(uint8_t* buf, int len) {
for (int i = 0; i < len; i++)
cobsDecodeByte(buf[i]);
}
void evSerial::cobsDecodeByte(uint8_t byte) {
// check max length
if ((decode - msg == 2048 - 1) && byte != 0x00) {
printf("cobsDecode: Buffer overflow\n");
cobsDecodeReset();
}
if (block) {
// we're currently decoding and should not get a 0
if (byte == 0x00) {
// probably found some garbage -> reset
printf("cobsDecode: Garbage detected\n");
cobsDecodeReset();
return;
}
*decode++ = byte;
} else {
if (code != 0xff) {
*decode++ = 0;
}
block = code = byte;
if (code == 0x00) {
// we're finished, reset everything and commit
if (decode == msg) {
// we received nothing, just a 0x00
printf("cobsDecode: Received nothing\n");
} else {
// set back decode with one, as it gets post-incremented
handlePacket(msg, decode - 1 - msg);
}
cobsDecodeReset();
return; // need to return here, because of block--
}
}
block--;
}
void evSerial::run() {
readThreadHandle = std::thread(&evSerial::readThread, this);
timeoutDetectionThreadHandle = std::thread(&evSerial::timeoutDetectionThread, this);
}
void evSerial::timeoutDetectionThread() {
while (true) {
sleep(1);
if (timeoutDetectionThreadHandle.shouldExit())
break;
if (serial_timed_out())
signalConnectionTimeout();
}
}
void evSerial::readThread() {
uint8_t buf[2048];
int n;
cobsDecodeReset();
while (true) {
if (readThreadHandle.shouldExit())
break;
if (fd > 0) {
n = read(fd, buf, sizeof buf);
cobsDecode(buf, n);
}
}
}
bool evSerial::linkWrite(EverestToMcu* m) {
if (fd <= 0) {
return false;
}
uint8_t tx_packet_buf[1024];
uint8_t encode_buf[1500];
pb_ostream_t ostream = pb_ostream_from_buffer(tx_packet_buf, sizeof(tx_packet_buf) - 4);
bool status = pb_encode(&ostream, EverestToMcu_fields, m);
if (!status) {
// couldn't encode
return false;
}
size_t tx_payload_len = ostream.bytes_written;
// add crc32 (CRC-32/JAMCRC)
uint32_t crc = crc32(tx_packet_buf, tx_payload_len);
for (int byte_pos = 0; byte_pos < 4; ++byte_pos) {
tx_packet_buf[tx_payload_len] = (uint8_t)crc & 0xFF;
crc = crc >> 8;
tx_payload_len++;
}
size_t tx_encode_len = cobsEncode(tx_packet_buf, tx_payload_len, encode_buf);
// std::cout << "Write "<<tx_encode_len<<" bytes to serial port." << std::endl;
write(fd, encode_buf, tx_encode_len);
return true;
}
size_t evSerial::cobsEncode(const void* data, size_t length, uint8_t* buffer) {
uint8_t* encode = buffer; // Encoded byte pointer
uint8_t* codep = encode++; // Output code pointer
uint8_t code = 1; // Code value
for (const uint8_t* byte = (const uint8_t*)data; length--; ++byte) {
if (*byte) // Byte not zero, write it
*encode++ = *byte, ++code;
if (!*byte || code == 0xff) // Input is zero or block completed, restart
{
*codep = code, code = 1, codep = encode;
if (!*byte || length)
++encode;
}
}
*codep = code; // Write final code value
// add final 0
*encode++ = 0x00;
return encode - buffer;
}
bool evSerial::serial_timed_out() {
auto now = date::utc_clock::now();
auto timeSinceLastKeepAlive =
std::chrono::duration_cast<std::chrono::milliseconds>(now - last_keep_alive_lo_timestamp).count();
if (timeSinceLastKeepAlive >= 5000)
return true;
return false;
}
void evSerial::setPWM(uint32_t dc) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_pwm_duty_cycle_tag;
msg_out.payload.pwm_duty_cycle = dc;
linkWrite(&msg_out);
}
void evSerial::allowPowerOn(bool p) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_allow_power_on_tag;
msg_out.payload.allow_power_on = p;
linkWrite(&msg_out);
}
void evSerial::forceUnlock() {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_connector_lock_tag;
msg_out.payload.connector_lock = false;
linkWrite(&msg_out);
}
void evSerial::set_number_of_phases(bool p) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_set_number_of_phases_tag;
msg_out.payload.set_number_of_phases = p;
linkWrite(&msg_out);
}
bool evSerial::reset(const std::string& reset_chip, const int reset_line) {
reset_done_flag = false;
forced_reset = true;
if (not reset_chip.empty()) {
// Try to hardware reset Yeti controller to be in a known state
Everest::Gpio reset_gpio;
reset_gpio.open(reset_chip, reset_line);
reset_gpio.set_output(true);
reset_gpio.set(true);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
reset_gpio.set(false);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
reset_gpio.set(true);
} else {
// Try to soft reset Yeti controller to be in a known state
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_reset_tag;
linkWrite(&msg_out);
}
bool success = false;
// Wait for reset done message from uC
for (int i = 0; i < 20; i++) {
if (reset_done_flag) {
success = true;
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
// Reset flag to detect run time spurious resets of uC from now on
reset_done_flag = false;
forced_reset = false;
// send some dummy packets to resync COBS etc.
keepAlive();
keepAlive();
keepAlive();
return success;
}
void evSerial::firmwareUpdate(bool rom) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_firmware_update_tag;
msg_out.payload.firmware_update.invoke_rom_bootloader = rom;
linkWrite(&msg_out);
}
void evSerial::keepAlive() {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_keep_alive_tag;
msg_out.payload.keep_alive.time_stamp = 0;
msg_out.payload.keep_alive.hw_type = 0;
msg_out.payload.keep_alive.hw_revision = 0;
strcpy(msg_out.payload.keep_alive.sw_version_string, "n/a");
linkWrite(&msg_out);
}

View File

@@ -0,0 +1,79 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#ifndef YETI_SERIAL
#define YETI_SERIAL
#include "yeti.pb.h"
#include <date/date.h>
#include <date/tz.h>
#include <sigslot/signal.hpp>
#include <stdint.h>
#include <termios.h>
#include <utils/thread.hpp>
class evSerial {
public:
evSerial();
~evSerial();
bool openDevice(const char* device, int baud);
bool is_open() {
return fd > 0;
};
void readThread();
void run();
bool reset(const std::string& reset_chip, const int reset_line);
void firmwareUpdate(bool rom);
void keepAlive();
void setPWM(uint32_t dc);
void allowPowerOn(bool p);
void forceUnlock();
void set_number_of_phases(bool p);
sigslot::signal<KeepAliveLo> signalKeepAliveLo;
sigslot::signal<PowerMeter> signalPowerMeter;
sigslot::signal<CpState> signalCPState;
sigslot::signal<PpState> signalPPState;
sigslot::signal<ErrorFlags> signalErrorFlags;
sigslot::signal<bool> signalRelaisState;
sigslot::signal<bool> signalLockState;
sigslot::signal<> signalSpuriousReset;
sigslot::signal<> signalConnectionTimeout;
private:
// Serial interface
bool setSerialAttributes();
int fd;
int baud;
// COBS de-/encoder
void cobsDecodeReset();
void handlePacket(uint8_t* buf, int len);
void cobsDecode(uint8_t* buf, int len);
void cobsDecodeByte(uint8_t byte);
size_t cobsEncode(const void* data, size_t length, uint8_t* buffer);
uint8_t msg[2048];
uint8_t code;
uint8_t block;
uint8_t* decode;
uint32_t crc32(uint8_t* buf, int len);
// Read thread for serial port
Everest::Thread readThreadHandle;
Everest::Thread timeoutDetectionThreadHandle;
bool linkWrite(EverestToMcu* m);
volatile bool reset_done_flag;
volatile bool forced_reset;
bool serial_timed_out();
void timeoutDetectionThread();
std::chrono::time_point<date::utc_clock> last_keep_alive_lo_timestamp;
};
#endif

View File

@@ -0,0 +1,126 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef YETI_FIRMWARE_VERSION
#define YETI_FIRMWARE_VERSION
#include <regex>
#include <string>
/*
Helper class to handle yeti Firmware Versions of the following style:
Reported by MCU: 2.0-1-g2d51638
Included in file name: yetiR1_2.0-1_firmware.bin
*/
static std::vector<std::string> split_by_delimeters(const std::string& s, const std::string& delimeters) {
std::regex re("[" + delimeters + "]");
std::sregex_token_iterator first{s.begin(), s.end(), re, -1}, last;
return {first, last};
}
// parse version string from filename
static std::string from_filename(const std::string& v) {
auto tokens = split_by_delimeters(v, "_");
if (tokens.size() >= 2) {
return tokens[1];
}
return "0.0-0";
}
class YetiFirmwareVersion {
public:
YetiFirmwareVersion() {
}
YetiFirmwareVersion(const std::string& _version_string) {
from_string(_version_string);
}
std::string to_string() {
return std::to_string(major) + "." + std::to_string(minor) + "-" + std::to_string(revision);
}
void from_string(const std::string& _version_string) {
std::string version_string;
// Is it a full filename or just a version string from the MCU?
if (_version_string.rfind("yetiR", 0) == 0) {
version_string = from_filename(_version_string);
} else {
version_string = _version_string;
}
auto tokens = split_by_delimeters(version_string, ".-");
// parse into major, minor and revision
if (tokens.size() >= 1) {
try {
major = std::stoi(tokens[0]);
} catch (...) {
// Set to 0 if we cannot parse it
major = 0;
}
}
if (tokens.size() >= 2) {
try {
minor = std::stoi(tokens[1]);
} catch (...) {
// Set to 0 if we cannot parse it
minor = 0;
}
}
if (tokens.size() >= 3) {
try {
revision = std::stoi(tokens[2]);
} catch (...) {
// Set to 0 if we cannot parse it
revision = 0;
}
}
}
YetiFirmwareVersion& operator=(const std::string& s) {
from_string(s);
return *this;
}
friend bool operator<(const YetiFirmwareVersion& l, const YetiFirmwareVersion& r) {
if (l.major < r.major) {
return true;
} else if (l.major > r.major) {
return false;
} else if (l.minor < r.minor) {
return true;
} else if (l.minor > r.minor) {
return false;
} else if (l.revision < r.revision) {
return true;
} else if (l.revision > r.revision) {
return false;
}
// they are identical
return false;
}
friend bool operator>(const YetiFirmwareVersion& lhs, const YetiFirmwareVersion& rhs) {
return rhs < lhs;
}
friend bool operator<=(const YetiFirmwareVersion& lhs, const YetiFirmwareVersion& rhs) {
return not(lhs > rhs);
}
friend bool operator>=(const YetiFirmwareVersion& lhs, const YetiFirmwareVersion& rhs) {
return not(lhs < rhs);
}
private:
int major{0};
int minor{0};
int revision{0};
};
#endif

View File

@@ -0,0 +1,2 @@
#!/bin/sh
nanopb_generator.py -L "#include <everest/3rd_party/nanopb/%s>" -I . -D . yeti.proto

View File

@@ -0,0 +1,2 @@
KeepAlive.sw_version_string max_length:50
KeepAliveLo.sw_version_string max_length:50

View File

@@ -0,0 +1,37 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.8 */
#include "yeti.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(EverestToMcu, EverestToMcu, 2)
PB_BIND(McuToEverest, McuToEverest, 2)
PB_BIND(ErrorFlags, ErrorFlags, AUTO)
PB_BIND(KeepAliveLo, KeepAliveLo, AUTO)
PB_BIND(KeepAlive, KeepAlive, AUTO)
PB_BIND(Telemetry, Telemetry, AUTO)
PB_BIND(PowerMeter, PowerMeter, AUTO)
PB_BIND(FirmwareUpdate, FirmwareUpdate, AUTO)

View File

@@ -0,0 +1,399 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.8 */
#ifndef PB_YETI_PB_H_INCLUDED
#define PB_YETI_PB_H_INCLUDED
#include <everest/3rd_party/nanopb/pb.h>
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _CpState {
CpState_STATE_A = 0,
CpState_STATE_B = 1,
CpState_STATE_C = 2,
CpState_STATE_D = 3,
CpState_STATE_E = 4,
CpState_STATE_F = 5
} CpState;
typedef enum _ResetReason {
ResetReason_USER = 0,
ResetReason_WATCHDOG = 1
} ResetReason;
typedef enum _PpState {
PpState_STATE_NC = 0,
PpState_STATE_13A = 1,
PpState_STATE_20A = 2,
PpState_STATE_32A = 3,
PpState_STATE_70A = 4,
PpState_STATE_FAULT = 5
} PpState;
typedef enum _LockState {
LockState_UNDEFINED = 0,
LockState_UNLOCKED = 1,
LockState_LOCKED = 2
} LockState;
/* Struct definitions */
typedef struct _ErrorFlags {
bool diode_fault;
bool rcd_selftest_failed;
bool rcd_triggered;
bool ventilation_not_available;
bool connector_lock_failed;
bool cp_signal_fault;
bool over_current;
} ErrorFlags;
typedef struct _KeepAliveLo {
uint32_t time_stamp;
uint32_t hw_type;
uint32_t hw_revision;
uint32_t protocol_version_major;
uint32_t protocol_version_minor;
char sw_version_string[51];
float hwcap_max_current;
float hwcap_min_current;
uint32_t hwcap_max_phase_count;
uint32_t hwcap_min_phase_count;
bool supports_changing_phases_during_charging;
} KeepAliveLo;
typedef struct _KeepAlive {
uint32_t time_stamp;
uint32_t hw_type;
uint32_t hw_revision;
char sw_version_string[51];
} KeepAlive;
typedef struct _Telemetry {
uint32_t cp_voltage_hi;
uint32_t cp_voltage_lo;
} Telemetry;
typedef struct _PowerMeter {
uint32_t time_stamp;
float vrmsL1;
float vrmsL2;
float vrmsL3;
float irmsL1;
float irmsL2;
float irmsL3;
float irmsN;
float wattHrL1;
float wattHrL2;
float wattHrL3;
float totalWattHr;
float tempL1;
float tempL2;
float tempL3;
float tempN;
float wattL1;
float wattL2;
float wattL3;
float freqL1;
float freqL2;
float freqL3;
bool phaseSeqError;
} PowerMeter;
/* This container message is send from MCU to EVerest and may contain any allowed message in that direction. */
typedef struct _McuToEverest {
pb_size_t which_payload;
union {
/* Needs to remain the same to allow firmware updates of older versions */
KeepAliveLo keep_alive;
/* Other IDs are 100+ to avoid compatibility issues with older firmware versions */
ResetReason reset;
CpState cp_state;
bool relais_state; /* false: relais are off, true: relais are on */
ErrorFlags error_flags;
Telemetry telemetry;
PpState pp_state;
LockState lock_state;
PowerMeter power_meter;
} payload;
} McuToEverest;
typedef struct _FirmwareUpdate {
bool invoke_rom_bootloader;
} FirmwareUpdate;
/* This container message is send from EVerest to MCU and may contain any allowed message in that direction. */
typedef struct _EverestToMcu {
pb_size_t which_payload;
union {
/* Needs to remain the same to allow firmware updates of older versions */
FirmwareUpdate firmware_update;
/* Other IDs are 100+ to avoid compatibility issues with older firmware versions */
KeepAlive keep_alive;
bool connector_lock; /* false: unlock, true: lock */
uint32_t pwm_duty_cycle; /* in 0.01 %, 0 = State F, 10000 = X1 */
bool allow_power_on;
bool reset;
bool set_number_of_phases;
} payload;
} EverestToMcu;
#ifdef __cplusplus
extern "C" {
#endif
/* Helper constants for enums */
#define _CpState_MIN CpState_STATE_A
#define _CpState_MAX CpState_STATE_F
#define _CpState_ARRAYSIZE ((CpState)(CpState_STATE_F+1))
#define _ResetReason_MIN ResetReason_USER
#define _ResetReason_MAX ResetReason_WATCHDOG
#define _ResetReason_ARRAYSIZE ((ResetReason)(ResetReason_WATCHDOG+1))
#define _PpState_MIN PpState_STATE_NC
#define _PpState_MAX PpState_STATE_FAULT
#define _PpState_ARRAYSIZE ((PpState)(PpState_STATE_FAULT+1))
#define _LockState_MIN LockState_UNDEFINED
#define _LockState_MAX LockState_LOCKED
#define _LockState_ARRAYSIZE ((LockState)(LockState_LOCKED+1))
#define McuToEverest_payload_reset_ENUMTYPE ResetReason
#define McuToEverest_payload_cp_state_ENUMTYPE CpState
#define McuToEverest_payload_pp_state_ENUMTYPE PpState
#define McuToEverest_payload_lock_state_ENUMTYPE LockState
/* Initializer values for message structs */
#define EverestToMcu_init_default {0, {FirmwareUpdate_init_default}}
#define McuToEverest_init_default {0, {KeepAliveLo_init_default}}
#define ErrorFlags_init_default {0, 0, 0, 0, 0, 0, 0}
#define KeepAliveLo_init_default {0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0}
#define KeepAlive_init_default {0, 0, 0, ""}
#define Telemetry_init_default {0, 0}
#define PowerMeter_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define FirmwareUpdate_init_default {0}
#define EverestToMcu_init_zero {0, {FirmwareUpdate_init_zero}}
#define McuToEverest_init_zero {0, {KeepAliveLo_init_zero}}
#define ErrorFlags_init_zero {0, 0, 0, 0, 0, 0, 0}
#define KeepAliveLo_init_zero {0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0}
#define KeepAlive_init_zero {0, 0, 0, ""}
#define Telemetry_init_zero {0, 0}
#define PowerMeter_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define FirmwareUpdate_init_zero {0}
/* Field tags (for use in manual encoding/decoding) */
#define ErrorFlags_diode_fault_tag 1
#define ErrorFlags_rcd_selftest_failed_tag 2
#define ErrorFlags_rcd_triggered_tag 3
#define ErrorFlags_ventilation_not_available_tag 4
#define ErrorFlags_connector_lock_failed_tag 5
#define ErrorFlags_cp_signal_fault_tag 6
#define ErrorFlags_over_current_tag 7
#define KeepAliveLo_time_stamp_tag 1
#define KeepAliveLo_hw_type_tag 2
#define KeepAliveLo_hw_revision_tag 3
#define KeepAliveLo_protocol_version_major_tag 4
#define KeepAliveLo_protocol_version_minor_tag 5
#define KeepAliveLo_sw_version_string_tag 6
#define KeepAliveLo_hwcap_max_current_tag 7
#define KeepAliveLo_hwcap_min_current_tag 8
#define KeepAliveLo_hwcap_max_phase_count_tag 9
#define KeepAliveLo_hwcap_min_phase_count_tag 10
#define KeepAliveLo_supports_changing_phases_during_charging_tag 11
#define KeepAlive_time_stamp_tag 1
#define KeepAlive_hw_type_tag 2
#define KeepAlive_hw_revision_tag 3
#define KeepAlive_sw_version_string_tag 6
#define Telemetry_cp_voltage_hi_tag 1
#define Telemetry_cp_voltage_lo_tag 2
#define PowerMeter_time_stamp_tag 1
#define PowerMeter_vrmsL1_tag 2
#define PowerMeter_vrmsL2_tag 3
#define PowerMeter_vrmsL3_tag 4
#define PowerMeter_irmsL1_tag 5
#define PowerMeter_irmsL2_tag 6
#define PowerMeter_irmsL3_tag 7
#define PowerMeter_irmsN_tag 8
#define PowerMeter_wattHrL1_tag 9
#define PowerMeter_wattHrL2_tag 10
#define PowerMeter_wattHrL3_tag 11
#define PowerMeter_totalWattHr_tag 12
#define PowerMeter_tempL1_tag 13
#define PowerMeter_tempL2_tag 14
#define PowerMeter_tempL3_tag 15
#define PowerMeter_tempN_tag 16
#define PowerMeter_wattL1_tag 17
#define PowerMeter_wattL2_tag 18
#define PowerMeter_wattL3_tag 19
#define PowerMeter_freqL1_tag 20
#define PowerMeter_freqL2_tag 21
#define PowerMeter_freqL3_tag 22
#define PowerMeter_phaseSeqError_tag 23
#define McuToEverest_keep_alive_tag 3
#define McuToEverest_reset_tag 101
#define McuToEverest_cp_state_tag 102
#define McuToEverest_relais_state_tag 103
#define McuToEverest_error_flags_tag 104
#define McuToEverest_telemetry_tag 105
#define McuToEverest_pp_state_tag 106
#define McuToEverest_lock_state_tag 107
#define McuToEverest_power_meter_tag 108
#define FirmwareUpdate_invoke_rom_bootloader_tag 1
#define EverestToMcu_firmware_update_tag 16
#define EverestToMcu_keep_alive_tag 100
#define EverestToMcu_connector_lock_tag 102
#define EverestToMcu_pwm_duty_cycle_tag 103
#define EverestToMcu_allow_power_on_tag 104
#define EverestToMcu_reset_tag 105
#define EverestToMcu_set_number_of_phases_tag 106
/* Struct field encoding specification for nanopb */
#define EverestToMcu_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (payload,firmware_update,payload.firmware_update), 16) \
X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 100) \
X(a, STATIC, ONEOF, BOOL, (payload,connector_lock,payload.connector_lock), 102) \
X(a, STATIC, ONEOF, UINT32, (payload,pwm_duty_cycle,payload.pwm_duty_cycle), 103) \
X(a, STATIC, ONEOF, BOOL, (payload,allow_power_on,payload.allow_power_on), 104) \
X(a, STATIC, ONEOF, BOOL, (payload,reset,payload.reset), 105) \
X(a, STATIC, ONEOF, BOOL, (payload,set_number_of_phases,payload.set_number_of_phases), 106)
#define EverestToMcu_CALLBACK NULL
#define EverestToMcu_DEFAULT NULL
#define EverestToMcu_payload_firmware_update_MSGTYPE FirmwareUpdate
#define EverestToMcu_payload_keep_alive_MSGTYPE KeepAlive
#define McuToEverest_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 3) \
X(a, STATIC, ONEOF, UENUM, (payload,reset,payload.reset), 101) \
X(a, STATIC, ONEOF, UENUM, (payload,cp_state,payload.cp_state), 102) \
X(a, STATIC, ONEOF, BOOL, (payload,relais_state,payload.relais_state), 103) \
X(a, STATIC, ONEOF, MESSAGE, (payload,error_flags,payload.error_flags), 104) \
X(a, STATIC, ONEOF, MESSAGE, (payload,telemetry,payload.telemetry), 105) \
X(a, STATIC, ONEOF, UENUM, (payload,pp_state,payload.pp_state), 106) \
X(a, STATIC, ONEOF, UENUM, (payload,lock_state,payload.lock_state), 107) \
X(a, STATIC, ONEOF, MESSAGE, (payload,power_meter,payload.power_meter), 108)
#define McuToEverest_CALLBACK NULL
#define McuToEverest_DEFAULT NULL
#define McuToEverest_payload_keep_alive_MSGTYPE KeepAliveLo
#define McuToEverest_payload_error_flags_MSGTYPE ErrorFlags
#define McuToEverest_payload_telemetry_MSGTYPE Telemetry
#define McuToEverest_payload_power_meter_MSGTYPE PowerMeter
#define ErrorFlags_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, diode_fault, 1) \
X(a, STATIC, SINGULAR, BOOL, rcd_selftest_failed, 2) \
X(a, STATIC, SINGULAR, BOOL, rcd_triggered, 3) \
X(a, STATIC, SINGULAR, BOOL, ventilation_not_available, 4) \
X(a, STATIC, SINGULAR, BOOL, connector_lock_failed, 5) \
X(a, STATIC, SINGULAR, BOOL, cp_signal_fault, 6) \
X(a, STATIC, SINGULAR, BOOL, over_current, 7)
#define ErrorFlags_CALLBACK NULL
#define ErrorFlags_DEFAULT NULL
#define KeepAliveLo_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, time_stamp, 1) \
X(a, STATIC, SINGULAR, UINT32, hw_type, 2) \
X(a, STATIC, SINGULAR, UINT32, hw_revision, 3) \
X(a, STATIC, SINGULAR, UINT32, protocol_version_major, 4) \
X(a, STATIC, SINGULAR, UINT32, protocol_version_minor, 5) \
X(a, STATIC, SINGULAR, STRING, sw_version_string, 6) \
X(a, STATIC, SINGULAR, FLOAT, hwcap_max_current, 7) \
X(a, STATIC, SINGULAR, FLOAT, hwcap_min_current, 8) \
X(a, STATIC, SINGULAR, UINT32, hwcap_max_phase_count, 9) \
X(a, STATIC, SINGULAR, UINT32, hwcap_min_phase_count, 10) \
X(a, STATIC, SINGULAR, BOOL, supports_changing_phases_during_charging, 11)
#define KeepAliveLo_CALLBACK NULL
#define KeepAliveLo_DEFAULT NULL
#define KeepAlive_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, time_stamp, 1) \
X(a, STATIC, SINGULAR, UINT32, hw_type, 2) \
X(a, STATIC, SINGULAR, UINT32, hw_revision, 3) \
X(a, STATIC, SINGULAR, STRING, sw_version_string, 6)
#define KeepAlive_CALLBACK NULL
#define KeepAlive_DEFAULT NULL
#define Telemetry_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, cp_voltage_hi, 1) \
X(a, STATIC, SINGULAR, UINT32, cp_voltage_lo, 2)
#define Telemetry_CALLBACK NULL
#define Telemetry_DEFAULT NULL
#define PowerMeter_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, time_stamp, 1) \
X(a, STATIC, SINGULAR, FLOAT, vrmsL1, 2) \
X(a, STATIC, SINGULAR, FLOAT, vrmsL2, 3) \
X(a, STATIC, SINGULAR, FLOAT, vrmsL3, 4) \
X(a, STATIC, SINGULAR, FLOAT, irmsL1, 5) \
X(a, STATIC, SINGULAR, FLOAT, irmsL2, 6) \
X(a, STATIC, SINGULAR, FLOAT, irmsL3, 7) \
X(a, STATIC, SINGULAR, FLOAT, irmsN, 8) \
X(a, STATIC, SINGULAR, FLOAT, wattHrL1, 9) \
X(a, STATIC, SINGULAR, FLOAT, wattHrL2, 10) \
X(a, STATIC, SINGULAR, FLOAT, wattHrL3, 11) \
X(a, STATIC, SINGULAR, FLOAT, totalWattHr, 12) \
X(a, STATIC, SINGULAR, FLOAT, tempL1, 13) \
X(a, STATIC, SINGULAR, FLOAT, tempL2, 14) \
X(a, STATIC, SINGULAR, FLOAT, tempL3, 15) \
X(a, STATIC, SINGULAR, FLOAT, tempN, 16) \
X(a, STATIC, SINGULAR, FLOAT, wattL1, 17) \
X(a, STATIC, SINGULAR, FLOAT, wattL2, 18) \
X(a, STATIC, SINGULAR, FLOAT, wattL3, 19) \
X(a, STATIC, SINGULAR, FLOAT, freqL1, 20) \
X(a, STATIC, SINGULAR, FLOAT, freqL2, 21) \
X(a, STATIC, SINGULAR, FLOAT, freqL3, 22) \
X(a, STATIC, SINGULAR, BOOL, phaseSeqError, 23)
#define PowerMeter_CALLBACK NULL
#define PowerMeter_DEFAULT NULL
#define FirmwareUpdate_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, invoke_rom_bootloader, 1)
#define FirmwareUpdate_CALLBACK NULL
#define FirmwareUpdate_DEFAULT NULL
extern const pb_msgdesc_t EverestToMcu_msg;
extern const pb_msgdesc_t McuToEverest_msg;
extern const pb_msgdesc_t ErrorFlags_msg;
extern const pb_msgdesc_t KeepAliveLo_msg;
extern const pb_msgdesc_t KeepAlive_msg;
extern const pb_msgdesc_t Telemetry_msg;
extern const pb_msgdesc_t PowerMeter_msg;
extern const pb_msgdesc_t FirmwareUpdate_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define EverestToMcu_fields &EverestToMcu_msg
#define McuToEverest_fields &McuToEverest_msg
#define ErrorFlags_fields &ErrorFlags_msg
#define KeepAliveLo_fields &KeepAliveLo_msg
#define KeepAlive_fields &KeepAlive_msg
#define Telemetry_fields &Telemetry_msg
#define PowerMeter_fields &PowerMeter_msg
#define FirmwareUpdate_fields &FirmwareUpdate_msg
/* Maximum encoded size of messages (where known) */
#define ErrorFlags_size 14
#define EverestToMcu_size 73
#define FirmwareUpdate_size 2
#define KeepAliveLo_size 106
#define KeepAlive_size 70
#define McuToEverest_size 124
#define PowerMeter_size 121
#define Telemetry_size 12
#define YETI_PB_H_MAX_SIZE McuToEverest_size
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -0,0 +1,134 @@
syntax = "proto3";
/*
This container message is send from EVerest to MCU and may contain any allowed message in that direction.
*/
message EverestToMcu {
oneof payload {
// Needs to remain the same to allow firmware updates of older versions
FirmwareUpdate firmware_update = 16;
// Other IDs are 100+ to avoid compatibility issues with older firmware versions
KeepAlive keep_alive = 100;
bool connector_lock = 102; // false: unlock, true: lock
uint32 pwm_duty_cycle = 103; // in 0.01 %, 0 = State F, 10000 = X1
bool allow_power_on = 104;
bool reset = 105;
bool set_number_of_phases = 106;
}
}
/*
This container message is send from MCU to EVerest and may contain any allowed message in that direction.
*/
message McuToEverest {
oneof payload {
// Needs to remain the same to allow firmware updates of older versions
KeepAliveLo keep_alive = 3;
// Other IDs are 100+ to avoid compatibility issues with older firmware versions
ResetReason reset = 101;
CpState cp_state = 102;
bool relais_state = 103; // false: relais are off, true: relais are on
ErrorFlags error_flags = 104;
Telemetry telemetry = 105;
PpState pp_state = 106;
LockState lock_state = 107;
PowerMeter power_meter = 108;
}
}
enum CpState {
STATE_A = 0;
STATE_B = 1;
STATE_C = 2;
STATE_D = 3;
STATE_E = 4;
STATE_F = 5;
}
message ErrorFlags {
bool diode_fault = 1;
bool rcd_selftest_failed = 2;
bool rcd_triggered = 3;
bool ventilation_not_available = 4;
bool connector_lock_failed = 5;
bool cp_signal_fault = 6;
bool over_current = 7;
}
enum ResetReason {
USER = 0;
WATCHDOG = 1;
}
message KeepAliveLo {
uint32 time_stamp = 1;
uint32 hw_type = 2;
uint32 hw_revision = 3;
uint32 protocol_version_major = 4;
uint32 protocol_version_minor = 5;
string sw_version_string = 6;
float hwcap_max_current = 7;
float hwcap_min_current = 8;
uint32 hwcap_max_phase_count = 9;
uint32 hwcap_min_phase_count = 10;
bool supports_changing_phases_during_charging = 11;
}
message KeepAlive {
uint32 time_stamp = 1;
uint32 hw_type = 2;
uint32 hw_revision = 3;
string sw_version_string = 6;
}
message Telemetry {
uint32 cp_voltage_hi = 1;
uint32 cp_voltage_lo = 2;
}
enum PpState {
STATE_NC = 0;
STATE_13A = 1;
STATE_20A = 2;
STATE_32A = 3;
STATE_70A = 4;
STATE_FAULT = 5;
}
enum LockState {
UNDEFINED = 0;
UNLOCKED = 1;
LOCKED = 2;
}
message PowerMeter {
uint32 time_stamp = 1;
float vrmsL1 = 2;
float vrmsL2 = 3;
float vrmsL3 = 4;
float irmsL1 = 5;
float irmsL2 = 6;
float irmsL3 = 7;
float irmsN = 8;
float wattHrL1 = 9;
float wattHrL2 = 10;
float wattHrL3 = 11;
float totalWattHr = 12;
float tempL1 = 13;
float tempL2 = 14;
float tempL3 = 15;
float tempN = 16;
float wattL1 = 17;
float wattL2 = 18;
float wattL3 = 19;
float freqL1 = 20;
float freqL2 = 21;
float freqL3 = 22;
bool phaseSeqError = 23;
}
message FirmwareUpdate {
bool invoke_rom_bootloader = 1;
}

View File

@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.10)
# set the project name
project(yeti_fwupdate VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# add the executable
add_executable(yeti_fwupdate main.cpp)
target_include_directories(yeti_fwupdate PUBLIC "${PROJECT_BINARY_DIR}" PUBLIC "../yeti_comms/nanopb" PUBLIC "../yeti_comms/protobuf" PUBLIC "../yeti_comms")
target_link_libraries(yeti_fwupdate PRIVATE Pal::Sigslot Threads::Threads yeti_comms everest::framework)
install(TARGETS yeti_fwupdate)

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#include <stdio.h>
#include <string.h>
#include "evSerial.h"
#include <filesystem>
#include <unistd.h>
#include "firmware_version.hpp"
#include "yeti.pb.h"
#include <sigslot/signal.hpp>
std::atomic_bool sw_version_received = false;
YetiFirmwareVersion installed_fw_version;
std::string installed_fw_version_orig;
void recvKeepAliveLo(KeepAliveLo s) {
installed_fw_version = s.sw_version_string;
installed_fw_version_orig = s.sw_version_string;
sw_version_received = true;
}
void help() {
printf("\nUsage: ./yeti_fwupdate /dev/ttyXXX firmware.bin\n\n");
printf("This tool uses stm32flash (version 0.6 and above) which needs to be installed.\n");
}
int main(int argc, char* argv[]) {
printf("Yeti ROM Bootloader Firmware Updater\n");
if (argc != 3) {
help();
exit(0);
}
const char* device = argv[1];
std::filesystem::path filename = argv[2];
evSerial* p = new evSerial();
if (p->openDevice(device, 115200)) {
// printf("Running\n");
p->run();
p->signalKeepAliveLo.connect(recvKeepAliveLo);
while (true) {
if (sw_version_received)
break;
usleep(100);
}
YetiFirmwareVersion update_file_fw_version{filename.filename()};
printf("Installed Yeti Firmware Version: %s (%s)\n", installed_fw_version.to_string().c_str(),
installed_fw_version_orig.c_str());
printf("Update File Firmware Version: %s\n", update_file_fw_version.to_string().c_str());
if (installed_fw_version >= update_file_fw_version) {
printf("Latest version already installed. Exiting.\n");
exit(1);
}
printf("\nRebooting Yeti in ROM Bootloader mode...\n");
// send some dummy commands to make sure protocol is in sync
p->keepAlive();
p->keepAlive();
// now reboot uC in boot loader mode
p->firmwareUpdate(true);
sleep(1);
delete p;
sleep(1);
char cmd[1000];
sprintf(cmd, "stm32flash -b 115200 %.100s -v -w %.100s -R", device, filename.string().c_str());
// sprintf(cmd, "stm32flash -b115200 %.100s", device);
printf("Executing %s ...\n", cmd);
system(cmd);
// printf ("Joining\n");
} else {
printf("Cannot open device \"%s\"\n", device);
delete p;
}
return 0;
}