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,36 @@
#
# 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(umwc_comms)
add_subdirectory(umwc_fwupdate)
target_include_directories(${MODULE_NAME}
PRIVATE
umwc_comms
umwc_comms/protobuf
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
umwc_comms
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"dc_supply/power_supply_DCImpl.cpp"
"powermeter/powermeterImpl.cpp"
"board_support/evse_board_supportImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "MicroMegaWattBSP.hpp"
namespace module {
void MicroMegaWattBSP::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;
}
invoke_init(*p_board_support);
invoke_init(*p_dc_supply);
invoke_init(*p_powermeter);
}
void MicroMegaWattBSP::ready() {
serial.run();
if (not config.reset_gpio_chip.empty()) {
EVLOG_info << "Perform HW reset with gpio chip " << config.reset_gpio_chip << " line " << config.reset_gpio;
if (!serial.reset(config.reset_gpio_chip, config.reset_gpio)) {
EVLOG_error << "uMWC reset not successful.";
}
}
serial.signalSpuriousReset.connect([this]() { EVLOG_warning << "uMWC uC spurious reset!"; });
serial.signalConnectionTimeout.connect([this]() { EVLOG_warning << "uMWC UART timeout!"; });
serial.signalTelemetry.connect([this](Telemetry t) {
mqtt.publish("everest_external/umwc/cp_hi", t.cp_hi);
mqtt.publish("everest_external/umwc/cp_lo", t.cp_lo);
mqtt.publish("everest_external/umwc/pwm_dc", t.pwm_dc);
mqtt.publish("everest_external/umwc/relais_on", t.relais_on);
mqtt.publish("everest_external/umwc/output_voltage", t.voltage);
});
invoke_ready(*p_board_support);
invoke_ready(*p_dc_supply);
invoke_ready(*p_powermeter);
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);
}
}
} // namespace module

View File

@@ -0,0 +1,80 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef MICRO_MEGA_WATT_BSP_HPP
#define MICRO_MEGA_WATT_BSP_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/evse_board_support/Implementation.hpp>
#include <generated/interfaces/power_supply_DC/Implementation.hpp>
#include <generated/interfaces/powermeter/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include "umwc_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 dc_max_voltage;
int connector_id;
};
class MicroMegaWattBSP : public Everest::ModuleBase {
public:
MicroMegaWattBSP() = delete;
MicroMegaWattBSP(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
std::unique_ptr<power_supply_DCImplBase> p_dc_supply,
std::unique_ptr<powermeterImplBase> p_powermeter,
std::unique_ptr<evse_board_supportImplBase> p_board_support, Conf& config) :
ModuleBase(info),
mqtt(mqtt_provider),
p_dc_supply(std::move(p_dc_supply)),
p_powermeter(std::move(p_powermeter)),
p_board_support(std::move(p_board_support)),
config(config){};
Everest::MqttProvider& mqtt;
const std::unique_ptr<power_supply_DCImplBase> p_dc_supply;
const std::unique_ptr<powermeterImplBase> p_powermeter;
const std::unique_ptr<evse_board_supportImplBase> p_board_support;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
evSerial serial;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
protected:
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
// insert your protected definitions here
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
private:
friend class LdEverest;
void init();
void ready();
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
// insert your private definitions here
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
};
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
// insert other definitions here
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
} // namespace module
#endif // MICRO_MEGA_WATT_BSP_HPP

View File

@@ -0,0 +1,159 @@
// 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;
}
void evse_board_supportImpl::init() {
{
std::scoped_lock lock(capsMutex);
caps.min_current_A_import = 0;
caps.max_current_A_import = 100;
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 = 0;
caps.max_current_A_export = 100;
caps.min_phase_count_export = 1;
caps.max_phase_count_export = 3;
}
mod->serial.signalKeepAliveLo.connect([this](KeepAliveLo l) {
if (not keep_alive_printed) {
EVLOG_info << "uMWC Controller Configuration:";
EVLOG_info << " Hardware revision: " << l.hw_revision;
EVLOG_info << " Firmware version: " << l.sw_version_string;
}
keep_alive_printed = true;
});
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(event_cp_state.event);
publish_event(event_cp_state);
last_cp_state = cp_state;
if (cp_state == CpState::CpState_STATE_A) {
if (error_state_monitor->is_error_active("evse_board_support/MREC8EmergencyStop", "")) {
clear_error("evse_board_support/MREC8EmergencyStop");
}
}
}
});
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->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/emergency_stop", mod->config.connector_id),
[this](const std::string& data) {
types::evse_manager::StopTransactionRequest request;
request.reason = types::evse_manager::StopTransactionReason::EmergencyStop;
mod->p_board_support->publish_request_stop_transaction(request);
Everest::error::Error error_object = error_factory->create_error(
"evse_board_support/MREC8EmergencyStop", "", "Emergency stop button pushed by user",
Everest::error::Severity::High);
raise_error(error_object);
});
mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/evse_utility_int", mod->config.connector_id),
[this](const std::string& data) {
types::evse_manager::StopTransactionRequest request;
request.reason = types::evse_manager::StopTransactionReason::PowerLoss;
mod->p_board_support->publish_request_stop_transaction(request);
});
mod->mqtt.subscribe(fmt::format("everest_external/nodered/{}/cmd/stop_transaction", mod->config.connector_id),
[this](const std::string& data) {
types::evse_manager::StopTransactionRequest request;
request.reason = types::evse_manager::StopTransactionReason::Local;
mod->p_board_support->publish_request_stop_transaction(request);
});
}
void evse_board_supportImpl::ready() {
{
// Publish caps once in the beginning
std::scoped_lock lock(capsMutex);
publish_capabilities(caps);
}
}
void evse_board_supportImpl::handle_enable(bool& value) {
mod->serial.enable(value);
}
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_switch_three_phases_while_charging(bool& value) {
// your code for cmd ac_switch_three_phases_while_charging goes here
}
void evse_board_supportImpl::handle_ac_set_overcurrent_limit_A(double& value) {
// your code for cmd ac_set_overcurrent_limit_A goes here
}
} // namespace board_support
} // namespace module

View File

@@ -0,0 +1,74 @@
// 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 "../MicroMegaWattBSP.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<MicroMegaWattBSP>& 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<MicroMegaWattBSP>& 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::mutex capsMutex;
std::atomic_bool keep_alive_printed{false};
CpState last_cp_state{CpState::CpState_STATE_E};
bool last_relais_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 board_support
} // namespace module
#endif // BOARD_SUPPORT_EVSE_BOARD_SUPPORT_IMPL_HPP

View File

@@ -0,0 +1,103 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "power_supply_DCImpl.hpp"
#include <fmt/core.h>
namespace module {
namespace dc_supply {
void power_supply_DCImpl::init() {
mod->serial.signalTelemetry.connect([this](Telemetry t) {
types::power_supply_DC::VoltageCurrent vc;
vc.current_A = 0;
vc.voltage_V = t.voltage;
publish_voltage_current(vc);
types::powermeter::Powermeter p;
p.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
p.meter_id = "UMWC";
types::units::Energy e;
e.total = 0.;
p.energy_Wh_import = e;
types::units::Voltage v;
v.DC = t.voltage;
p.voltage_V = v;
mod->p_powermeter->publish_powermeter(p);
});
std::thread([this]() {
float low_pass_voltage = 0.;
float last_low_pass_voltage = -1;
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// prevent overshoot
if (low_pass_voltage > req_voltage) {
// step down immediately
low_pass_voltage = req_voltage;
} else {
float delta = req_voltage - low_pass_voltage;
if (delta > 500) {
low_pass_voltage += 100;
} else {
if (delta > 50) {
low_pass_voltage += 25;
} else {
low_pass_voltage = req_voltage;
}
}
}
if (not is_on) {
low_pass_voltage = 0.;
}
if (last_low_pass_voltage not_eq low_pass_voltage) {
mod->serial.setOutputVoltageCurrent(low_pass_voltage, 0.);
}
last_low_pass_voltage = low_pass_voltage;
}
}).detach();
}
void power_supply_DCImpl::ready() {
types::power_supply_DC::Capabilities caps;
caps.bidirectional = false;
caps.conversion_efficiency_export = 0.9;
caps.max_export_current_A = 25;
caps.max_export_voltage_V = mod->config.dc_max_voltage;
caps.min_export_current_A = 0;
caps.min_export_voltage_V = 50;
caps.max_export_power_W = 10000;
caps.current_regulation_tolerance_A = 1;
caps.peak_current_ripple_A = 0;
publish_capabilities(caps);
}
void power_supply_DCImpl::handle_setMode(types::power_supply_DC::Mode& mode,
types::power_supply_DC::ChargingPhase& phase) {
// your code for cmd setMode goes here
if (mode == types::power_supply_DC::Mode::Export) {
mod->serial.setOutputVoltageCurrent(req_voltage, req_current);
is_on = true;
} else {
mod->serial.setOutputVoltageCurrent(0, 0);
is_on = false;
}
};
void power_supply_DCImpl::handle_setExportVoltageCurrent(double& voltage, double& current) {
req_voltage = voltage;
req_current = current;
};
void power_supply_DCImpl::handle_setImportVoltageCurrent(double& voltage, double& current){
// not supported here
};
} // namespace dc_supply
} // namespace module

View File

@@ -0,0 +1,67 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef DC_SUPPLY_POWER_SUPPLY_DC_IMPL_HPP
#define DC_SUPPLY_POWER_SUPPLY_DC_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/power_supply_DC/Implementation.hpp>
#include "../MicroMegaWattBSP.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace dc_supply {
struct Conf {};
class power_supply_DCImpl : public power_supply_DCImplBase {
public:
power_supply_DCImpl() = delete;
power_supply_DCImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<MicroMegaWattBSP>& mod, Conf& config) :
power_supply_DCImplBase(ev, "dc_supply"), 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_setMode(types::power_supply_DC::Mode& mode,
types::power_supply_DC::ChargingPhase& phase) override;
virtual void handle_setExportVoltageCurrent(double& voltage, double& current) override;
virtual void handle_setImportVoltageCurrent(double& voltage, double& current) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<MicroMegaWattBSP>& 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<float> req_voltage{0};
std::atomic<float> req_current{0};
std::atomic_bool is_on{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 dc_supply
} // namespace module
#endif // DC_SUPPLY_POWER_SUPPLY_DC_IMPL_HPP

View File

@@ -0,0 +1,46 @@
description: Driver module for the Micro Mega Watt DC Charging Tester v1.0
config:
serial_port:
description: Serial port the uMWC hardware is connected to
type: string
default: /dev/ttyUSB0
baud_rate:
description: Serial baud rate to use when communicating with uMWC hardware
type: integer
minimum: 9600
maximum: 230400
default: 115200
reset_gpio_chip:
description: >-
Reset GPIO chip to use to HW reset uMWC. If set to empty string, it is disabled.
type: string
default: 'gpiochip0'
reset_gpio:
description: GPIO line to use to reset uMWC
type: integer
default: 27
dc_max_voltage:
description: Maximum voltage to support
type: integer
minimum: 50
maximum: 1000
default: 1000
connector_id:
description: Connector id
type: integer
default: 1
provides:
dc_supply:
interface: power_supply_DC
description: Interface for the DC/DC output supply
powermeter:
interface: powermeter
description: Interface for the powermeter
board_support:
interface: evse_board_support
description: provides the board support Interface to low level control control pilot, relais, rcd, motor lock
enable_external_mqtt: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Cornelius Claussen

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "powermeterImpl.hpp"
namespace module {
namespace powermeter {
void powermeterImpl::init() {
}
void powermeterImpl::ready() {
}
types::powermeter::TransactionStartResponse
powermeterImpl::handle_start_transaction(types::powermeter::TransactionReq& value) {
// your code for cmd start_transaction goes here
return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED,
"MicroMegaWattBSP powermeter does not support the start_transaction command"};
}
types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) {
return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED,
{},
{},
"MicroMegaWattBSP powermeter does not support the stop_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 "../MicroMegaWattBSP.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<MicroMegaWattBSP>& 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<MicroMegaWattBSP>& 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,36 @@
cmake_minimum_required(VERSION 3.10)
# set the project name
project(umwc_comms VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# add the executable
add_library(umwc_comms STATIC)
ev_register_library_target(umwc_comms)
target_sources(umwc_comms
PRIVATE
evSerial.cpp
protobuf/umwc.pb.c
)
target_include_directories(umwc_comms
PUBLIC
"${PROJECT_BINARY_DIR}"
protobuf
)
target_link_libraries(umwc_comms
PUBLIC
date::date-tz
everest::nanopb
PRIVATE
Pal::Sigslot
everest::framework
everest::gpio
)

View File

@@ -0,0 +1,413 @@
// 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 "umwc.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 crc, msk;
i = 0;
crc = 0xFFFFFFFF;
while (i < len) {
uint32_t 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)
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_telemetry_tag:
/*printf("Received telemetry cp_hi %f cp_lo %f relais_on %i pwm_dc %f\n", msg_in.payload.telemetry.cp_hi,
msg_in.payload.telemetry.cp_lo, (int)msg_in.payload.telemetry.relais_on,
msg_in.payload.telemetry.pwm_dc);*/
signalTelemetry(msg_in.payload.telemetry);
break;
case McuToEverest_cp_state_tag:
signalCPState(msg_in.payload.cp_state);
break;
case McuToEverest_relais_state_tag:
signalRelaisState(msg_in.payload.relais_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();
// send keep alive to LO
keepAlive();
}
}
void evSerial::readThread() {
uint8_t buf[2048];
cobsDecodeReset();
while (true) {
if (readThreadHandle.shouldExit())
break;
if (fd > 0) {
int 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::enable(bool en) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_enable_tag;
msg_out.payload.enable = en;
linkWrite(&msg_out);
}
void evSerial::replug() {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_replug_tag;
linkWrite(&msg_out);
}
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::setOutputVoltageCurrent(float v, float c) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_set_output_voltage_current_tag;
msg_out.payload.set_output_voltage_current.voltage = v;
msg_out.payload.set_output_voltage_current.current = c;
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::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,81 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#ifndef YETI_SERIAL
#define YETI_SERIAL
#include "umwc.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();
void enable(bool en);
void disable();
void replug();
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 setOutputVoltageCurrent(float v, float c);
sigslot::signal<KeepAliveLo> signalKeepAliveLo;
sigslot::signal<Telemetry> signalTelemetry;
sigslot::signal<CpState> signalCPState;
sigslot::signal<ErrorFlags> signalErrorFlags;
sigslot::signal<bool> signalRelaisState;
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,2 @@
#!/bin/sh
nanopb_generator.py -L "#include <everest/3rd_party/nanopb/%s>" -I . -D . umwc.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,35 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.8 */
#include "umwc.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(SetOutputVoltageCurrent, SetOutputVoltageCurrent, AUTO)
PB_BIND(ErrorFlags, ErrorFlags, AUTO)
PB_BIND(KeepAliveLo, KeepAliveLo, AUTO)
PB_BIND(KeepAlive, KeepAlive, AUTO)
PB_BIND(Telemetry, Telemetry, AUTO)
PB_BIND(FirmwareUpdate, FirmwareUpdate, AUTO)

View File

@@ -0,0 +1,299 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.8 */
#ifndef PB_UMWC_PB_H_INCLUDED
#define PB_UMWC_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;
/* Struct definitions */
typedef struct _SetOutputVoltageCurrent {
float voltage;
float current;
} SetOutputVoltageCurrent;
typedef struct _ErrorFlags {
bool diode_fault;
bool cp_signal_fault;
} 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 {
float cp_hi;
float cp_lo;
float pwm_dc;
float relais_on;
float voltage;
} Telemetry;
/* 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;
} 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;
SetOutputVoltageCurrent set_output_voltage_current;
/* Other IDs are 100+ to avoid compatibility issues with older firmware versions */
KeepAlive keep_alive;
uint32_t pwm_duty_cycle; /* in 0.01 %, 0 = State F, 10000 = X1 */
bool allow_power_on;
bool reset;
bool enable;
bool replug;
} 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 McuToEverest_payload_reset_ENUMTYPE ResetReason
#define McuToEverest_payload_cp_state_ENUMTYPE CpState
/* Initializer values for message structs */
#define EverestToMcu_init_default {0, {FirmwareUpdate_init_default}}
#define McuToEverest_init_default {0, {KeepAliveLo_init_default}}
#define SetOutputVoltageCurrent_init_default {0, 0}
#define ErrorFlags_init_default {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, 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 SetOutputVoltageCurrent_init_zero {0, 0}
#define ErrorFlags_init_zero {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, 0, 0, 0}
#define FirmwareUpdate_init_zero {0}
/* Field tags (for use in manual encoding/decoding) */
#define SetOutputVoltageCurrent_voltage_tag 1
#define SetOutputVoltageCurrent_current_tag 2
#define ErrorFlags_diode_fault_tag 1
#define ErrorFlags_cp_signal_fault_tag 6
#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_hi_tag 1
#define Telemetry_cp_lo_tag 2
#define Telemetry_pwm_dc_tag 3
#define Telemetry_relais_on_tag 4
#define Telemetry_voltage_tag 5
#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 FirmwareUpdate_invoke_rom_bootloader_tag 1
#define EverestToMcu_firmware_update_tag 16
#define EverestToMcu_set_output_voltage_current_tag 50
#define EverestToMcu_keep_alive_tag 100
#define EverestToMcu_pwm_duty_cycle_tag 103
#define EverestToMcu_allow_power_on_tag 104
#define EverestToMcu_reset_tag 105
#define EverestToMcu_enable_tag 106
#define EverestToMcu_replug_tag 107
/* 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,set_output_voltage_current,payload.set_output_voltage_current), 50) \
X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 100) \
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,enable,payload.enable), 106) \
X(a, STATIC, ONEOF, BOOL, (payload,replug,payload.replug), 107)
#define EverestToMcu_CALLBACK NULL
#define EverestToMcu_DEFAULT NULL
#define EverestToMcu_payload_firmware_update_MSGTYPE FirmwareUpdate
#define EverestToMcu_payload_set_output_voltage_current_MSGTYPE SetOutputVoltageCurrent
#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)
#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 SetOutputVoltageCurrent_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, FLOAT, voltage, 1) \
X(a, STATIC, SINGULAR, FLOAT, current, 2)
#define SetOutputVoltageCurrent_CALLBACK NULL
#define SetOutputVoltageCurrent_DEFAULT NULL
#define ErrorFlags_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, diode_fault, 1) \
X(a, STATIC, SINGULAR, BOOL, cp_signal_fault, 6)
#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, FLOAT, cp_hi, 1) \
X(a, STATIC, SINGULAR, FLOAT, cp_lo, 2) \
X(a, STATIC, SINGULAR, FLOAT, pwm_dc, 3) \
X(a, STATIC, SINGULAR, FLOAT, relais_on, 4) \
X(a, STATIC, SINGULAR, FLOAT, voltage, 5)
#define Telemetry_CALLBACK NULL
#define Telemetry_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 SetOutputVoltageCurrent_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 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 SetOutputVoltageCurrent_fields &SetOutputVoltageCurrent_msg
#define ErrorFlags_fields &ErrorFlags_msg
#define KeepAliveLo_fields &KeepAliveLo_msg
#define KeepAlive_fields &KeepAlive_msg
#define Telemetry_fields &Telemetry_msg
#define FirmwareUpdate_fields &FirmwareUpdate_msg
/* Maximum encoded size of messages (where known) */
#define ErrorFlags_size 4
#define EverestToMcu_size 73
#define FirmwareUpdate_size 2
#define KeepAliveLo_size 106
#define KeepAlive_size 70
#define McuToEverest_size 108
#define SetOutputVoltageCurrent_size 10
#define Telemetry_size 25
#define UMWC_PB_H_MAX_SIZE McuToEverest_size
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -0,0 +1,95 @@
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;
SetOutputVoltageCurrent set_output_voltage_current = 50;
// Other IDs are 100+ to avoid compatibility issues with older firmware versions
KeepAlive keep_alive = 100;
uint32 pwm_duty_cycle = 103; // in 0.01 %, 0 = State F, 10000 = X1
bool allow_power_on = 104;
bool reset = 105;
bool enable = 106;
bool replug = 107;
}
}
/*
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;
}
}
enum CpState {
STATE_A = 0;
STATE_B = 1;
STATE_C = 2;
STATE_D = 3;
STATE_E = 4;
STATE_F = 5;
}
message SetOutputVoltageCurrent {
float voltage = 1;
float current = 2;
}
message ErrorFlags {
bool diode_fault = 1;
bool cp_signal_fault = 6;
}
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 {
float cp_hi = 1;
float cp_lo = 2;
float pwm_dc = 3;
float relais_on = 4;
float voltage = 5;
}
message FirmwareUpdate {
bool invoke_rom_bootloader = 1;
}

View File

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

View File

@@ -0,0 +1,81 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
#include "evSerial.h"
#include <chrono>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sigslot/signal.hpp>
#include "umwc.pb.h"
#include <everest/gpio/gpio.hpp>
using namespace std::chrono_literals;
volatile bool sw_version_received = false;
void recvKeepAliveLo(KeepAliveLo s) {
printf("Current uMWC SW Version: %s (Protocol %i.%i)\n", s.sw_version_string, s.protocol_version_major,
s.protocol_version_minor);
sw_version_received = true;
}
void help() {
printf("\nUsage: ./umwc_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("uMWC ROM Bootloader Firmware Updater\n");
if (argc != 3) {
help();
exit(0);
}
const char* device = argv[1];
const char* 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;
std::this_thread::sleep_for(100us);
}
printf("\nRebooting uMWC in ROM Bootloader mode...\n");
// send some dummy commands to make sure protocol is in sync
p->enable(false);
p->enable(false);
// now reboot uC in boot loader mode
p->firmwareUpdate(true);
sleep(1);
delete p;
sleep(1);
// Try to hardware reset Yeti controller to be in a known state
Everest::Gpio reset_gpio;
reset_gpio.open("gpiochip0", 27);
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);
char cmd[1000];
sprintf(cmd, "stm32flash -b 115200 %.100s -v -w %.100s -R", device, filename);
// 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;
}