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,154 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
// Portions (c) 2025 Analog Devices Inc.
#include "AdAcEvse22KwzKitBSP.hpp"
#include <fmt/core.h>
#include <utils/date.hpp>
namespace module {
void AdAcEvse22KwzKitBSP::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);
}
void AdAcEvse22KwzKitBSP::ready() {
serial.run();
if (!serial.reset(config.reset_gpio_chip, config.reset_gpio)) {
EVLOG_error << "AD-ACEVSE22KWZ-KIT reset not successful.";
}
serial.signalSpuriousReset.connect([this]() { EVLOG_error << "AD-ACEVSE22KWZ-KIT spurious reset!"; });
serial.signalConnectionTimeout.connect([this]() { EVLOG_error << "AD-ACEVSE22KWZ-KIT UART timeout!"; });
invoke_ready(*p_powermeter);
invoke_ready(*p_board_support);
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 AdAcEvse22KwzKitBSP::publish_external_telemetry_livedata(const std::string& topic,
const Everest::TelemetryMap& data) {
if (info.telemetry_enabled) {
telemetry.publish("livedata", topic, data);
}
}
bool cp_signal_fault;
void AdAcEvse22KwzKitBSP::clear_errors_on_unplug() {
if (error_MREC2GroundFailure) {
p_board_support->clear_error("evse_board_support/MREC2GroundFailure");
}
error_MREC2GroundFailure = false;
}
void AdAcEvse22KwzKitBSP::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.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,88 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef AD_AC_EVSE22KWZ_KIT_BSP_HPP
#define AD_AC_EVSE22KWZ_KIT_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/powermeter/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
#include "adkit_comms/evSerial.hpp"
// 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 AdAcEvse22KwzKitBSP : public Everest::ModuleBase {
public:
AdAcEvse22KwzKitBSP() = delete;
AdAcEvse22KwzKitBSP(const ModuleInfo& info, Everest::TelemetryProvider& telemetry,
std::unique_ptr<powermeterImplBase> p_powermeter,
std::unique_ptr<evse_board_supportImplBase> p_board_support, Conf& config) :
ModuleBase(info),
telemetry(telemetry),
p_powermeter(std::move(p_powermeter)),
p_board_support(std::move(p_board_support)),
config(config){};
Everest::TelemetryProvider& telemetry;
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
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};
// 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 // AD_AC_EVSE22KWZ_KIT_BSP_HPP

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(adkit_comms)
target_include_directories(${MODULE_NAME}
PRIVATE
"common"
"adkit_comms"
"adkit_comms/nanopb"
"adkit_comms/protobuf"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
adkit_comms
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"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,33 @@
cmake_minimum_required(VERSION 3.10)
# set the project name
project(adkit_comms VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# add the executable
add_library(adkit_comms STATIC)
ev_register_library_target(adkit_comms)
target_sources(adkit_comms
PRIVATE
evSerial.cpp
protobuf/adkit.pb.c
)
target_include_directories(adkit_comms
PUBLIC
"${PROJECT_BINARY_DIR}"
protobuf
)
target_link_libraries(adkit_comms
PUBLIC
date::date-tz
everest::nanopb
PRIVATE
Pal::Sigslot
everest::framework
everest::gpio
)

View File

@@ -0,0 +1,387 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
// Portions (c) 2025 Analog Devices Inc.
#include "evSerial.hpp"
#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 "adkit.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:
// detect connection timeout if keep_alive packets stop coming...
last_timeout_detection_timestamp = date::utc_clock::now();
signalKeepAliveLo(msg_in.payload.keep_alive);
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_error_flags_tag:
signalErrorFlags(msg_in.payload.error_flags);
break;
case McuToEverest_reset_tag:
// reset keep alive so that timeouts don't occur upon reset during initial start up
last_timeout_detection_timestamp = date::utc_clock::now();
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_timeout_detection_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);
}
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 AD-ACEVSE22KWZ-KIT 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 AD-ACEVSE22KWZ-KIT 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 < 100; 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,76 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
// Portions (c) 2025 Analog Devices Inc.
#ifndef AD_ACEVSE22KWZ_KIT_SERIAL_HPP
#define AD_ACEVSE22KWZ_KIT_SERIAL_HPP
#include "adkit.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 keepAlive();
void setPWM(uint32_t dc);
void allowPowerOn(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<> 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_timeout_detection_timestamp;
};
#endif

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,33 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.8 */
#include "adkit.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)

View File

@@ -0,0 +1,352 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.8 */
#ifndef PB_ADKIT_PB_H_INCLUDED
#define PB_ADKIT_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;
/* Struct definitions */
typedef struct _ErrorFlags {
bool diode_fault;
bool rcd_selftest_failed;
bool rcd_triggered;
bool ventilation_not_available;
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;
/* 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 {
KeepAlive keep_alive;
uint32_t pwm_duty_cycle; /* in 0.01 %, 0 = State F, 10000 = X1 */
bool allow_power_on;
bool reset;
} payload;
} EverestToMcu;
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 {
KeepAliveLo keep_alive;
ResetReason reset;
CpState cp_state;
bool relais_state; /* false: relais are off, true: relais are on */
ErrorFlags error_flags;
Telemetry telemetry;
PpState pp_state;
PowerMeter power_meter;
} payload;
} McuToEverest;
#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 McuToEverest_payload_reset_ENUMTYPE ResetReason
#define McuToEverest_payload_cp_state_ENUMTYPE CpState
#define McuToEverest_payload_pp_state_ENUMTYPE PpState
/* Initializer values for message structs */
#define EverestToMcu_init_default {0, {KeepAlive_init_default}}
#define McuToEverest_init_default {0, {KeepAliveLo_init_default}}
#define ErrorFlags_init_default {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 EverestToMcu_init_zero {0, {KeepAlive_init_zero}}
#define McuToEverest_init_zero {0, {KeepAliveLo_init_zero}}
#define ErrorFlags_init_zero {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}
/* 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_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 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 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_power_meter_tag 108
/* Struct field encoding specification for nanopb */
#define EverestToMcu_FIELDLIST(X, a) \
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)
#define EverestToMcu_CALLBACK NULL
#define EverestToMcu_DEFAULT NULL
#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, 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, 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
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;
/* 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
/* Maximum encoded size of messages (where known) */
#define ADKIT_PB_H_MAX_SIZE McuToEverest_size
#define ErrorFlags_size 12
#define EverestToMcu_size 73
#define KeepAliveLo_size 106
#define KeepAlive_size 70
#define McuToEverest_size 124
#define PowerMeter_size 121
#define Telemetry_size 12
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -0,0 +1,123 @@
syntax = "proto3";
/*
This container message is send from EVerest to MCU and may contain any allowed message in that direction.
*/
message EverestToMcu {
// Please keep IDs and don't use reserved IDs to maintain support with older firmware versions
reserved 16, 102, 106;
oneof payload {
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;
}
}
/*
This container message is send from MCU to EVerest and may contain any allowed message in that direction.
*/
message McuToEverest {
// Please keep IDs and don't use reserved IDs to maintain support with older firmware versions
reserved 107;
oneof payload {
KeepAliveLo keep_alive = 3;
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;
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 {
// Please keep IDs and don't use reserved IDs to maintain support with older firmware versions
reserved 5;
bool diode_fault = 1;
bool rcd_selftest_failed = 2;
bool rcd_triggered = 3;
bool ventilation_not_available = 4;
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;
}
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;
}

View File

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

View File

@@ -0,0 +1,200 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
// Portions (c) 2025 Analog Devices Inc.
#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 << "AD-ACEVSE22KWZ-KIT 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 AD-ACEVSE22KWZ-KIT 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) {
EVLOG_warning << "AdAdEvse22KwzKitBSP doesn't support ac_switch_three_phases_while_charging, ignoring command.";
}
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,77 @@
// 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 "../AdAcEvse22KwzKitBSP.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<AdAcEvse22KwzKitBSP>& 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<AdAcEvse22KwzKitBSP>& 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,222 @@
.. _everest_modules_handwritten_AdAcEvse22KwzKitBSP:
.. ************************
.. AdAcEvse22KwzKitBSP
.. ************************
The module ``AdAcEvse22KwzKitBSP`` is a board support driver for the Analog Devices
`AD-ACEVSE22KWZ-KIT <https://www.analog.com/en/resources/evaluation-hardware-and-software/evaluation-boards-kits/ad-acevse22kwz-kit.html#eb-overview>`_
EVSE reference design.
AdAcEvse22KwzKitBSP Quickstart
==============================
A typical hardware setup would consist of the AD-ACEVSE22KWZ-KIT and a Raspberry Pi 4 running
EVerest with this module. Communication between AD-ACEVSE22KWZ-KIT and the AdAcEvse22KwzKitBSP
on the Raspberry Pi 4 is through a 3.3V TTL UART link with a default configuration of 115200 bps 8N1.
There is also a GPIO used to reset AD-ACEVSE22KWZ-KIT from EVerest so the firmware is in a known state.
Hardware Connectivity
---------------------
By default, AD-ACEVSE22KWZ-KIT supports the Pionix Yak board and Raspberry Pi 4 as host boards running
EVerest.
The simplest way to get AD-ACEVSE22KWZ-KIT up and running with EVerest is with a Pionix Yak board. First,
you must connect AD-ACEVSE22KWZ-KIT's P3 to Yak's J3 using a 10-wire cable. Note that using the 10-wire
cable has Yak powered off AD-ACEVSE22KWZ-KIT's 12V supply.
Next, we need to add the AdAcEvse22KwzKitBSP as an active module inside the configuration YAML file
passed to EVerest:
.. code-block:: yaml
active_modules:
# ** Other EVerest modules **
adacevse22kwz_driver:
telemetry:
id: 1
config_module:
baud_rate: 115200 # default baud rate
reset_gpio: 27 # Yak reset GPIO pin
serial_port: /dev/serial0 # Yak UART0 port
connections: {}
module: AdAcEvse22KwzKitBSP
# ** Other EVerest modules **
Next, we need to link our active AdAcEvse22KwzKitBSP to EvseManager to facilitate high-level charging
logic. This can be done by adding the following to EvseManager inside the EVerest configuration YAML:
.. code-block:: yaml
active_modules:
# ** Other EVerest modules **
evse_manager:
config_module:
# ** Various EvseManager configurations **
connections:
bsp:
- implementation_id: board_support
module_id: adacevse22kwz_driver
powermeter_grid_side:
- implementation_id: powermeter
module_id: adacevse22kwz_driver
module: EvseManager
telemetry:
id: 1
# ** Other EVerest modules **
Finally, we can start EVerest. AD-ACEVSE22KWZ-KIT should now be able to communicate with EVerest
on the Yak board using AdAcEvse22KwzKitBSP.
AD-ACEVSE22KWZ-KIT can also be run off a Raspberry Pi 4 by using various test points on the
AD-ACEVSE22KWZ-KIT instead of the 10-wire cable. These test points can then be connected to
Raspberry Pi pins which can operate as a UART link and a standard GPIO:
.. list-table::
:header-rows: 1
* - Purpose
- Test Point
- RPI Pin
* - Reset Pin
- TP_18
- GPIO27
* - Host TX
- TP_28
- TX_D0
* - Host RX
- TP_41
- RX_D0
* - Shared ground
- Any GND pin
- Any GND pin
You can now follow the steps used for configuring EVerest on the Yak board to start EVerest
on the Raspberry Pi.
Protocol
========
EVerest can send commands to AD-ACEVSE22KWZ-KIT and AD-ACEVSE22KWZ-KIT 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 ``adkit_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 ``adkit_comms/protobuf``.
``adkit.proto`` contains all messages that can be sent by EVerest and AD-ACEVSE22KWZ-KIT.
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 AD-ACEVSE22KWZ-KIT Firmware to ensure the same
definition is used on both sides when making changes.
Modes of Operation
-----------------------------
AD-ACEVSE22KWZ-KIT board operates in the following two modes:
``Hostless mode``: AD-ACEVSE22KWZ-KIT acts as a standalone EVSE
and will control PWM and relay state without external influence. In this
mode, PWM will be enabled immediately upon entering state B1 with the
relay closing in state C2 assuming no errors occur. If an error occurs
(i.e. RCD trigger, diode short, C1 timeout, etc.), AD-ACEVSE22KWZ-KIT
will open the relay and disable PWM until state A1 is reentered where
the errors will be cleared.
``Host-driven mode``: AD-ACEVSE22KWZ-KIT will allow EVerest to influence PWM
and relay states. In this mode, PWM will not be enabled until an EVerest
``PwmDutyCycle`` command is received. Similarly, the relay will not open in state
C2 until an ``AllowPowerOn`` message is received. AD-ACEVSE22KWZ-KIT can
override relay and PWM state in the event of an error.
By default, the AD-ACEVSE22KWZ-KIT operates in hostless mode until a message
is received from the host. Additionally, all outbound messages from
AD-ACEVSE22KWZ-KIT are sent irrespective of mode of operation. This enables
AD-ACEVSE22KWZ-KIT evaluation without using EVerest.
Message types
-------------
AD-ACEVSE22KWZ-KIT supports the following set of messages:
EVerest to AD-ACEVSE22KWZ-KIT:
______________________________
``AllowPowerOn(bool)``: Inform AD-ACEVSE22KWZ-KIT that it is allowed to
switch on the power relays/contactors to the car on (true) or must switch
off now (false). The final decision remains with AD-ACEVSE22KWZ-KIT 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 C etc).
On power off, AD-ACEVSE22KWZ-KIT will switch off immediately.
``PwmDutyCycle(uint32)``: Set AD-ACEVSE22KWZ-KIT PWM state and duty
cycle. PWM can be enabled at specific duty cycle by passing a value of
1-10000, where each value corresponds to 0.0001% duty cycle (i.e. 50%
duty cycle = 5000 passed value). AD-ACEVSE22KWZ will ignore any duty cycles
greater than 5333 as this corresponds to the maximum duty cycle supported.
PWM can be disabled by passing a value greater than 10000. PWM state F can
be enabled by passing a PWM value of 0.
``KeepAlive(Message)``: EVerest sends this packet to AD-ACEVSE22KWZ-KIT at 1Hz.
Currently unused by AD-ACEVSE22KWZ-KIT.
``Reset(bool)``: Reset AD-ACEVSE22KWZ-KIT firmware.
AD-ACEVSE22KWZ-KIT to EVerest
-----------------------------
``CpState(enum)``: Notify EVerest of current CP state (A/B/C/D/E/F). Sent upon
state change. AD-ACEVSE22KWZ-KIT currently doesn't support State D.
``RelaisState(bool)``: Notify EVerest of current relay state. Sent upon relay
closing/opening. True corresponds to relay closed and false is sent when relay
is open.
``PpState(enum)``: Notify EVerest of current PP state (NC/13A/20A/32A/70A/F).
AD-ACEVSE22KWZ-KIT currently doesn't support PP for maximum output current
so 32A is sent by default.
``PowerMeter(Message)``: Sent roughly every second when relay is closed.
Contains all data from the ADE9178 power measurement.
``ErrorState(Message)``: Notify EVerest of active errors. Sent when an errors
are set/cleared. Each error has an associated boolean value where true
corresponds to active error and false corresponds to error not active. Currently,
only diode faults, RCD triggered, and overcurrent are supported by
AD-ACEVSE22KWZ-KIT.
``Telemetry(Message)``: Telemetry message with cp pwm high and low voltage
values. Not currently supported by AD-ACEVSE22KWZ-KIT firmware.
``KeepAliveLo(Message)``: AD-ACEVSE22KWZ-KIT sends this every 3 seconds to keep
connection online.
``ResetReason(enum)``: Sent once on boot of the AD-ACEVSE22KWZ-KIT firmware.

View File

@@ -0,0 +1,45 @@
description: Board support package module for the AD-ACEVSE22KWZ-KIT reference design
config:
serial_port:
description: Serial port the AD-ACEVSE22KWZ-KIT is connected to
type: string
default: /dev/ttyUSB0
baud_rate:
description: Serial baud rate to use when communicating with the AD-ACEVSE22KWZ-KIT
type: integer
minimum: 9600
maximum: 230400
default: 115200
reset_gpio_chip:
description: >-
Reset GPIO chip to use to HW reset the AD-ACEVSE22KWZ-KIT. If set to empty string, it is disabled.
type: string
default: 'gpiochip0'
reset_gpio:
description: GPIO line to use to reset AD-ACEVSE22KWZ-KIT
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 AD-ACEVSE22KWZ-KIT Internal Power Meter
board_support:
interface: evse_board_support
description: provides the board support Interface to low level control control pilot, relais, 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
- Ryan Wiebe

View File

@@ -0,0 +1,73 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
// Portions (c) 2025 Analog Devices Inc.
#include "powermeterImpl.hpp"
#include <utils/date.hpp>
namespace module {
namespace powermeter {
static types::powermeter::Powermeter adacevse22kwzkit_to_everest(const PowerMeter& p) {
types::powermeter::Powermeter j;
j.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
j.meter_id = "AD_ACEVSE22KWZ_KIT_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(adacevse22kwzkit_to_everest(p)); });
}
void powermeterImpl::ready() {
}
types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) {
return {types::powermeter::TransactionRequestStatus::NOT_SUPPORTED,
{},
{},
"AdAcEvse22KwzKitBSP 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,
"AdAcEvse22KwzKitBSP 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 "../AdAcEvse22KwzKitBSP.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<AdAcEvse22KwzKitBSP>& 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<AdAcEvse22KwzKitBSP>& 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,5 @@
ev_add_module(AdAcEvse22KwzKitBSP)
ev_add_module(MicroMegaWattBSP)
ev_add_module(PhyVersoBSP)
ev_add_module(TIDA010939)
ev_add_module(YetiDriver)

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;
}

View File

@@ -0,0 +1,20 @@
load("//modules:module.bzl", "cc_everest_module")
IMPLS = [
"connector_2",
"connector_1",
"rcd_1",
"rcd_2",
"connector_lock_1",
"connector_lock_2",
]
cc_everest_module(
name = "PhyVersoBSP",
deps = [
"//modules/HardwareDrivers/EVSE/PhyVersoBSP/phyverso_gpio",
"//modules/HardwareDrivers/EVSE/PhyVersoBSP/phyverso_mcu_comms",
],
impls = IMPLS,
srcs = ["board_support_common.cpp", "board_support_common.hpp"],
)

View File

@@ -0,0 +1,61 @@
#
# 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
# insert your custom targets and additional config variables here
add_subdirectory(phyverso_mcu_comms)
add_subdirectory(phyverso_cli)
add_subdirectory(phyverso_gpio)
target_include_directories(${MODULE_NAME}
PRIVATE
"common"
"phyverso_mcu_comms"
"phyverso_mcu_comms/nanopb"
"phyverso_mcu_comms/protobuf"
"phyverso_config"
"phyverso_gpio"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
everest::gpio
phyverso_mcu_comms
phyverso_config
phyverso_gpio
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"connector_1/evse_board_supportImpl.cpp"
"connector_2/evse_board_supportImpl.cpp"
"rcd_1/ac_rcdImpl.cpp"
"rcd_2/ac_rcdImpl.cpp"
"connector_lock_1/connector_lockImpl.cpp"
"connector_lock_2/connector_lockImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
target_sources(${MODULE_NAME}
PRIVATE
"board_support_common.cpp"
)
# install MCU configs
# List all JSON files in the current directory and subdirectories
file(GLOB_RECURSE JSON_FILES "*.json")
# Check if the list is not empty
if(JSON_FILES)
# Install JSON files
install(FILES ${JSON_FILES} DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/everest")
endif()
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,166 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "PhyVersoBSP.hpp"
#include <filesystem>
namespace module {
void PhyVersoBSP::init() {
// transform everest config into evConfig accessible to evSerial
everest_config_to_verso_config();
// initialize serial driver
if (!serial.open_device(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;
}
// init user gpios
if (not gpio.init_gpios()) {
EVLOG_error << "Could not initialize user GPIOs. Terminating.";
return;
}
serial.flush_buffers();
serial.signal_config_request.connect([&]() {
serial.send_config();
EVLOG_info << "Sent config packet to MCU";
});
serial.signal_connection_timeout.connect([this]() {
auto err = p_connector_1->error_factory->create_error("evse_board_support/CommunicationFault", "McuToEverest",
"Serial connection to MCU timed out");
p_connector_1->raise_error(err);
err = p_connector_2->error_factory->create_error("evse_board_support/CommunicationFault", "McuToEverest",
"Serial connection to MCU timed out");
p_connector_2->raise_error(err);
});
serial.signal_error_flags.connect([this](int connector, ErrorFlags error_flags) {
// heartbeat failure from Mcu side (not receiving packets) will be visible in both connector errors
if (error_flags.heartbeat_timeout != last_heartbeat_error) {
if (error_flags.heartbeat_timeout) {
auto err = p_connector_1->error_factory->create_error(
"evse_board_support/CommunicationFault", "EverestToMcu", "MCU did not receive Everest heartbeat");
p_connector_1->raise_error(err);
err = p_connector_2->error_factory->create_error(
"evse_board_support/CommunicationFault", "EverestToMcu", "MCU did not receive Everest heartbeat");
p_connector_2->raise_error(err);
} else {
p_connector_1->clear_error("evse_board_support/CommunicationFault", "EverestToMcu");
p_connector_2->clear_error("evse_board_support/CommunicationFault", "EverestToMcu");
}
}
last_heartbeat_error = error_flags.heartbeat_timeout;
});
serial.signal_keep_alive.connect([this](KeepAlive d) {
mcu_config_done = d.configuration_done;
p_connector_1->clear_error("evse_board_support/CommunicationFault", "McuToEverest");
p_connector_2->clear_error("evse_board_support/CommunicationFault", "McuToEverest");
});
serial.reset(1);
serial.run();
gpio.run();
// very sporadically multiple resets needed for MCU to respond -> retrying until we get MCU response in a
// configured, ready and running state
mcu_config_done = false;
uint16_t n_tries = 0;
while (!mcu_config_done) {
serial.keep_alive();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
n_tries++;
if (n_tries > 20) {
EVLOG_info << "Trying reset again";
serial.flush_buffers();
serial.reset(1);
n_tries = 0;
}
}
invoke_init(*p_connector_1);
invoke_init(*p_connector_2);
invoke_init(*p_rcd_1);
invoke_init(*p_rcd_2);
invoke_init(*p_connector_lock_1);
invoke_init(*p_connector_lock_2);
}
void PhyVersoBSP::ready() {
invoke_ready(*p_connector_1);
invoke_ready(*p_connector_2);
invoke_ready(*p_rcd_1);
invoke_ready(*p_rcd_2);
invoke_ready(*p_connector_lock_1);
invoke_ready(*p_connector_lock_2);
if (not serial.is_open()) {
auto err = p_connector_1->error_factory->create_error("evse_board_support/CommunicationFault", "",
"Could not open serial port.");
p_connector_1->raise_error(err);
err = p_connector_2->error_factory->create_error("evse_board_support/CommunicationFault", "",
"Could not open serial port.");
p_connector_2->raise_error(err);
}
}
// fills evConfig bridge with config values from manifest/everest config
void PhyVersoBSP::everest_config_to_verso_config() {
// if a port is configured to be AC and has a socket, a motor lock type specification/usage is mandatory
if ((this->config.conn1_disable_port == false) && (this->config.conn1_dc == false) &&
(this->config.conn1_has_socket == true) && (this->config.conn1_motor_lock_type < 1)) {
EVLOG_critical << "Motor lock type for connector 1 has to be specified when using connector 1 as AC charging "
"port with a socket/detachable charging cable!";
throw std::runtime_error("Motor lock type for connector 1 has to be specified when using connector 1 as AC "
"charging port with a socket/detachable charging cable!");
}
if ((this->config.conn2_disable_port == false) && (this->config.conn2_dc == false) &&
(this->config.conn2_has_socket == true) && (this->config.conn2_motor_lock_type < 1)) {
EVLOG_critical << "Motor lock type for connector 2 has to be specified when using connector 2 as AC charging "
"port with a socket/detachable charging cable!";
throw std::runtime_error("Motor lock type for connector 2 has to be specified when using connector 2 as AC "
"charging port with a socket/detachable charging cable!");
}
if ((this->config.conn1_feedback_pull < 0) || (this->config.conn1_feedback_pull > 2)) {
EVLOG_error << "conn1_feedback_pull out of range! Falling back to default: 2";
verso_config.conf.conn1_feedback_pull = 2;
} else {
verso_config.conf.conn1_feedback_pull = this->config.conn1_feedback_pull;
}
if ((this->config.conn2_feedback_pull < 0) || (this->config.conn2_feedback_pull > 2)) {
EVLOG_error << "conn2_feedback_pull out of range! Falling back to default: 2";
verso_config.conf.conn2_feedback_pull = 2;
} else {
verso_config.conf.conn2_feedback_pull = this->config.conn2_feedback_pull;
}
verso_config.conf.serial_port = this->config.serial_port;
verso_config.conf.baud_rate = this->config.baud_rate;
verso_config.conf.reset_gpio_bank = this->config.reset_gpio_bank;
verso_config.conf.reset_gpio_pin = this->config.reset_gpio_pin;
verso_config.conf.conn1_motor_lock_type = this->config.conn1_motor_lock_type;
verso_config.conf.conn2_motor_lock_type = this->config.conn2_motor_lock_type;
verso_config.conf.conn1_gpio_stop_button_enabled = this->config.conn1_gpio_stop_button_enabled;
verso_config.conf.conn1_gpio_stop_button_bank = this->config.conn1_gpio_stop_button_bank;
verso_config.conf.conn1_gpio_stop_button_pin = this->config.conn1_gpio_stop_button_pin;
verso_config.conf.conn1_gpio_stop_button_invert = this->config.conn1_gpio_stop_button_invert;
verso_config.conf.conn2_gpio_stop_button_enabled = this->config.conn2_gpio_stop_button_enabled;
verso_config.conf.conn2_gpio_stop_button_bank = this->config.conn2_gpio_stop_button_bank;
verso_config.conf.conn2_gpio_stop_button_pin = this->config.conn2_gpio_stop_button_pin;
verso_config.conf.conn2_gpio_stop_button_invert = this->config.conn2_gpio_stop_button_invert;
verso_config.conf.conn1_disable_port = this->config.conn1_disable_port;
verso_config.conf.conn2_disable_port = this->config.conn2_disable_port;
verso_config.conf.conn1_feedback_active_low = this->config.conn1_feedback_active_low;
verso_config.conf.conn2_feedback_active_low = this->config.conn2_feedback_active_low;
verso_config.conf.conn1_dc = this->config.conn1_dc;
verso_config.conf.conn2_dc = this->config.conn2_dc;
}
} // namespace module

View File

@@ -0,0 +1,128 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef PHY_VERSO_BSP_HPP
#define PHY_VERSO_BSP_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>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
#include "phyverso_gpio/evGpio.h"
#include "phyverso_mcu_comms/evSerial.h"
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string serial_port;
int baud_rate;
int reset_gpio;
int conn1_max_current_A_import;
int conn1_min_current_A_import;
int conn1_min_phase_count_import;
int conn1_max_phase_count_import;
int conn1_min_current_A_export;
int conn1_max_current_A_export;
int conn1_min_phase_count_export;
int conn1_max_phase_count_export;
bool conn1_has_socket;
bool conn1_dc;
int conn2_max_current_A_import;
int conn2_min_current_A_import;
int conn2_min_phase_count_import;
int conn2_max_phase_count_import;
int conn2_min_current_A_export;
int conn2_max_current_A_export;
int conn2_min_phase_count_export;
int conn2_max_phase_count_export;
bool conn2_has_socket;
bool conn2_dc;
int reset_gpio_bank;
int reset_gpio_pin;
int conn1_motor_lock_type;
int conn2_motor_lock_type;
bool conn1_gpio_stop_button_enabled;
std::string conn1_gpio_stop_button_bank;
int conn1_gpio_stop_button_pin;
bool conn1_gpio_stop_button_invert;
bool conn2_gpio_stop_button_enabled;
std::string conn2_gpio_stop_button_bank;
int conn2_gpio_stop_button_pin;
bool conn2_gpio_stop_button_invert;
bool conn1_disable_port;
bool conn2_disable_port;
bool conn1_feedback_active_low;
bool conn2_feedback_active_low;
int conn1_feedback_pull;
int conn2_feedback_pull;
};
class PhyVersoBSP : public Everest::ModuleBase {
public:
PhyVersoBSP() = delete;
PhyVersoBSP(const ModuleInfo& info, Everest::TelemetryProvider& telemetry,
std::unique_ptr<evse_board_supportImplBase> p_connector_1,
std::unique_ptr<evse_board_supportImplBase> p_connector_2, std::unique_ptr<ac_rcdImplBase> p_rcd_1,
std::unique_ptr<ac_rcdImplBase> p_rcd_2, std::unique_ptr<connector_lockImplBase> p_connector_lock_1,
std::unique_ptr<connector_lockImplBase> p_connector_lock_2, Conf& config) :
ModuleBase(info),
telemetry(telemetry),
p_connector_1(std::move(p_connector_1)),
p_connector_2(std::move(p_connector_2)),
p_rcd_1(std::move(p_rcd_1)),
p_rcd_2(std::move(p_rcd_2)),
p_connector_lock_1(std::move(p_connector_lock_1)),
p_connector_lock_2(std::move(p_connector_lock_2)),
config(config){};
Everest::TelemetryProvider& telemetry;
const std::unique_ptr<evse_board_supportImplBase> p_connector_1;
const std::unique_ptr<evse_board_supportImplBase> p_connector_2;
const std::unique_ptr<ac_rcdImplBase> p_rcd_1;
const std::unique_ptr<ac_rcdImplBase> p_rcd_2;
const std::unique_ptr<connector_lockImplBase> p_connector_lock_1;
const std::unique_ptr<connector_lockImplBase> p_connector_lock_2;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
evSerial serial{verso_config};
evConfig verso_config;
evGpio gpio{verso_config};
// 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
void everest_config_to_verso_config();
bool last_heartbeat_error;
bool mcu_config_done = false;
// 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 // PHY_VERSO_BSP_HPP

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "board_support_common.hpp"
namespace module {
types::board_support_common::BspEvent to_bsp_event(CpState s) {
switch (s) {
case CpState_STATE_A:
return {types::board_support_common::Event::A};
case CpState_STATE_B:
return {types::board_support_common::Event::B};
case CpState_STATE_C:
return {types::board_support_common::Event::C};
case CpState_STATE_D:
return {types::board_support_common::Event::D};
case CpState_STATE_E:
return {types::board_support_common::Event::E};
case CpState_STATE_F:
return {types::board_support_common::Event::F};
// This should never happen
default:
return {types::board_support_common::Event::F};
}
}
types::board_support_common::BspEvent to_bsp_event(CoilState s) {
// TODO: implement coil type handling
if (s.coil_state) {
return {types::board_support_common::Event::PowerOn};
} else {
return {types::board_support_common::Event::PowerOff};
}
}
types::board_support_common::ProximityPilot to_pp_ampacity(PpState s) {
switch (s) {
case PpState_STATE_NC: {
return {types::board_support_common::Ampacity::None};
}
case PpState_STATE_13A: {
return {types::board_support_common::Ampacity::A_13};
}
case PpState_STATE_20A: {
return {types::board_support_common::Ampacity::A_20};
}
case PpState_STATE_32A: {
return {types::board_support_common::Ampacity::A_32};
}
case PpState_STATE_70A: {
return {types::board_support_common::Ampacity::A_63_3ph_70_1ph};
}
default: {
return {types::board_support_common::Ampacity::None};
}
}
}
} // namespace module

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef EVSE_BOARD_SUPPORT_COMMON_HPP
#define EVSE_BOARD_SUPPORT_COMMON_HPP
#include "phyverso.pb.h"
#include <generated/interfaces/evse_board_support/Implementation.hpp>
namespace module {
types::board_support_common::BspEvent to_bsp_event(CpState s);
types::board_support_common::BspEvent to_bsp_event(CoilState s);
types::board_support_common::ProximityPilot to_pp_ampacity(PpState s);
} // namespace module
#endif // EVSE_BOARD_SUPPORT_COMMON_HPP

View File

@@ -0,0 +1,5 @@
{
"conn1_motor_lock_type": 1,
"conn2_motor_lock_type": 1,
"reset_gpio_bank": 2
}

View File

@@ -0,0 +1,5 @@
{
"conn1_motor_lock_type": 2,
"conn2_motor_lock_type": 2,
"reset_gpio_bank": 2
}

View File

@@ -0,0 +1,181 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "evse_board_supportImpl.hpp"
namespace module {
namespace connector_1 {
void evse_board_supportImpl::init() {
{
std::scoped_lock lock(caps_mutex);
caps.min_current_A_import = mod->config.conn1_min_current_A_import;
caps.max_current_A_import = mod->config.conn1_max_current_A_import;
caps.min_phase_count_import = mod->config.conn1_min_phase_count_import;
caps.max_phase_count_import = mod->config.conn1_max_phase_count_import;
caps.supports_changing_phases_during_charging = false;
caps.supports_cp_state_E = false;
caps.min_current_A_export = mod->config.conn1_min_current_A_export;
caps.max_current_A_export = mod->config.conn1_max_current_A_export;
caps.min_phase_count_export = mod->config.conn1_min_phase_count_export;
caps.max_phase_count_export = mod->config.conn1_max_phase_count_export;
if (mod->config.conn1_has_socket) {
caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Socket;
} else {
caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Cable;
}
}
mod->serial.signal_cp_state.connect([this](int connector, CpState s) {
if (connector == 1 && s != last_cp_state) {
publish_event(to_bsp_event(s));
EVLOG_info << "[1] CP State changed: " << to_bsp_event(s);
last_cp_state = s;
}
});
mod->serial.signal_set_coil_state_response.connect([this](int connector, CoilState s) {
if (connector == 1) {
EVLOG_info << "[1] Relais: " << (s.coil_state ? "ON" : "OFF");
publish_event(to_bsp_event(s));
}
});
mod->serial.signal_telemetry.connect([this](int connector, Telemetry t) {
if (connector == 1) {
EVLOG_info << "[1] CP Voltage: " << t.cp_voltage_hi << " " << t.cp_voltage_lo;
}
});
mod->serial.signal_pp_state.connect([this](int connector, PpState s) {
if (connector == 1) {
if (last_pp_state != s) {
EVLOG_info << "[1] PpState " << s;
publish_ac_pp_ampacity(to_pp_ampacity(s));
}
last_pp_state = s;
}
});
mod->gpio.signal_stop_button_state.connect([this](int connector, bool state) {
if (connector == 1 && (state != last_stop_button_state)) {
types::evse_manager::StopTransactionRequest request;
request.reason = types::evse_manager::StopTransactionReason::Local;
this->publish_request_stop_transaction(request);
EVLOG_info << "[1] Request stop button state: " << (state ? "PUSHED" : "RELEASED");
last_stop_button_state = state;
}
});
mod->serial.signal_error_flags.connect([this](int connector, ErrorFlags error_flags) {
if (connector == 1) {
// Contactor feedback divergence
if (error_flags.coil_feedback_diverges != last_error_flags.coil_feedback_diverges) {
if (error_flags.coil_feedback_diverges) {
Everest::error::Error error_object = this->error_factory->create_error(
"evse_board_support/MREC17EVSEContactorFault", "",
"Port 1 contactor feedback diverges from target state", Everest::error::Severity::High);
this->raise_error(error_object);
} else {
this->clear_error("evse_board_support/MREC17EVSEContactorFault");
}
}
// Diode fault
if (error_flags.diode_fault != last_error_flags.diode_fault) {
if (error_flags.diode_fault) {
Everest::error::Error error_object = this->error_factory->create_error(
"evse_board_support/DiodeFault", "", "Port 1 diode fault", Everest::error::Severity::High);
this->raise_error(error_object);
} else {
this->clear_error("evse_board_support/DiodeFault");
}
}
// PP fault
if (error_flags.pp_signal_fault != last_error_flags.pp_signal_fault) {
if (error_flags.pp_signal_fault) {
Everest::error::Error error_object =
this->error_factory->create_error("evse_board_support/MREC23ProximityFault", "",
"Port 1 PP signal fault", Everest::error::Severity::High);
this->raise_error(error_object);
} else {
this->clear_error("evse_board_support/MREC23ProximityFault");
}
}
last_error_flags = error_flags;
}
});
}
void evse_board_supportImpl::ready() {
{
std::scoped_lock lock(caps_mutex);
publish_capabilities(caps);
}
}
void evse_board_supportImpl::handle_enable(bool& value) {
enabled = value;
if (enabled) {
mod->serial.set_pwm(1, last_pwm_raw);
} else {
mod->serial.set_pwm(1, 0);
}
}
void evse_board_supportImpl::handle_pwm_on(double& value) {
if (value >= 0 && value <= 100.) {
last_pwm_raw = value * 100;
if (enabled) {
mod->serial.set_pwm(1, last_pwm_raw);
}
} else {
EVLOG_warning << "Invalid pwm value " << value;
}
}
void evse_board_supportImpl::handle_cp_state_X1() {
last_pwm_raw = 10000;
if (enabled) {
mod->serial.set_pwm(1, last_pwm_raw);
}
}
void evse_board_supportImpl::handle_cp_state_F() {
last_pwm_raw = 0;
if (enabled) {
mod->serial.set_pwm(1, last_pwm_raw);
}
}
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) {
if (mod->config.conn1_disable_port) {
EVLOG_error << "[1] Port disabled; Cannot set power_on!";
return;
}
if (mod->config.conn1_dc) {
mod->serial.set_coil_state_request(1, CoilType_COIL_DC1, value.allow_power_on);
} else {
mod->serial.set_coil_state_request(1, CoilType_COIL_AC, 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 connector_1
} // namespace module

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef CONNECTOR_1_EVSE_BOARD_SUPPORT_IMPL_HPP
#define CONNECTOR_1_EVSE_BOARD_SUPPORT_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/evse_board_support/Implementation.hpp>
#include "../PhyVersoBSP.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include "board_support_common.hpp"
#include "evGpio.h"
#include <atomic>
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace connector_1 {
struct Conf {};
class evse_board_supportImpl : public evse_board_supportImplBase {
public:
evse_board_supportImpl() = delete;
evse_board_supportImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<PhyVersoBSP>& mod, Conf& config) :
evse_board_supportImplBase(ev, "connector_1"), 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<PhyVersoBSP>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
types::evse_board_support::HardwareCapabilities caps;
std::mutex caps_mutex;
CpState last_cp_state;
PpState last_pp_state; ///< The last pp state received from the MCU.
bool last_stop_button_state;
ErrorFlags last_error_flags;
int last_pwm_raw{10000};
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 connector_1
} // namespace module
#endif // CONNECTOR_1_EVSE_BOARD_SUPPORT_IMPL_HPP

View File

@@ -0,0 +1,181 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "evse_board_supportImpl.hpp"
namespace module {
namespace connector_2 {
void evse_board_supportImpl::init() {
{
std::scoped_lock lock(caps_mutex);
caps.min_current_A_import = mod->config.conn2_min_current_A_import;
caps.max_current_A_import = mod->config.conn2_max_current_A_import;
caps.min_phase_count_import = mod->config.conn2_min_phase_count_import;
caps.max_phase_count_import = mod->config.conn2_max_phase_count_import;
caps.supports_changing_phases_during_charging = false;
caps.supports_cp_state_E = false;
caps.min_current_A_export = mod->config.conn2_min_current_A_export;
caps.max_current_A_export = mod->config.conn2_max_current_A_export;
caps.min_phase_count_export = mod->config.conn2_min_phase_count_export;
caps.max_phase_count_export = mod->config.conn2_max_phase_count_export;
if (mod->config.conn2_has_socket) {
caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Socket;
} else {
caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Cable;
}
}
mod->serial.signal_cp_state.connect([this](int connector, CpState s) {
if (connector == 2 && s != last_cp_state) {
publish_event(to_bsp_event(s));
EVLOG_info << "[2] CP State changed: " << to_bsp_event(s);
last_cp_state = s;
}
});
mod->serial.signal_set_coil_state_response.connect([this](int connector, CoilState s) {
if (connector == 2) {
EVLOG_info << "[2] Relais: " << (s.coil_state ? "ON" : "OFF");
publish_event(to_bsp_event(s));
}
});
mod->serial.signal_telemetry.connect([this](int connector, Telemetry t) {
if (connector == 2) {
EVLOG_info << "[2] CP Voltage: " << t.cp_voltage_hi << " " << t.cp_voltage_lo;
}
});
mod->serial.signal_pp_state.connect([this](int connector, PpState s) {
if (connector == 2) {
if (last_pp_state != s) {
EVLOG_info << "[2] PpState " << s;
publish_ac_pp_ampacity(to_pp_ampacity(s));
}
last_pp_state = s;
}
});
mod->gpio.signal_stop_button_state.connect([this](int connector, bool state) {
if (connector == 2 && (state != last_stop_button_state)) {
types::evse_manager::StopTransactionRequest request;
request.reason = types::evse_manager::StopTransactionReason::Local;
this->publish_request_stop_transaction(request);
EVLOG_info << "[2] Request stop button state: " << (state ? "PUSHED" : "RELEASED");
last_stop_button_state = state;
}
});
mod->serial.signal_error_flags.connect([this](int connector, ErrorFlags error_flags) {
if (connector == 2) {
// Contactor feedback divergence
if (error_flags.coil_feedback_diverges != last_error_flags.coil_feedback_diverges) {
if (error_flags.coil_feedback_diverges) {
Everest::error::Error error_object = this->error_factory->create_error(
"evse_board_support/MREC17EVSEContactorFault", "",
"Port 2 contactor feedback diverges from target state", Everest::error::Severity::High);
this->raise_error(error_object);
} else {
this->clear_error("evse_board_support/MREC17EVSEContactorFault");
}
}
// Diode fault
if (error_flags.diode_fault != last_error_flags.diode_fault) {
if (error_flags.diode_fault) {
Everest::error::Error error_object = this->error_factory->create_error(
"evse_board_support/DiodeFault", "", "Port 2 diode fault", Everest::error::Severity::High);
this->raise_error(error_object);
} else {
this->clear_error("evse_board_support/DiodeFault");
}
}
// PP fault
if (error_flags.pp_signal_fault != last_error_flags.pp_signal_fault) {
if (error_flags.pp_signal_fault) {
Everest::error::Error error_object =
this->error_factory->create_error("evse_board_support/MREC23ProximityFault", "",
"Port 2 PP signal fault", Everest::error::Severity::High);
this->raise_error(error_object);
} else {
this->clear_error("evse_board_support/MREC23ProximityFault");
}
}
last_error_flags = error_flags;
}
});
}
void evse_board_supportImpl::ready() {
{
std::scoped_lock lock(caps_mutex);
publish_capabilities(caps);
}
}
void evse_board_supportImpl::handle_enable(bool& value) {
enabled = value;
if (enabled) {
mod->serial.set_pwm(2, last_pwm_raw);
} else {
mod->serial.set_pwm(2, 0);
}
}
void evse_board_supportImpl::handle_pwm_on(double& value) {
if (value >= 0 && value <= 100.) {
last_pwm_raw = value * 100;
if (enabled) {
mod->serial.set_pwm(2, last_pwm_raw);
}
} else {
EVLOG_warning << "Invalid pwm value " << value;
}
}
void evse_board_supportImpl::handle_cp_state_X1() {
last_pwm_raw = 10000;
if (enabled) {
mod->serial.set_pwm(2, last_pwm_raw);
}
}
void evse_board_supportImpl::handle_cp_state_F() {
last_pwm_raw = 0;
if (enabled) {
mod->serial.set_pwm(2, last_pwm_raw);
}
}
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) {
if (mod->config.conn2_disable_port) {
EVLOG_error << "[2] Port disabled; Cannot set power_on!";
return;
}
if (mod->config.conn2_dc) {
mod->serial.set_coil_state_request(2, CoilType_COIL_DC1, value.allow_power_on);
} else {
mod->serial.set_coil_state_request(2, CoilType_COIL_AC, 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 connector_2
} // namespace module

View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef CONNECTOR_2_EVSE_BOARD_SUPPORT_IMPL_HPP
#define CONNECTOR_2_EVSE_BOARD_SUPPORT_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/evse_board_support/Implementation.hpp>
#include "../PhyVersoBSP.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include "board_support_common.hpp"
#include <atomic>
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace connector_2 {
struct Conf {};
class evse_board_supportImpl : public evse_board_supportImplBase {
public:
evse_board_supportImpl() = delete;
evse_board_supportImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<PhyVersoBSP>& mod, Conf& config) :
evse_board_supportImplBase(ev, "connector_2"), 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<PhyVersoBSP>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
types::evse_board_support::HardwareCapabilities caps;
std::mutex caps_mutex;
CpState last_cp_state;
PpState last_pp_state; ///< The last pp state received from the MCU.
bool last_stop_button_state;
ErrorFlags last_error_flags;
int last_pwm_raw{10000};
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 connector_2
} // namespace module
#endif // CONNECTOR_2_EVSE_BOARD_SUPPORT_IMPL_HPP

View File

@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "connector_lockImpl.hpp"
namespace module {
namespace connector_lock_1 {
void connector_lockImpl::init() {
}
void connector_lockImpl::ready() {
}
void connector_lockImpl::handle_lock() {
EVLOG_info << "Locking connector 1";
mod->serial.lock(1, true);
}
void connector_lockImpl::handle_unlock() {
EVLOG_info << "Unlocking connector 1";
mod->serial.lock(1, false);
}
} // namespace connector_lock_1
} // namespace module

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef CONNECTOR_LOCK_1_CONNECTOR_LOCK_IMPL_HPP
#define CONNECTOR_LOCK_1_CONNECTOR_LOCK_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/connector_lock/Implementation.hpp>
#include "../PhyVersoBSP.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_1 {
struct Conf {};
class connector_lockImpl : public connector_lockImplBase {
public:
connector_lockImpl() = delete;
connector_lockImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<PhyVersoBSP>& mod, Conf& config) :
connector_lockImplBase(ev, "connector_lock_1"), 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<PhyVersoBSP>& 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 connector_lock_1
} // namespace module
#endif // CONNECTOR_LOCK_1_CONNECTOR_LOCK_IMPL_HPP

View File

@@ -0,0 +1,26 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "connector_lockImpl.hpp"
namespace module {
namespace connector_lock_2 {
void connector_lockImpl::init() {
}
void connector_lockImpl::ready() {
}
void connector_lockImpl::handle_lock() {
EVLOG_info << "Locking connector 2";
mod->serial.lock(2, true);
}
void connector_lockImpl::handle_unlock() {
EVLOG_info << "Unlocking connector 2";
mod->serial.lock(2, false);
}
} // namespace connector_lock_2
} // namespace module

View File

@@ -0,0 +1,62 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef CONNECTOR_LOCK_2_CONNECTOR_LOCK_IMPL_HPP
#define CONNECTOR_LOCK_2_CONNECTOR_LOCK_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/connector_lock/Implementation.hpp>
#include "../PhyVersoBSP.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_2 {
struct Conf {};
class connector_lockImpl : public connector_lockImplBase {
public:
connector_lockImpl() = delete;
connector_lockImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<PhyVersoBSP>& mod, Conf& config) :
connector_lockImplBase(ev, "connector_lock_2"), 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<PhyVersoBSP>& 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 connector_lock_2
} // namespace module
#endif // CONNECTOR_LOCK_2_CONNECTOR_LOCK_IMPL_HPP

View File

@@ -0,0 +1,237 @@
description: Driver module for Phytec PhyVerso EV Charging controller with Pionix MCU firmware
config:
serial_port:
description: Serial port the hardware is connected to
type: string
default: /dev/ttyUSB0
baud_rate:
description: Serial baud rate to use when communicating with the hardware
type: integer
minimum: 9600
maximum: 230400
default: 115200
reset_gpio:
description: If set <0 it is disabled. If > 0, configured reset_gpio_bank and reset_gpio_pin configuration is used for hard reset of MCU
type: integer
minimum: -1
maximum: 1000
default: -1
conn1_max_current_A_import:
description: Maximum import current in amps
type: integer
minimum: 0
default: 16
conn1_min_current_A_import:
description: Minimum import current in amps
type: integer
minimum: 0
default: 6
conn1_min_phase_count_import:
description: Minimum phase count for import
type: integer
minimum: 1
maximum: 3
default: 3
conn1_max_phase_count_import:
description: Maximum phase count for import
type: integer
minimum: 1
maximum: 3
default: 3
conn1_min_current_A_export:
description: Minimum export current in amps
type: integer
minimum: 0
maximum: 63
default: 0
conn1_max_current_A_export:
description: Maximum export current in amps
type: integer
minimum: 0
maximum: 63
default: 0
conn1_min_phase_count_export:
description: Minimum phase count for export
type: integer
minimum: 1
maximum: 3
default: 3
conn1_max_phase_count_export:
description: Maximum phase count for export
type: integer
minimum: 1
maximum: 3
default: 3
conn1_has_socket:
description: Set to true if it has a socket, false if it has a permanently attached cable
type: boolean
default: false
conn1_dc:
description: Set to true if it is for DC, false if it is AC
type: boolean
default: false
conn2_max_current_A_import:
description: Maximum import current in amps
type: integer
minimum: 0
default: 16
conn2_min_current_A_import:
description: Minimum import current in amps
type: integer
minimum: 0
default: 6
conn2_min_phase_count_import:
description: Minimum phase count for import
type: integer
minimum: 1
maximum: 3
default: 3
conn2_max_phase_count_import:
description: Maximum phase count for import
type: integer
minimum: 1
maximum: 3
default: 3
conn2_min_current_A_export:
description: Minimum export current in amps
type: integer
minimum: 0
maximum: 63
default: 0
conn2_max_current_A_export:
description: Maximum export current in amps
type: integer
minimum: 0
maximum: 63
default: 0
conn2_min_phase_count_export:
description: Minimum phase count for export
type: integer
minimum: 1
maximum: 3
default: 3
conn2_max_phase_count_export:
description: Maximum phase count for export
type: integer
minimum: 1
maximum: 3
default: 3
conn2_has_socket:
description: Set to true if it has a socket, false if it has a permanently attached cable
type: boolean
default: false
conn2_dc:
description: Set to true if it is for DC, false if it is AC
type: boolean
default: false
reset_gpio_bank:
description: GPIO peripheral bank the nRST pin of the MCU is mapped to
type: integer
default: 1
reset_gpio_pin:
description: GPIO peripheral pin the nRST pin of the MCU is mapped to
type: integer
default: 23
conn1_motor_lock_type:
description: >
Connector 1 motor lock type;
-1 == no Lock
1 == Hella Style time-based lock,
2 == Valeo potentiometer feedback based;
If charging port has a socket and is AC charging, it will need a lock specified
type: integer
default: -1
conn2_motor_lock_type:
description: >
Connector 2 motor lock type;
-1 == no Lock
1 == Hella Style time-based lock,
2 == Valeo potentiometer feedback based;
If charging port has a socket and is AC charging, it will need a lock specified
type: integer
default: -1
conn1_gpio_stop_button_enabled:
description: Set to true to enable external charging stop button for connector 1 on a GPIO connected to the SOM
type: boolean
default: false
conn1_gpio_stop_button_bank:
description: GPIO peripheral bank for connector 1 stop button
type: string
default: gpiochip1
conn1_gpio_stop_button_pin:
description: GPIO peripheral pin for connector 1 stop button
type: integer
default: 36
conn1_gpio_stop_button_invert:
description: Set to true to invert pin logic
type: boolean
default: false
conn2_gpio_stop_button_enabled:
description: Set to true to enable external charging stop button for connector 2 on a GPIO connected to the SOM
type: boolean
default: false
conn2_gpio_stop_button_bank:
description: GPIO peripheral bank for connector 2 stop button
type: string
default: gpiochip1
conn2_gpio_stop_button_pin:
description: GPIO peripheral pin for connector 2 stop button
type: integer
default: 37
conn2_gpio_stop_button_invert:
description: Set to true to invert pin logic
type: boolean
default: false
conn1_disable_port:
description: Set to true if port 1 is neither used for AC nor DC charging (will overwrite conn1_dc parameter)
type: boolean
default: false
conn2_disable_port:
description: Set to true if port 2 is neither used for AC nor DC charging (will overwrite conn2_dc parameter)
type: boolean
default: false
conn1_feedback_active_low:
description: Set to true if relay mirror contact on port 1 feedback is active LOW, false if active HIGH; don't change for AC port config
type: boolean
default: true
conn2_feedback_active_low:
description: Set to true if relay mirror contact on port 2 feedback is active LOW, false if active HIGH; don't change for AC port config
type: boolean
default: true
conn1_feedback_pull:
description: DC port config only - specify which way internal pull resistors will work; 0 -> None, 1 -> PullUp, 2 -> PullDown (default=PD)
type: integer
default: 2
conn2_feedback_pull:
description: DC port config only - specify which way internal pull resistors will work; 0 -> None, 1 -> PullUp, 2 -> PullDown (default=PD)
type: integer
default: 2
provides:
connector_1:
interface: evse_board_support
description: provides the board support interface to low level control the proximity and control pilots, relais and motor lock
connector_2:
interface: evse_board_support
description: provides the board support interface to low level control the proximity and control pilots, relais and motor lock
rcd_1:
interface: ac_rcd
description: RCD interface for an external RDC-MD
rcd_2:
interface: ac_rcd
description: RCD interface of the onboard RDC-MD
connector_lock_1:
interface: connector_lock
description: Lock interface
connector_lock_2:
interface: connector_lock
description: Lock interface
enable_telemetry: true
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Cornelius Claussen
- Jonas Rockstroh

View File

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

View File

@@ -0,0 +1,307 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <stdio.h>
#include <string.h>
#include "evSerial.h"
#include <unistd.h>
#include "phyverso.pb.h"
#include <sigslot/signal.hpp>
std::atomic_bool sw_version_received = false;
void help() {
printf("\nUsage: ./phyverso_cli /dev/ttyXXX /path/to/config.json\n\n");
}
int main(int argc, char* argv[]) {
int selected_connector = 1;
printf("-- Phyverso CLI tool --\n");
printf("Use the following keys to send packets:\n");
printf("A or a: set AC coil on or off\n");
printf("D or d: set (monitored) DC coil on or off\n");
printf("O or o: set (unmonitored) AUX1 DC coil on or off\n");
printf("P or p: set (unmonitored) AUX2 DC coil on or off\n");
printf("L or l: motorlock lock or unlock\n");
printf("R or r: hard or soft reset\n");
printf("V: send keep alive/get version\n");
printf("K: trigger RCD TEST on selected connector\n");
printf("k: reset RCD on selected connector\n\n");
printf("1: use connector 1\n");
printf("2: use connector 2\n\n");
printf("0: PWM F (0%% DC, -12V)\n");
printf("5: PWM 5%%\n");
printf("6: PWM 10%%\n");
printf("7: PWM 80%%\n");
printf("8: PWM 97%%\n");
printf("9: PWM X1 (100%%)\n");
printf("9: PWM X1 (100%%)\n");
printf("U or u: Fan1 50%% or OFF (if your fan supports 0%% duty cycle)\n");
printf("I or i: Fan2 ON or 20%%\n");
if (argc != 3) {
help();
exit(0);
}
const char* device = argv[1];
const char* config_path = argv[2];
evConfig verso_config;
// with config rework we still have to go the json route here
if (!verso_config.open_file(config_path)) {
printf("Could not open config file \"%s\"\n", config_path);
return -1;
}
verso_config.json_conf_to_evConfig();
evSerial p(verso_config);
if (!p.open_device(device, 115200)) {
printf("Cannot open device \"%s\"\n", device);
} else {
p.run();
p.signal_config_request.connect([&]() {
printf("Received config request\n");
p.send_config();
printf("Sent config packet\n");
});
p.signal_keep_alive.connect([](KeepAlive s) {
printf(">> KeepAlive: phyverso MCU SW Version: %s, Hardware %i/rev %i, MCU Timestamp %i\n",
s.sw_version_string, s.hw_type, s.hw_revision, s.time_stamp);
sw_version_received = true;
});
p.signal_set_coil_state_response.connect([](int connector, CoilState s) {
if (s.coil_state)
printf(">> Connector %i, Coil %d: Relais CLOSED\n", connector, s.coil_type);
else
printf(">> Connector %i, Coil %d: Relais OPEN\n", connector, s.coil_type);
});
p.signal_telemetry.connect([](int connector, Telemetry t) {
printf(">> Connector %i: CP Voltage %i %i\n", connector, t.cp_voltage_hi, t.cp_voltage_lo);
});
p.signal_cp_state.connect([](int connector, CpState s) {
switch (s) {
case CpState_STATE_A:
printf(">> Connector %i: CP state A\n", connector);
break;
case CpState_STATE_B:
printf(">> Connector %i: CP state B\n", connector);
break;
case CpState_STATE_C:
printf(">> Connector %i: CP state C\n", connector);
break;
case CpState_STATE_D:
printf(">> Connector %i: CP state D\n", connector);
break;
case CpState_STATE_E:
printf(">> Connector %i: CP state E\n", connector);
break;
case CpState_STATE_F:
printf(">> Connector %i: CP state F\n", connector);
break;
}
});
p.signal_pp_state.connect([](int connector, PpState s) {
switch (s) {
case PpState_STATE_NC:
printf(">> Connector %i: PP state NC\n", connector);
break;
case PpState_STATE_13A:
printf(">> Connector %i: PP state 13A\n", connector);
break;
case PpState_STATE_20A:
printf(">> Connector %i: PP state 20A\n", connector);
break;
case PpState_STATE_32A:
printf(">> Connector %i: PP state 32A\n", connector);
break;
case PpState_STATE_70A:
printf(">> Connector %i: PP state 70A\n", connector);
break;
case PpState_STATE_FAULT:
printf(">> Connector %i: PP state FAULT\n", connector);
break;
}
});
p.signal_fan_state.connect([](FanState s) {
printf(">> Fan %i: EN=%s, Duty=%d RPM=%d\n", s.fan_id, (s.enabled ? "ON" : "OFF"), s.duty, s.rpm);
});
p.signal_lock_state.connect([](int connector, LockState s) {
switch (s) {
case LockState_UNDEFINED:
printf(">> Connector %i: Lock State UNDEFINED\n", connector);
break;
case LockState_LOCKED:
printf(">> Connector %i: Lock State Locked\n", connector);
break;
case LockState_UNLOCKED:
printf(">> Connector %i: Lock State Unlocked\n", connector);
break;
case LockState_LOCKING:
printf(">> Connector %i: Lock State Locking\n", connector);
break;
case LockState_UNLOCKING:
printf(">> Connector %i: Lock State Unlocking\n", connector);
break;
}
});
p.signal_error_flags.connect([](int connector, ErrorFlags error_flags) {
printf("------------\nError flags Connector %d:\n", connector);
printf("\tdiode_fault: %d\n", error_flags.diode_fault);
printf("\trcd_selftest_failed: %d\n", error_flags.rcd_selftest_failed);
printf("\trcd_triggered: %d\n", error_flags.rcd_triggered);
printf("\tventilation_not_available: %d\n", error_flags.ventilation_not_available);
printf("\tconnector_lock_failed: %d\n", error_flags.connector_lock_failed);
printf("\tcp_signal_fault: %d\n", error_flags.cp_signal_fault);
printf("\theartbeat_timeout: %d\n", error_flags.heartbeat_timeout);
printf("\tcoil_feedback_diverges_ac: %d\n", error_flags.coil_feedback_diverges);
printf("\tpp_signal_fault: %d\n", error_flags.pp_signal_fault);
printf("------------\n");
});
while (true) {
char c = getc(stdin);
switch (c) {
/* AC coils*/
case 'A':
printf("Setting AC coil to ON\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_AC, true);
break;
case 'a':
printf("Setting AC coil to OFF\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_AC, false);
break;
/* DC coils */
case 'D':
printf("Setting monitored DC coil to ON\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_DC1, true);
break;
case 'd':
printf("Setting monitored DC coil to OFF\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_DC1, false);
break;
case 'O':
printf("Setting AUX1 DC coil to ON\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_DC2, true);
break;
case 'o':
printf("Setting AUX1 DC coil to OFF\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_DC2, false);
break;
case 'P':
printf("Setting AUX2 DC coil to ON\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_DC3, true);
break;
case 'p':
printf("Setting AUX2 DC coil to OFF\n");
p.set_coil_state_request(selected_connector, CoilType_COIL_DC3, false);
break;
/* Motor lock */
case 'L':
printf("Locking connector\n");
p.lock(selected_connector, true);
break;
case 'l':
printf("Unlocking connector\n");
p.lock(selected_connector, false);
break;
/* Resets */
case 'r':
printf("Soft reset\n");
p.reset(-1);
break;
case 'R':
printf("Hard reset\n");
p.reset(1);
break;
/* Versions/timestamp */
case 'V':
printf("Sending keep alive\n");
p.keep_alive();
break;
/* Charging connector selection */
case '1':
printf("Connector 1 selected.\n");
selected_connector = 1;
break;
case '2':
printf("Connector 2 selected.\n");
selected_connector = 2;
break;
/* CP PWM setting */
case '0':
printf("Set 0%% PWM\n");
p.set_pwm(selected_connector, 0);
break;
case '5':
printf("Set 5%% PWM\n");
p.set_pwm(selected_connector, 500);
break;
case '6':
printf("Set 10%% PWM\n");
p.set_pwm(selected_connector, 1000);
break;
case '7':
printf("Set 80%% PWM\n");
p.set_pwm(selected_connector, 8000);
break;
case '8':
printf("Set 97%% PWM\n");
p.set_pwm(selected_connector, 9700);
break;
case '9':
printf("Set 100%% PWM\n");
p.set_pwm(selected_connector, 10000);
break;
/* Fans */
case 'U':
printf("Set fan1 to 50%%\n");
p.set_fan_state(0, true, 500);
break;
case 'u':
printf("Set fan1 to OFF\n");
p.set_fan_state(0, false, 500); // example for setting fan off via enable param
// check if your fan supports full OFF on PWM 0% duty (also check PWM on oscilloscope)
// some PWM fans wont turn fully off without switching 12V supply
break;
case 'I':
printf("Set fan2 to ON\n");
p.set_fan_state(1, true, 1000);
break;
case 'i':
printf("Set fan2 to 20%%\n");
p.set_fan_state(1, true, 200);
break;
/* RCD */
case 'K':
printf("Sending RCD Test on connector %d\n", selected_connector);
p.set_rcd_test(selected_connector, true);
break;
case 'k':
printf("Resetting RCD on connector %d\n", selected_connector);
p.reset_rcd(selected_connector, true);
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
return 0;
}

View File

@@ -0,0 +1,19 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "phyverso_gpio",
deps = [
"//modules/HardwareDrivers/EVSE/PhyVersoBSP/phyverso_mcu_comms",
"//lib/everest/gpio",
"@sigslot//:sigslot",
],
srcs = glob([
"**/*.h",
"**/*.cpp",
]),
visibility = ["//visibility:public"],
includes = [
".",
],
copts = ["-std=c++17"],
)

View File

@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.10)
# set the project name
project(phyverso_gpio VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_library(phyverso_gpio STATIC)
ev_register_library_target(phyverso_gpio)
target_sources(phyverso_gpio
PRIVATE
evGpio.cpp
)
target_include_directories(phyverso_gpio
PUBLIC
"${PROJECT_BINARY_DIR}"
"../phyverso_mcu_comms"
)
target_link_libraries(phyverso_gpio
PUBLIC
date::date-tz
everest::nanopb
PRIVATE
Pal::Sigslot
everest::framework
everest::gpio
phyverso_config
fmt::fmt
)

View File

@@ -0,0 +1,89 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "evGpio.h"
#include <everest/logging.hpp>
#include <fmt/format.h>
evGpio::evGpio(evConfig& _verso_config) : verso_config(_verso_config) {
}
evGpio::~evGpio() {
// TODO: deinit gpios?
}
bool evGpio::init_gpios() {
push_buttons[CONN1_PB_STOP].set_enabled(verso_config.conf.conn1_gpio_stop_button_enabled);
push_buttons[CONN2_PB_STOP].set_enabled(verso_config.conf.conn2_gpio_stop_button_enabled);
if (push_buttons[CONN1_PB_STOP].get_enabled()) {
Everest::GpioSettings settings;
settings.chip_name = verso_config.conf.conn1_gpio_stop_button_bank;
settings.line_number = verso_config.conf.conn1_gpio_stop_button_pin;
settings.inverted = verso_config.conf.conn1_gpio_stop_button_invert;
push_buttons[CONN1_PB_STOP].init_gpio(settings);
if (not push_buttons[CONN1_PB_STOP].ready()) {
EVLOG_error << "Could not initialize Connector 1 push button";
return false;
}
}
if (push_buttons[CONN2_PB_STOP].get_enabled()) {
Everest::GpioSettings settings;
settings.chip_name = verso_config.conf.conn2_gpio_stop_button_bank;
settings.line_number = verso_config.conf.conn2_gpio_stop_button_pin;
settings.inverted = verso_config.conf.conn2_gpio_stop_button_invert;
push_buttons[CONN2_PB_STOP].init_gpio(settings);
if (not push_buttons[CONN2_PB_STOP].ready()) {
EVLOG_error << "Could not initialize Connector 2 push button";
return false;
}
}
return true;
}
void evGpio::run() {
poll_thread_handle = std::thread(&evGpio::poll_thread, this);
}
void evGpio::poll_thread() {
while (true) {
if (poll_thread_handle.shouldExit())
break;
// iterate over button list
for (int i = 0; i < NUM_PB_NAMES; i++) {
PushButton& button = push_buttons[i];
if (not button.get_enabled())
continue;
// check if GPIO is still usable
if (not button.ready()) {
EVLOG_error << fmt::format("Push button {} not ready. Stopping thread.", i + 1);
goto cleanup; // break out of polling loop immediatly and terminate thread
}
button.read();
if (button.get_state_changed()) {
// select which signal to send depending on button name
switch (i) {
case CONN1_PB_STOP:
signal_stop_button_state(1, button.get_state());
break;
case CONN2_PB_STOP:
signal_stop_button_state(2, button.get_state());
break;
default:
break;
}
}
}
// sleep the nominal polling interval time
std::this_thread::sleep_for(std::chrono::milliseconds(poll_time_ms));
}
cleanup : {}
}

View File

@@ -0,0 +1,103 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef PHYVERSO_GPIO_EV_GPIO_H
#define PHYVERSO_GPIO_EV_GPIO_H
#include "evConfig.h"
#include <atomic>
#include <chrono>
#include <everest/gpio/gpio.hpp>
#include <sigslot/signal.hpp>
#include <stdexcept>
#include <stdint.h>
#include <utility>
#include <utils/thread.hpp>
class evGpio {
public:
evGpio(evConfig& _verso_config);
~evGpio();
void poll_thread();
void run();
bool init_gpios();
// Read thread for serial port
Everest::Thread poll_thread_handle;
// Signals to communicate state changes to other modules
sigslot::signal<int, bool> signal_stop_button_state;
// List of used buttons/gpios
enum PushButtonName {
CONN1_PB_STOP,
CONN2_PB_STOP,
NUM_PB_NAMES,
};
private:
static constexpr uint32_t poll_time_ms = 10; //< time in ms between polled gpio input readings/debouncing
// structure to encapsulate Everest::Gpio and corresponding debounce shift register
// for now we will use 16 consecutive 0's or 1's to correspond to a stable input level (could be 8/32/64 aswell,
// just needs adjusting in bitmasks and bitwidths)
struct PushButton {
void init_gpio(const Everest::GpioSettings& settings) {
gpio.open(settings);
gpio.set_input();
};
bool ready() {
return gpio.is_ready();
};
void read() {
debounce_shift_reg = (debounce_shift_reg << 1) | gpio.read();
switch (debounce_shift_reg) {
case 0xFFFF:
state = true;
break;
case 0x0000:
state = false;
break;
default:
break;
}
state_changed = (last_state != state);
last_state = state;
};
bool get_state() {
return state;
};
bool get_state_changed() {
return state_changed;
};
void set_enabled(bool _enabled) {
enabled = _enabled;
};
bool get_enabled() {
return enabled;
};
private:
Everest::Gpio gpio;
bool state, last_state, state_changed = false;
bool enabled = false;
uint16_t debounce_shift_reg = 0;
};
PushButton push_buttons[NUM_PB_NAMES];
// config bridge (filled by json or everest module config)
evConfig& verso_config;
};
#endif // PHYVERSO_GPIO_EV_GPIO_H

View File

@@ -0,0 +1,48 @@
load("@rules_cc//cc:defs.bzl", "cc_library")
cc_library(
name = "phyverso_config",
deps = [
"//lib/3rd_party/nanopb",
"//lib/everest/framework:framework",
],
srcs = glob([
"protobuf/*.h",
"protobuf/*.c",
]) + [
"evConfig.h",
"evConfig.cpp"
],
visibility = ["//visibility:public"],
includes = [
".",
"protobuf",
],
)
cc_library(
name = "phyverso_mcu_comms",
deps = [
":phyverso_config",
"//lib/3rd_party/nanopb",
"@com_github_HowardHinnant_date//:date",
"//lib/everest/framework:framework",
"@sigslot//:sigslot",
],
srcs = glob([
"**/*.h",
"**/*.c",
"**/*.cpp",
],
exclude = [
"evConfig.h",
"evConfig.cpp",
]),
visibility = ["//visibility:public"],
includes = [
".",
"protobuf",
"bsl",
],
copts = ["-std=c++17"],
)

View File

@@ -0,0 +1,55 @@
cmake_minimum_required(VERSION 3.10)
# set the project name
project(phyverso_mcu_comms VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
add_library(phyverso_config STATIC)
ev_register_library_target(phyverso_config)
target_sources(phyverso_config
PRIVATE
evConfig.cpp
)
target_include_directories(phyverso_config
PUBLIC
"${PROJECT_BINARY_DIR}"
protobuf
)
target_link_libraries(phyverso_config
PRIVATE
everest::nanopb
nlohmann_json::nlohmann_json
fmt::fmt
)
add_library(phyverso_mcu_comms STATIC)
ev_register_library_target(phyverso_mcu_comms)
target_sources(phyverso_mcu_comms
PRIVATE
evSerial.cpp
protobuf/phyverso.pb.c
bsl/bsl_gpio.cpp
)
target_include_directories(phyverso_mcu_comms
PUBLIC
"${PROJECT_BINARY_DIR}"
protobuf
bsl
)
target_link_libraries(phyverso_mcu_comms
PUBLIC
date::date-tz
everest::nanopb
PRIVATE
Pal::Sigslot
everest::framework
phyverso_config
)

View File

@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "bsl_gpio.h"
#include <chrono>
#include <iostream>
#include <thread>
BSL_GPIO::BSL_GPIO(_gpio_def _bsl, _gpio_def _reset) : bsl_out(_bsl), reset_out(_reset) {
}
bool BSL_GPIO::hard_reset(uint16_t ms_reset_time) {
bool status = set_pin(reset_out, true);
if (!status) {
printf("Could set reset active\n");
return status;
}
std::this_thread::sleep_for(std::chrono::milliseconds(ms_reset_time));
status = set_pin(reset_out, false);
if (!status) {
printf("Could set reset inactive\n");
return status;
}
return status;
}
bool BSL_GPIO::enter_bsl() {
bool status = set_pin(bsl_out, true);
if (!status) {
printf("Could not set BSL pin high\n");
return status;
}
std::this_thread::sleep_for(std::chrono::milliseconds(ms_bsl_out_settle));
status = hard_reset();
if (!status) {
printf("Could not reset\n");
return status;
}
std::this_thread::sleep_for(std::chrono::milliseconds(ms_bsl_out_settle));
status = set_pin(bsl_out, false);
if (!status) {
printf("Could not set BSL pin low\n");
return status;
}
return status;
}
bool BSL_GPIO::set_pin(_gpio_def gpio, bool level) {
char* cmd;
int size = asprintf(&cmd, "gpioset %d %d=%d", gpio.bank, gpio.pin, (level ? 1 : 0));
if ((size == -1) || (!cmd)) {
return false;
}
// printf("%s\n", cmd); // debug
int status = system(cmd);
free(cmd);
if (status == 0) {
return true;
} else {
return false;
}
}

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef PHYVERSO_MCU_COMMS_BSL_BSL_GPIO_H
#define PHYVERSO_MCU_COMMS_BSL_BSL_GPIO_H
#include <cstdint>
class BSL_GPIO {
public:
struct _gpio_def {
uint8_t bank;
uint8_t pin;
};
BSL_GPIO(_gpio_def _bsl = {.bank = 1, .pin = 12}, _gpio_def _reset = {.bank = 1, .pin = 23});
bool hard_reset(uint16_t ms_reset_time = default_ms_reset_time);
bool enter_bsl();
private:
bool set_pin(_gpio_def gpio, bool level);
static constexpr uint16_t default_ms_reset_time = 10;
static constexpr uint16_t ms_bsl_out_settle = 10;
// should be changeable later by loading a conf file
_gpio_def bsl_out;
_gpio_def reset_out;
};
#endif // PHYVERSO_MCU_COMMS_BSL_BSL_GPIO_H

View File

@@ -0,0 +1,108 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "evConfig.h"
#include "phyverso.pb.h"
#include <fmt/core.h>
#include <fstream>
#include <iostream>
// for convenience
using json = nlohmann::json;
evConfig::evConfig() {
}
evConfig::~evConfig() {
}
bool evConfig::open_file(std::string path) {
try {
std::ifstream f(path);
config_file = json::parse(f);
return true;
} catch (const std::exception& e) {
std::cerr << "error: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Exception of unknown type!" << std::endl;
}
return false;
}
// unused for now
bool evConfig::read_hw_eeprom(ConfigHardwareRevision& hw_rev) {
// TODO: read eeprom on new phyVERSO hw revisions,
// for now return hardcoded value
hw_rev = ConfigHardwareRevision_HW_REV_A;
return true;
}
void evConfig::fill_config_packet() {
config_packet.which_payload = EverestToMcu_config_response_tag;
config_packet.connector = 0;
read_hw_eeprom(config_packet.payload.config_response.hw_rev);
/* fill port 1 config */
{
auto& chargeport_config = config_packet.payload.config_response.chargeport_config[0];
chargeport_config.has_lock = true;
chargeport_config.lock.type = static_cast<MotorLockType>(conf.conn1_motor_lock_type);
chargeport_config.feedback_active_low = conf.conn1_feedback_active_low;
chargeport_config.feedback_pull = static_cast<GpioPull>(conf.conn1_feedback_pull);
chargeport_config.has_socket = conf.conn1_has_socket;
if (conf.conn1_disable_port) {
chargeport_config.type = ChargePortType_DISABLED;
} else if (conf.conn1_dc) {
chargeport_config.type = ChargePortType_DC;
} else {
chargeport_config.type = ChargePortType_AC;
}
}
/* fill port 2 config */
{
auto& chargeport_config = config_packet.payload.config_response.chargeport_config[1];
chargeport_config.has_lock = true;
chargeport_config.lock.type = static_cast<MotorLockType>(conf.conn2_motor_lock_type);
chargeport_config.feedback_active_low = conf.conn2_feedback_active_low;
chargeport_config.feedback_pull = static_cast<GpioPull>(conf.conn2_feedback_pull);
chargeport_config.has_socket = conf.conn2_has_socket;
if (conf.conn2_disable_port) {
chargeport_config.type = ChargePortType_DISABLED;
} else if (conf.conn2_dc) {
chargeport_config.type = ChargePortType_DC;
} else {
chargeport_config.type = ChargePortType_AC;
}
}
}
EverestToMcu evConfig::get_config_packet() {
fill_config_packet();
return config_packet;
}
// keep in mind, json config is only used for testing via phyverso_cli
void evConfig::json_conf_to_evConfig() {
// try and get value from json file or keep default values as is
conf.conn1_motor_lock_type = config_file.value("conn1_motor_lock_type", conf.conn1_motor_lock_type);
conf.conn2_motor_lock_type = config_file.value("conn2_motor_lock_type", conf.conn2_motor_lock_type);
conf.reset_gpio_bank = config_file.value("reset_gpio_bank", conf.reset_gpio_bank);
conf.reset_gpio_pin = config_file.value("reset_gpio_pin", conf.reset_gpio_pin);
conf.conn1_disable_port = config_file.value("conn1_disable_port", conf.conn1_disable_port);
conf.conn2_disable_port = config_file.value("conn2_disable_port", conf.conn2_disable_port);
conf.conn1_feedback_active_low = config_file.value("conn1_feedback_active_low", conf.conn1_feedback_active_low);
conf.conn2_feedback_active_low = config_file.value("conn2_feedback_active_low", conf.conn2_feedback_active_low);
conf.conn1_feedback_pull = config_file.value("conn1_feedback_pull", conf.conn1_feedback_pull);
conf.conn2_feedback_pull = config_file.value("conn2_feedback_pull", conf.conn2_feedback_pull);
conf.conn1_dc = config_file.value("conn1_dc", conf.conn1_dc);
conf.conn2_dc = config_file.value("conn2_dc", conf.conn2_dc);
conf.conn1_disable_port = config_file.value("conn1_disable_port", conf.conn1_disable_port);
conf.conn2_disable_port = config_file.value("conn2_disable_port", conf.conn2_disable_port);
conf.conn1_has_socket = config_file.value("conn1_has_socket", conf.conn1_has_socket);
conf.conn2_has_socket = config_file.value("conn2_has_socket", conf.conn2_has_socket);
}

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef PHYVERSO_CONFIG_EV_CONFIG_HPP
#define PHYVERSO_CONFIG_EV_CONFIG_HPP
#include "phyverso.pb.h"
#include <nlohmann/json.hpp>
#include <string>
using json = nlohmann::json;
class evConfig {
public:
// same structure as in PhyVersoBSP, gets filled either by json parsing config file for phyverso_cli
// or gets overwritten with PhyVersoBSP everest module config
// changes in manifest have to be fixed here manually
struct Conf {
std::string serial_port = "/dev/ttyUSB0";
int baud_rate = 115200;
int reset_gpio = -1;
int conn1_max_current_A_import = 16;
int conn1_min_current_A_import = 6;
int conn1_min_phase_count_import = 3;
int conn1_max_phase_count_import = 3;
int conn1_min_current_A_export = 0;
int conn1_max_current_A_export = 0;
int conn1_min_phase_count_export = 3;
int conn1_max_phase_count_export = 3;
bool conn1_has_socket = false;
bool conn1_dc = false;
int conn2_max_current_A_import = 16;
int conn2_min_current_A_import = 6;
int conn2_min_phase_count_import = 3;
int conn2_max_phase_count_import = 3;
int conn2_min_current_A_export = 0;
int conn2_max_current_A_export = 0;
int conn2_min_phase_count_export = 3;
int conn2_max_phase_count_export = 3;
bool conn2_has_socket = false;
bool conn2_dc = false;
int reset_gpio_bank = 1;
int reset_gpio_pin = 23;
int conn1_motor_lock_type = -1;
int conn2_motor_lock_type = -1;
bool conn1_gpio_stop_button_enabled = false;
std::string conn1_gpio_stop_button_bank = "gpiochip1";
int conn1_gpio_stop_button_pin = 36;
bool conn1_gpio_stop_button_invert = false;
bool conn2_gpio_stop_button_enabled = false;
std::string conn2_gpio_stop_button_bank = "gpiochip1";
int conn2_gpio_stop_button_pin = 37;
bool conn2_gpio_stop_button_invert = false;
bool conn1_disable_port = false;
bool conn2_disable_port = false;
bool conn1_feedback_active_low = true;
bool conn2_feedback_active_low = true;
int conn1_feedback_pull = 2;
int conn2_feedback_pull = 2;
} conf;
evConfig();
~evConfig();
bool open_file(std::string path);
EverestToMcu get_config_packet();
void json_conf_to_evConfig();
private:
bool check_validity();
bool read_hw_eeprom(ConfigHardwareRevision& hw_rev);
void fill_config_packet();
json config_file;
EverestToMcu config_packet = EverestToMcu_init_default;
};
#endif // PHYVERSO_CONFIG_EV_CONFIG_HPP

View File

@@ -0,0 +1,450 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 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/logging.hpp>
#include "phyverso.pb.h"
#include "bsl_gpio.h"
evSerial::evSerial(evConfig& _verso_config) :
fd(0), baud(0), reset_done_flag(false), forced_reset(false), verso_config(_verso_config) {
cobs_decode_reset();
}
evSerial::~evSerial() {
if (fd) {
close(fd);
}
}
bool evSerial::open_device(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);
cobs_decode_reset();
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 set_serial_attributes();
}
void evSerial::flush_buffers() {
tcflush(fd, TCIOFLUSH);
}
bool evSerial::set_serial_attributes() {
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;
}
return true;
}
void evSerial::cobs_decode_reset() {
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;
}
return crc;
}
void evSerial::handle_packet(uint8_t* buf, int len) {
if (crc32(buf, len)) {
printf("CRC mismatch\n");
return;
}
len -= 4;
if (handle_McuToEverest_packet(buf, len))
return;
else
printf("Cannot handle a packet");
}
bool evSerial::handle_McuToEverest_packet(uint8_t* buf, int len) {
McuToEverest msg_in;
pb_istream_t istream = pb_istream_from_buffer(buf, len);
if (!pb_decode(&istream, McuToEverest_fields, &msg_in))
return false;
switch (msg_in.which_payload) {
case McuToEverest_keep_alive_tag:
signal_keep_alive(msg_in.payload.keep_alive);
last_keep_alive_lo_timestamp = date::utc_clock::now();
break;
case McuToEverest_cp_state_tag:
signal_cp_state(msg_in.connector, msg_in.payload.cp_state);
break;
case McuToEverest_set_coil_state_response_tag:
signal_set_coil_state_response(msg_in.connector, msg_in.payload.set_coil_state_response);
break;
case McuToEverest_error_flags_tag:
signal_error_flags(msg_in.connector, msg_in.payload.error_flags);
break;
case McuToEverest_telemetry_tag:
signal_telemetry(msg_in.connector, msg_in.payload.telemetry);
break;
case McuToEverest_reset_tag:
reset_done_flag = true;
if (!forced_reset)
signal_spurious_reset(msg_in.payload.reset);
break;
case McuToEverest_pp_state_tag:
signal_pp_state(msg_in.connector, msg_in.payload.pp_state);
break;
case McuToEverest_fan_state_tag:
signal_fan_state(msg_in.payload.fan_state);
break;
case McuToEverest_lock_state_tag:
signal_lock_state(msg_in.connector, msg_in.payload.lock_state);
break;
case McuToEverest_config_request_tag:
signal_config_request();
break;
}
return true;
}
void evSerial::cobs_decode(uint8_t* buf, int len) {
for (int i = 0; i < len; i++)
cobs_decode_byte(buf[i]);
}
void evSerial::cobs_decode_byte(uint8_t byte) {
// check max length
if ((decode - msg == 2048 - 1) && byte != 0x00) {
printf("cobsDecode: Buffer overflow\n");
cobs_decode_reset();
}
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");
cobs_decode_reset();
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
handle_packet(msg, decode - 1 - msg);
}
cobs_decode_reset();
return; // need to return here, because of block--
}
}
block--;
}
void evSerial::run() {
read_thread_handle = std::thread(&evSerial::read_thread, this);
timeout_detection_thread_handle = std::thread(&evSerial::timeout_detection_thread, this);
last_keep_alive_lo_timestamp = date::utc_clock::now();
}
void evSerial::timeout_detection_thread() {
while (true) {
sleep(1);
if (timeout_detection_thread_handle.shouldExit())
break;
if (serial_timed_out())
signal_connection_timeout();
// send keep alive
keep_alive();
}
}
void evSerial::read_thread() {
uint8_t buf[2048];
int n;
cobs_decode_reset();
while (true) {
if (read_thread_handle.shouldExit())
break;
if (fd > 0) {
n = read(fd, buf, sizeof buf);
cobs_decode(buf, n);
}
}
}
bool evSerial::link_write(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 = cobs_encode(tx_packet_buf, tx_payload_len, encode_buf);
write(fd, encode_buf, tx_encode_len);
return true;
}
size_t evSerial::cobs_encode(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 time_since_last_keep_alive =
std::chrono::duration_cast<std::chrono::milliseconds>(now - last_keep_alive_lo_timestamp).count();
if (time_since_last_keep_alive >= 5000)
return true;
return false;
}
void evSerial::set_pwm(int target_connector, uint32_t duty_cycle_e2) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_pwm_duty_cycle_tag;
msg_out.payload.pwm_duty_cycle = duty_cycle_e2;
msg_out.connector = target_connector;
link_write(&msg_out);
}
void evSerial::set_coil_state_request(int target_connector, CoilType type, bool power_on) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_set_coil_state_request_tag;
msg_out.payload.set_coil_state_request.coil_type = type;
msg_out.payload.set_coil_state_request.coil_state = power_on;
msg_out.connector = target_connector;
link_write(&msg_out);
}
void evSerial::lock(int target_connector, bool _lock) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_connector_lock_tag;
msg_out.payload.connector_lock = _lock;
msg_out.connector = target_connector;
link_write(&msg_out);
}
void evSerial::firmware_update() {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_firmware_update_tag;
msg_out.connector = 0;
link_write(&msg_out);
}
bool evSerial::reset(const int reset_pin) {
reset_done_flag = false;
forced_reset = true;
if (reset_pin > 0) {
EVLOG_info << "Hard-resetting PhyVerso";
auto bsl_gpio = BSL_GPIO({.bank = 1, .pin = 12}, // BSL pins are unused here so keep defaults
{.bank = static_cast<uint8_t>(verso_config.conf.reset_gpio_bank),
.pin = static_cast<uint8_t>(verso_config.conf.reset_gpio_pin)});
bsl_gpio.hard_reset(25);
} else {
// Try to soft reset phyVERSO controller to be in a known state
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_reset_tag;
msg_out.connector = 0;
link_write(&msg_out);
}
bool success = true;
// send some dummy packets to resync COBS etc.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
cobs_decode_reset();
keep_alive();
keep_alive();
keep_alive();
return success;
}
void evSerial::keep_alive() {
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");
msg_out.connector = 0;
link_write(&msg_out);
}
void evSerial::send_config() {
EverestToMcu config_packet = verso_config.get_config_packet();
link_write(&config_packet);
}
void evSerial::set_fan_state(uint8_t fan_id, bool enabled, uint32_t duty) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_set_fan_state_tag;
msg_out.payload.set_fan_state.fan_id = fan_id;
msg_out.payload.set_fan_state.enabled = enabled;
msg_out.payload.set_fan_state.duty = duty;
msg_out.connector = 0;
link_write(&msg_out);
}
void evSerial::set_rcd_test(int target_connector, bool _test) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_rcd_cmd_tag;
msg_out.payload.rcd_cmd.test = _test;
msg_out.payload.rcd_cmd.reset = false;
msg_out.connector = target_connector;
link_write(&msg_out);
}
void evSerial::reset_rcd(int target_connector, bool _reset) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_rcd_cmd_tag;
msg_out.payload.rcd_cmd.test = false;
msg_out.payload.rcd_cmd.reset = _reset;
msg_out.connector = target_connector;
link_write(&msg_out);
}

View File

@@ -0,0 +1,97 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef PHYVERSO_MCU_COMMS_EV_SERIAL_H
#define PHYVERSO_MCU_COMMS_EV_SERIAL_H
#include "evConfig.h"
#include "phyverso.pb.h"
#include <atomic>
#include <chrono>
#include <date/date.h>
#include <date/tz.h>
#include <sigslot/signal.hpp>
#include <stdexcept>
#include <stdint.h>
#include <termios.h>
#include <utility>
#include <utils/thread.hpp>
#include <vector>
class evSerial {
public:
evSerial(evConfig& _verso_config);
~evSerial();
bool open_device(const char* device, int baud);
bool is_open() {
return fd > 0;
};
void flush_buffers();
void read_thread();
void run();
bool reset(const int reset_pin);
void firmware_update();
void keep_alive();
void set_pwm(int target_connector, uint32_t duty_cycle_e2);
void set_coil_state_request(int target_connector, CoilType type, bool power_on);
void lock(int target_connector, bool _lock);
void unlock(int target_connector);
void set_fan_state(uint8_t fan_id, bool enabled, uint32_t duty);
void set_rcd_test(int target_connector, bool _test);
void reset_rcd(int target_connector, bool _reset);
void send_config();
sigslot::signal<KeepAlive> signal_keep_alive;
sigslot::signal<int, CpState> signal_cp_state;
sigslot::signal<int, CoilState> signal_set_coil_state_response;
sigslot::signal<int, ErrorFlags> signal_error_flags;
sigslot::signal<int, Telemetry> signal_telemetry;
sigslot::signal<ResetReason> signal_spurious_reset;
sigslot::signal<> signal_connection_timeout;
sigslot::signal<int, PpState> signal_pp_state;
sigslot::signal<FanState> signal_fan_state;
sigslot::signal<int, LockState> signal_lock_state;
sigslot::signal<> signal_config_request;
private:
// Serial interface
bool set_serial_attributes();
int fd;
int baud;
// COBS de-/encoder
void cobs_decode_reset();
void handle_packet(uint8_t* buf, int len);
bool handle_McuToEverest_packet(uint8_t* buf, int len);
void cobs_decode(uint8_t* buf, int len);
void cobs_decode_byte(uint8_t byte);
size_t cobs_encode(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 read_thread_handle;
Everest::Thread timeout_detection_thread_handle;
bool link_write(EverestToMcu* m);
std::atomic_bool reset_done_flag;
std::atomic_bool forced_reset;
bool serial_timed_out();
void timeout_detection_thread();
std::chrono::time_point<date::utc_clock> last_keep_alive_lo_timestamp;
// config bridge (filled by json or everest module config)
evConfig& verso_config;
};
#endif // PHYVERSO_MCU_COMMS_EV_SERIAL_H

View File

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

View File

@@ -0,0 +1,5 @@
KeepAlive.sw_version_string max_length:50
FanState.fan_id int_size:IS_8
FanState.rpm int_size:IS_16
BootConfigResponse.chargeport_config max_count:2
BootConfigResponse.chargeport_config fixed_count:true

View File

@@ -0,0 +1,54 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.8 */
#include "phyverso.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(EverestToMcu, EverestToMcu, AUTO)
PB_BIND(McuToEverest, McuToEverest, AUTO)
PB_BIND(ErrorFlags, ErrorFlags, AUTO)
PB_BIND(KeepAlive, KeepAlive, AUTO)
PB_BIND(Telemetry, Telemetry, AUTO)
PB_BIND(FanState, FanState, AUTO)
PB_BIND(CoilState, CoilState, AUTO)
PB_BIND(BootConfigRequest, BootConfigRequest, AUTO)
PB_BIND(BootConfigResponse, BootConfigResponse, AUTO)
PB_BIND(ChargePortConfig, ChargePortConfig, AUTO)
PB_BIND(ConfigMotorLockType, ConfigMotorLockType, AUTO)
PB_BIND(RcdCommand, RcdCommand, AUTO)

View File

@@ -0,0 +1,487 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.8 */
#ifndef PB_PHYVERSO_PB_H_INCLUDED
#define PB_PHYVERSO_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_LOCKING = 3,
LockState_UNLOCKING = 4
} LockState;
typedef enum _CoilType {
CoilType_COIL_UNKNOWN = 0,
CoilType_COIL_AC = 1,
CoilType_COIL_DC1 = 2,
/* add precharge and discharge coils here later */
CoilType_COIL_DC2 = 3,
CoilType_COIL_DC3 = 4
} CoilType;
typedef enum _ChargePortType {
ChargePortType_DISABLED = 0,
ChargePortType_AC = 1,
ChargePortType_DC = 2
} ChargePortType;
typedef enum _GpioPull {
GpioPull_NONE = 0,
GpioPull_UP = 1,
GpioPull_DOWN = 2
} GpioPull;
typedef enum _ConfigHardwareRevision {
ConfigHardwareRevision_HW_REV_UNKNOWN = 0,
ConfigHardwareRevision_HW_REV_A = 1,
ConfigHardwareRevision_HW_REV_B = 2
} ConfigHardwareRevision;
typedef enum _MotorLockType {
MotorLockType_MOTOR_LOCK_UNKNOWN = 0,
MotorLockType_MOTOR_LOCK_HELLA = 1,
MotorLockType_MOTOR_LOCK_DEBUG_VALEO_HVAC = 2,
/* add additional locks here */
MotorLockType_MOTOR_LOCK_NONE = -1
} MotorLockType;
/* 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 heartbeat_timeout;
bool coil_feedback_diverges;
bool pp_signal_fault;
} ErrorFlags;
typedef struct _KeepAlive {
uint32_t time_stamp;
uint32_t hw_type;
uint32_t hw_revision;
char sw_version_string[51];
bool configuration_done;
} KeepAlive;
typedef struct _Telemetry {
uint32_t cp_voltage_hi;
uint32_t cp_voltage_lo;
} Telemetry;
typedef struct _FanState {
uint8_t fan_id;
bool enabled;
uint32_t duty; /* in 0.1%, 1000 = 100% */
uint16_t rpm;
} FanState;
typedef struct _CoilState {
CoilType coil_type;
bool coil_state; /* true -> on; false -> off */
} CoilState;
typedef struct _BootConfigRequest { /* TODO */
char dummy_field;
} BootConfigRequest;
/* 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 {
KeepAlive keep_alive;
ResetReason reset;
CpState cp_state;
CoilState set_coil_state_response;
ErrorFlags error_flags;
Telemetry telemetry;
PpState pp_state;
FanState fan_state;
LockState lock_state;
BootConfigRequest config_request;
} payload;
int32_t connector; /* 0: None, 1: Connector 1, 2: Connector 2 */
} McuToEverest;
typedef struct _ConfigMotorLockType {
MotorLockType type; /* additional lock specific options could be added here later
will still keep this in place even if it only holds the type enum at the moment */
} ConfigMotorLockType;
typedef struct _ChargePortConfig {
ChargePortType type;
bool feedback_active_low;
GpioPull feedback_pull;
bool has_lock;
ConfigMotorLockType lock;
bool has_socket;
} ChargePortConfig;
typedef struct _BootConfigResponse {
ConfigHardwareRevision hw_rev;
ChargePortConfig chargeport_config[2];
} BootConfigResponse;
typedef struct _RcdCommand {
bool test; /* true -> set TEST pin high, false -> set TEST pin low */
bool reset; /* reset RCD/emergency off if set to true */
} RcdCommand;
/* 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 {
KeepAlive keep_alive;
bool firmware_update;
bool connector_lock; /* false: unlock, true: lock */
uint32_t pwm_duty_cycle; /* in 0.01 %, 0 = State F, 10000 = X1 */
CoilState set_coil_state_request;
bool reset;
BootConfigResponse config_response;
FanState set_fan_state;
RcdCommand rcd_cmd;
} payload;
int32_t connector; /* 0: None, 1: Connector 1, 2: Connector 2 */
} 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_UNLOCKING
#define _LockState_ARRAYSIZE ((LockState)(LockState_UNLOCKING+1))
#define _CoilType_MIN CoilType_COIL_UNKNOWN
#define _CoilType_MAX CoilType_COIL_DC3
#define _CoilType_ARRAYSIZE ((CoilType)(CoilType_COIL_DC3+1))
#define _ChargePortType_MIN ChargePortType_DISABLED
#define _ChargePortType_MAX ChargePortType_DC
#define _ChargePortType_ARRAYSIZE ((ChargePortType)(ChargePortType_DC+1))
#define _GpioPull_MIN GpioPull_NONE
#define _GpioPull_MAX GpioPull_DOWN
#define _GpioPull_ARRAYSIZE ((GpioPull)(GpioPull_DOWN+1))
#define _ConfigHardwareRevision_MIN ConfigHardwareRevision_HW_REV_UNKNOWN
#define _ConfigHardwareRevision_MAX ConfigHardwareRevision_HW_REV_B
#define _ConfigHardwareRevision_ARRAYSIZE ((ConfigHardwareRevision)(ConfigHardwareRevision_HW_REV_B+1))
#define _MotorLockType_MIN MotorLockType_MOTOR_LOCK_NONE
#define _MotorLockType_MAX MotorLockType_MOTOR_LOCK_DEBUG_VALEO_HVAC
#define _MotorLockType_ARRAYSIZE ((MotorLockType)(MotorLockType_MOTOR_LOCK_DEBUG_VALEO_HVAC+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
#define CoilState_coil_type_ENUMTYPE CoilType
#define BootConfigResponse_hw_rev_ENUMTYPE ConfigHardwareRevision
#define ChargePortConfig_type_ENUMTYPE ChargePortType
#define ChargePortConfig_feedback_pull_ENUMTYPE GpioPull
#define ConfigMotorLockType_type_ENUMTYPE MotorLockType
/* Initializer values for message structs */
#define EverestToMcu_init_default {0, {KeepAlive_init_default}, 0}
#define McuToEverest_init_default {0, {KeepAlive_init_default}, 0}
#define ErrorFlags_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define KeepAlive_init_default {0, 0, 0, "", 0}
#define Telemetry_init_default {0, 0}
#define FanState_init_default {0, 0, 0, 0}
#define CoilState_init_default {_CoilType_MIN, 0}
#define BootConfigRequest_init_default {0}
#define BootConfigResponse_init_default {_ConfigHardwareRevision_MIN, {ChargePortConfig_init_default, ChargePortConfig_init_default}}
#define ChargePortConfig_init_default {_ChargePortType_MIN, 0, _GpioPull_MIN, false, ConfigMotorLockType_init_default, 0}
#define ConfigMotorLockType_init_default {_MotorLockType_MIN}
#define RcdCommand_init_default {0, 0}
#define EverestToMcu_init_zero {0, {KeepAlive_init_zero}, 0}
#define McuToEverest_init_zero {0, {KeepAlive_init_zero}, 0}
#define ErrorFlags_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
#define KeepAlive_init_zero {0, 0, 0, "", 0}
#define Telemetry_init_zero {0, 0}
#define FanState_init_zero {0, 0, 0, 0}
#define CoilState_init_zero {_CoilType_MIN, 0}
#define BootConfigRequest_init_zero {0}
#define BootConfigResponse_init_zero {_ConfigHardwareRevision_MIN, {ChargePortConfig_init_zero, ChargePortConfig_init_zero}}
#define ChargePortConfig_init_zero {_ChargePortType_MIN, 0, _GpioPull_MIN, false, ConfigMotorLockType_init_zero, 0}
#define ConfigMotorLockType_init_zero {_MotorLockType_MIN}
#define RcdCommand_init_zero {0, 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_heartbeat_timeout_tag 7
#define ErrorFlags_coil_feedback_diverges_tag 8
#define ErrorFlags_pp_signal_fault_tag 9
#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 KeepAlive_configuration_done_tag 7
#define Telemetry_cp_voltage_hi_tag 1
#define Telemetry_cp_voltage_lo_tag 2
#define FanState_fan_id_tag 1
#define FanState_enabled_tag 2
#define FanState_duty_tag 3
#define FanState_rpm_tag 4
#define CoilState_coil_type_tag 1
#define CoilState_coil_state_tag 2
#define McuToEverest_keep_alive_tag 1
#define McuToEverest_reset_tag 2
#define McuToEverest_cp_state_tag 3
#define McuToEverest_set_coil_state_response_tag 4
#define McuToEverest_error_flags_tag 5
#define McuToEverest_telemetry_tag 7
#define McuToEverest_pp_state_tag 8
#define McuToEverest_fan_state_tag 9
#define McuToEverest_lock_state_tag 10
#define McuToEverest_config_request_tag 11
#define McuToEverest_connector_tag 6
#define ConfigMotorLockType_type_tag 1
#define ChargePortConfig_type_tag 1
#define ChargePortConfig_feedback_active_low_tag 2
#define ChargePortConfig_feedback_pull_tag 3
#define ChargePortConfig_lock_tag 4
#define ChargePortConfig_has_socket_tag 5
#define BootConfigResponse_hw_rev_tag 1
#define BootConfigResponse_chargeport_config_tag 6
#define RcdCommand_test_tag 1
#define RcdCommand_reset_tag 2
#define EverestToMcu_keep_alive_tag 1
#define EverestToMcu_firmware_update_tag 2
#define EverestToMcu_connector_lock_tag 3
#define EverestToMcu_pwm_duty_cycle_tag 4
#define EverestToMcu_set_coil_state_request_tag 5
#define EverestToMcu_reset_tag 6
#define EverestToMcu_config_response_tag 8
#define EverestToMcu_set_fan_state_tag 9
#define EverestToMcu_rcd_cmd_tag 10
#define EverestToMcu_connector_tag 7
/* Struct field encoding specification for nanopb */
#define EverestToMcu_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 1) \
X(a, STATIC, ONEOF, BOOL, (payload,firmware_update,payload.firmware_update), 2) \
X(a, STATIC, ONEOF, BOOL, (payload,connector_lock,payload.connector_lock), 3) \
X(a, STATIC, ONEOF, UINT32, (payload,pwm_duty_cycle,payload.pwm_duty_cycle), 4) \
X(a, STATIC, ONEOF, MESSAGE, (payload,set_coil_state_request,payload.set_coil_state_request), 5) \
X(a, STATIC, ONEOF, BOOL, (payload,reset,payload.reset), 6) \
X(a, STATIC, SINGULAR, INT32, connector, 7) \
X(a, STATIC, ONEOF, MESSAGE, (payload,config_response,payload.config_response), 8) \
X(a, STATIC, ONEOF, MESSAGE, (payload,set_fan_state,payload.set_fan_state), 9) \
X(a, STATIC, ONEOF, MESSAGE, (payload,rcd_cmd,payload.rcd_cmd), 10)
#define EverestToMcu_CALLBACK NULL
#define EverestToMcu_DEFAULT NULL
#define EverestToMcu_payload_keep_alive_MSGTYPE KeepAlive
#define EverestToMcu_payload_set_coil_state_request_MSGTYPE CoilState
#define EverestToMcu_payload_config_response_MSGTYPE BootConfigResponse
#define EverestToMcu_payload_set_fan_state_MSGTYPE FanState
#define EverestToMcu_payload_rcd_cmd_MSGTYPE RcdCommand
#define McuToEverest_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 1) \
X(a, STATIC, ONEOF, UENUM, (payload,reset,payload.reset), 2) \
X(a, STATIC, ONEOF, UENUM, (payload,cp_state,payload.cp_state), 3) \
X(a, STATIC, ONEOF, MESSAGE, (payload,set_coil_state_response,payload.set_coil_state_response), 4) \
X(a, STATIC, ONEOF, MESSAGE, (payload,error_flags,payload.error_flags), 5) \
X(a, STATIC, SINGULAR, INT32, connector, 6) \
X(a, STATIC, ONEOF, MESSAGE, (payload,telemetry,payload.telemetry), 7) \
X(a, STATIC, ONEOF, UENUM, (payload,pp_state,payload.pp_state), 8) \
X(a, STATIC, ONEOF, MESSAGE, (payload,fan_state,payload.fan_state), 9) \
X(a, STATIC, ONEOF, UENUM, (payload,lock_state,payload.lock_state), 10) \
X(a, STATIC, ONEOF, MESSAGE, (payload,config_request,payload.config_request), 11)
#define McuToEverest_CALLBACK NULL
#define McuToEverest_DEFAULT NULL
#define McuToEverest_payload_keep_alive_MSGTYPE KeepAlive
#define McuToEverest_payload_set_coil_state_response_MSGTYPE CoilState
#define McuToEverest_payload_error_flags_MSGTYPE ErrorFlags
#define McuToEverest_payload_telemetry_MSGTYPE Telemetry
#define McuToEverest_payload_fan_state_MSGTYPE FanState
#define McuToEverest_payload_config_request_MSGTYPE BootConfigRequest
#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, heartbeat_timeout, 7) \
X(a, STATIC, SINGULAR, BOOL, coil_feedback_diverges, 8) \
X(a, STATIC, SINGULAR, BOOL, pp_signal_fault, 9)
#define ErrorFlags_CALLBACK NULL
#define ErrorFlags_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) \
X(a, STATIC, SINGULAR, BOOL, configuration_done, 7)
#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 FanState_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, fan_id, 1) \
X(a, STATIC, SINGULAR, BOOL, enabled, 2) \
X(a, STATIC, SINGULAR, UINT32, duty, 3) \
X(a, STATIC, SINGULAR, UINT32, rpm, 4)
#define FanState_CALLBACK NULL
#define FanState_DEFAULT NULL
#define CoilState_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, coil_type, 1) \
X(a, STATIC, SINGULAR, BOOL, coil_state, 2)
#define CoilState_CALLBACK NULL
#define CoilState_DEFAULT NULL
#define BootConfigRequest_FIELDLIST(X, a) \
#define BootConfigRequest_CALLBACK NULL
#define BootConfigRequest_DEFAULT NULL
#define BootConfigResponse_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, hw_rev, 1) \
X(a, STATIC, FIXARRAY, MESSAGE, chargeport_config, 6)
#define BootConfigResponse_CALLBACK NULL
#define BootConfigResponse_DEFAULT NULL
#define BootConfigResponse_chargeport_config_MSGTYPE ChargePortConfig
#define ChargePortConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, type, 1) \
X(a, STATIC, SINGULAR, BOOL, feedback_active_low, 2) \
X(a, STATIC, SINGULAR, UENUM, feedback_pull, 3) \
X(a, STATIC, OPTIONAL, MESSAGE, lock, 4) \
X(a, STATIC, SINGULAR, BOOL, has_socket, 5)
#define ChargePortConfig_CALLBACK NULL
#define ChargePortConfig_DEFAULT NULL
#define ChargePortConfig_lock_MSGTYPE ConfigMotorLockType
#define ConfigMotorLockType_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, ENUM, type, 1)
#define ConfigMotorLockType_CALLBACK NULL
#define ConfigMotorLockType_DEFAULT NULL
#define RcdCommand_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, test, 1) \
X(a, STATIC, SINGULAR, BOOL, reset, 2)
#define RcdCommand_CALLBACK NULL
#define RcdCommand_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 KeepAlive_msg;
extern const pb_msgdesc_t Telemetry_msg;
extern const pb_msgdesc_t FanState_msg;
extern const pb_msgdesc_t CoilState_msg;
extern const pb_msgdesc_t BootConfigRequest_msg;
extern const pb_msgdesc_t BootConfigResponse_msg;
extern const pb_msgdesc_t ChargePortConfig_msg;
extern const pb_msgdesc_t ConfigMotorLockType_msg;
extern const pb_msgdesc_t RcdCommand_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 KeepAlive_fields &KeepAlive_msg
#define Telemetry_fields &Telemetry_msg
#define FanState_fields &FanState_msg
#define CoilState_fields &CoilState_msg
#define BootConfigRequest_fields &BootConfigRequest_msg
#define BootConfigResponse_fields &BootConfigResponse_msg
#define ChargePortConfig_fields &ChargePortConfig_msg
#define ConfigMotorLockType_fields &ConfigMotorLockType_msg
#define RcdCommand_fields &RcdCommand_msg
/* Maximum encoded size of messages (where known) */
#define BootConfigRequest_size 0
#define BootConfigResponse_size 48
#define ChargePortConfig_size 21
#define CoilState_size 4
#define ConfigMotorLockType_size 11
#define ErrorFlags_size 18
#define EverestToMcu_size 85
#define FanState_size 15
#define KeepAlive_size 72
#define McuToEverest_size 85
#define PHYVERSO_PB_H_MAX_SIZE EverestToMcu_size
#define RcdCommand_size 4
#define Telemetry_size 12
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -0,0 +1,169 @@
syntax = "proto3";
/*
This container message is send from EVerest to MCU and may contain any allowed message in that direction.
*/
message EverestToMcu {
oneof payload {
KeepAlive keep_alive = 1;
bool firmware_update = 2;
bool connector_lock = 3; // false: unlock, true: lock
uint32 pwm_duty_cycle = 4; // in 0.01 %, 0 = State F, 10000 = X1
CoilState set_coil_state_request = 5;
bool reset = 6;
BootConfigResponse config_response = 8;
FanState set_fan_state = 9;
RcdCommand rcd_cmd = 10;
}
int32 connector = 7; // 0: None, 1: Connector 1, 2: Connector 2
}
/*
This container message is send from MCU to EVerest and may contain any allowed message in that direction.
*/
message McuToEverest {
oneof payload {
KeepAlive keep_alive = 1;
ResetReason reset = 2;
CpState cp_state = 3;
CoilState set_coil_state_response = 4;
ErrorFlags error_flags = 5;
Telemetry telemetry = 7;
PpState pp_state = 8;
FanState fan_state = 9;
LockState lock_state = 10;
BootConfigRequest config_request = 11;
}
int32 connector = 6; // 0: None, 1: Connector 1, 2: Connector 2
}
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 heartbeat_timeout = 7;
bool coil_feedback_diverges = 8;
bool pp_signal_fault = 9;
}
enum ResetReason {
USER = 0;
WATCHDOG = 1;
}
message KeepAlive {
uint32 time_stamp = 1;
uint32 hw_type = 2;
uint32 hw_revision = 3;
string sw_version_string = 6;
bool configuration_done = 7;
}
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;
}
message FanState {
uint32 fan_id = 1;
bool enabled = 2;
uint32 duty = 3; // in 0.1%, 1000 = 100%
uint32 rpm = 4;
}
enum LockState {
UNDEFINED = 0;
UNLOCKED = 1;
LOCKED = 2;
LOCKING = 3;
UNLOCKING = 4;
}
message CoilState {
CoilType coil_type = 1;
bool coil_state = 2; // true -> on; false -> off
}
enum CoilType {
COIL_UNKNOWN = 0;
COIL_AC = 1;
COIL_DC1 = 2;
// add precharge and discharge coils here later
COIL_DC2 = 3;
COIL_DC3 = 4;
}
message BootConfigRequest {
// TODO
}
message BootConfigResponse {
ConfigHardwareRevision hw_rev = 1;
repeated ChargePortConfig chargeport_config = 6;
}
message ChargePortConfig {
ChargePortType type = 1;
bool feedback_active_low = 2;
GpioPull feedback_pull = 3;
ConfigMotorLockType lock = 4;
bool has_socket = 5;
}
enum ChargePortType {
DISABLED = 0;
AC = 1;
DC = 2;
}
enum GpioPull {
NONE = 0;
UP = 1;
DOWN = 2;
}
message ConfigMotorLockType {
MotorLockType type = 1;
// additional lock specific options could be added here later
// will still keep this in place even if it only holds the type enum at the moment
}
enum ConfigHardwareRevision {
HW_REV_UNKNOWN = 0;
HW_REV_A = 1;
HW_REV_B = 2;
}
enum MotorLockType {
MOTOR_LOCK_UNKNOWN = 0;
MOTOR_LOCK_HELLA = 1;
MOTOR_LOCK_DEBUG_VALEO_HVAC = 2;
// add additional locks here
MOTOR_LOCK_NONE = -1;
}
message RcdCommand {
bool test = 1; // true -> set TEST pin high, false -> set TEST pin low
bool reset = 2; // reset RCD/emergency off if set to true
}

View File

@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "ac_rcdImpl.hpp"
namespace module {
namespace rcd_1 {
void ac_rcdImpl::init() {
mod->serial.signal_error_flags.connect([this](int connector, ErrorFlags error_flags) {
if (connector == 1) {
if (error_flags.rcd_triggered and not last_error_flags.rcd_triggered) {
Everest::error::Error error_object = this->error_factory->create_error(
"ac_rcd/DC", "", "Port 1 RDC-MD triggered", Everest::error::Severity::High);
this->raise_error(error_object);
} else if (not error_flags.rcd_triggered and last_error_flags.rcd_triggered) {
this->clear_error("ac_rcd/DC");
}
last_error_flags = error_flags;
}
});
}
void ac_rcdImpl::ready() {
}
void ac_rcdImpl::handle_self_test() {
mod->serial.set_rcd_test(1, true);
}
bool ac_rcdImpl::handle_reset() {
mod->serial.reset_rcd(1, true);
return true;
}
} // namespace rcd_1
} // namespace module

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef RCD_1_AC_RCD_IMPL_HPP
#define RCD_1_AC_RCD_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/ac_rcd/Implementation.hpp>
#include "../PhyVersoBSP.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_1 {
struct Conf {};
class ac_rcdImpl : public ac_rcdImplBase {
public:
ac_rcdImpl() = delete;
ac_rcdImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<PhyVersoBSP>& mod, Conf& config) :
ac_rcdImplBase(ev, "rcd_1"), 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<PhyVersoBSP>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
ErrorFlags last_error_flags{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 rcd_1
} // namespace module
#endif // RCD_1_AC_RCD_IMPL_HPP

View File

@@ -0,0 +1,38 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "ac_rcdImpl.hpp"
namespace module {
namespace rcd_2 {
void ac_rcdImpl::init() {
mod->serial.signal_error_flags.connect([this](int connector, ErrorFlags error_flags) {
if (connector == 2) {
if (error_flags.rcd_triggered and not last_error_flags.rcd_triggered) {
Everest::error::Error error_object = this->error_factory->create_error(
"ac_rcd/DC", "", "Port 2 RDC-MD triggered", Everest::error::Severity::High);
this->raise_error(error_object);
} else if (not error_flags.rcd_triggered and last_error_flags.rcd_triggered) {
this->clear_error("ac_rcd/DC");
}
last_error_flags = error_flags;
}
});
}
void ac_rcdImpl::ready() {
}
void ac_rcdImpl::handle_self_test() {
mod->serial.set_rcd_test(2, true);
}
bool ac_rcdImpl::handle_reset() {
mod->serial.reset_rcd(2, true);
return true;
}
} // namespace rcd_2
} // namespace module

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef RCD_2_AC_RCD_IMPL_HPP
#define RCD_2_AC_RCD_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/ac_rcd/Implementation.hpp>
#include "../PhyVersoBSP.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_2 {
struct Conf {};
class ac_rcdImpl : public ac_rcdImplBase {
public:
ac_rcdImpl() = delete;
ac_rcdImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<PhyVersoBSP>& mod, Conf& config) :
ac_rcdImplBase(ev, "rcd_2"), 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<PhyVersoBSP>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
ErrorFlags last_error_flags{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 rcd_2
} // namespace module
#endif // RCD_2_AC_RCD_IMPL_HPP

View File

@@ -0,0 +1 @@
Drivers for an AC or DC power path on the EVSE side

View File

@@ -0,0 +1,38 @@
#
# 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(tida_010939_comms)
target_include_directories(${MODULE_NAME}
PRIVATE
"common"
"tida_010939_comms"
"tida_010939_comms/nanopb"
"tida_010939_comms/protobuf"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
tida_010939_comms
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"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 firmware.bin DESTINATION ${CMAKE_INSTALL_DATADIR}/everest/modules/TIDA010939/firmware)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "TIDA010939.hpp"
#include <fmt/core.h>
#include <utils/date.hpp>
namespace module {
void TIDA010939::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_connector_lock);
invoke_init(*p_rcd);
}
void TIDA010939::ready() {
serial.run();
if (!serial.reset(config.reset_gpio_chip, config.reset_gpio)) {
EVLOG_error << "TIDA010939 reset not successful.";
}
serial.signalSpuriousReset.connect([this]() { EVLOG_error << "TIDA010939 uC spurious reset!"; });
serial.signalConnectionTimeout.connect([this]() { EVLOG_error << "TIDA010939 UART timeout!"; });
invoke_ready(*p_board_support);
invoke_ready(*p_connector_lock);
invoke_ready(*p_rcd);
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 TIDA010939::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 TIDA010939::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.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,90 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef TIDA010939_HPP
#define TIDA010939_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>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
#include "tida_010939_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 max_current_A_import;
int min_current_A_import;
int min_phase_count_import;
int max_phase_count_import;
int min_current_A_export;
int max_current_A_export;
int min_phase_count_export;
int max_phase_count_export;
bool has_socket;
};
class TIDA010939 : public Everest::ModuleBase {
public:
TIDA010939() = delete;
TIDA010939(const ModuleInfo& info, 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),
p_board_support(std::move(p_board_support)),
p_rcd(std::move(p_rcd)),
p_connector_lock(std::move(p_connector_lock)),
config(config){};
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
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 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 // TIDA010939_HPP

View File

@@ -0,0 +1,187 @@
// 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 = mod->config.min_current_A_import;
caps.max_current_A_import = mod->config.max_current_A_import;
caps.min_phase_count_import = mod->config.min_phase_count_import;
caps.max_phase_count_import = mod->config.max_phase_count_import;
caps.supports_changing_phases_during_charging = false;
caps.supports_cp_state_E = false;
caps.min_current_A_export = mod->config.min_current_A_export;
caps.max_current_A_export = mod->config.max_current_A_export;
caps.min_phase_count_export = mod->config.min_phase_count_export;
caps.max_phase_count_export = mod->config.max_phase_count_export;
if (mod->config.has_socket) {
caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Socket;
} else {
caps.connector_type = types::evse_board_support::Connector_type::IEC62196Type2Cable;
}
}
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) {
if (not caps_received) {
EVLOG_info << "TIDA-010939 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 TIDA-010939 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) {
}
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 "../TIDA010939.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<TIDA010939>& 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<TIDA010939>& 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,25 @@
// 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() {
mod->serial.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 "../TIDA010939.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<TIDA010939>& 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<TIDA010939>& 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,66 @@
.. _everest_modules_handwritten_TIDA010939:
.. **********
.. TIDA010939
.. **********
The module ``TIDA010939`` is a board support driver for Texas Instruments
TIDA-010939 reference design. It is based on the Yeti driver with similar structure
and functionality.
Communication between the TIDA010939 microcontroller and this driver module
============================================================================
The hardware connection between TIDA010939 and the host system (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 boot into the bootloader).
The default configuration is 115200 bps 8N1.
Protocol
========
EVerest can send commands to TIDA010939 and TIDA010939 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 ``tida_010939_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 ``tida_010939_comms/protobuf``.
``tida_010939.proto`` contains all messages that can be sent from EVerest to TIDA010939 and
all messages that TIDA010939 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 TIDA010939 Firmware to ensure the same
definition is used on both sides when making changes.
References
============
`Official website https://www.ti.com/tool/TIDA-010939 <https://www.ti.com/tool/TIDA-010939>`_

View File

@@ -0,0 +1,89 @@
description: Driver module for the Texas Instruments TIDA-010939 reference design
config:
serial_port:
description: Serial port the TIDA010939 hardware is connected to
type: string
default: /dev/ttyUSB0
baud_rate:
description: Serial baud rate to use when communicating with TIDA010939 hardware
type: integer
minimum: 9600
maximum: 230400
default: 115200
reset_gpio_chip:
description: >-
Reset GPIO chip to use to HW reset TIDA010939. If set to empty string, it is disabled.
type: string
default: 'gpiochip0'
reset_gpio:
description: GPIO line to use to reset TIDA010939
type: integer
default: 27
max_current_A_import:
description: Maximum import current in amps
type: integer
minimum: 0
default: 16
min_current_A_import:
description: Minimum import current in amps
type: integer
minimum: 0
default: 6
min_phase_count_import:
description: Minimum phase count for import
type: integer
minimum: 1
maximum: 3
default: 3
max_phase_count_import:
description: Maximum phase count for import
type: integer
minimum: 1
maximum: 3
default: 3
min_current_A_export:
description: Minimum export current in amps
type: integer
minimum: 0
maximum: 63
default: 0
max_current_A_export:
description: Maximum export current in amps
type: integer
minimum: 0
maximum: 63
default: 0
min_phase_count_export:
description: Minimum phase count for export
type: integer
minimum: 1
maximum: 3
default: 3
max_phase_count_export:
description: Maximum phase count for export
type: integer
minimum: 1
maximum: 3
default: 3
has_socket:
description: Set to true if it has a socket, false if it has a permanently attached cable
type: boolean
default: false
provides:
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
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Cornelius Claussen
- Kai-Uwe Hermann
- Thilo Molitor
- Anton Wöllert
- Fabian Gajek

View File

@@ -0,0 +1,44 @@
// 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() {
mod->serial.signalErrorFlags.connect([this](ErrorFlags error_flags) {
if (error_flags.rcd_triggered and not last_error_flags.rcd_triggered) {
Everest::error::Error error_object =
this->error_factory->create_error("ac_rcd/DC", "", "RDC-MD triggered", Everest::error::Severity::High);
this->raise_error(error_object);
} else if (not error_flags.rcd_triggered and last_error_flags.rcd_triggered) {
this->clear_error("ac_rcd/DC");
}
if (error_flags.rcd_selftest_failed and not last_error_flags.rcd_selftest_failed) {
Everest::error::Error error_object = this->error_factory->create_error(
"ac_rcd/Selftest", "", "RCD self-test failed", Everest::error::Severity::High);
this->raise_error(error_object);
} else if (not error_flags.rcd_selftest_failed and last_error_flags.rcd_selftest_failed) {
this->clear_error("ac_rcd/Selftest");
}
last_error_flags = error_flags;
});
}
void ac_rcdImpl::ready() {
}
void ac_rcdImpl::handle_self_test() {
mod->serial.set_rcd_test(true);
}
bool ac_rcdImpl::handle_reset() {
mod->serial.reset_rcd(true);
return true;
}
} // namespace rcd
} // namespace module

View File

@@ -0,0 +1,63 @@
// 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 "../TIDA010939.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<TIDA010939>& 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<TIDA010939>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// insert your private definitions here
ErrorFlags last_error_flags;
// 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(tida_010939_comms VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# add the executable
add_library(tida_010939_comms STATIC)
ev_register_library_target(tida_010939_comms)
target_sources(tida_010939_comms
PRIVATE
evSerial.cpp
protobuf/tida010939.pb.c
)
target_include_directories(tida_010939_comms
PUBLIC
"${PROJECT_BINARY_DIR}"
protobuf
)
target_link_libraries(tida_010939_comms
PUBLIC
date::date-tz
everest::nanopb
PRIVATE
Pal::Sigslot
everest::framework
everest::gpio
)

View File

@@ -0,0 +1,437 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 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 "tida010939.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");
// hexdump the data
for (int i = 0; i < len; i++) {
printf("%02X ", buf[i]);
}
printf("\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)) {
// printf("Decoded message with type %d\n", msg_in.which_payload);
switch (msg_in.which_payload) {
case McuToEverest_telemetry_tag:
// printf("Received telemetry\n");
// printf("cp_voltage_hi: %f\n", msg_in.payload.telemetry.cp_voltage_hi);
// printf("cp_voltage_lo: %f\n", msg_in.payload.telemetry.cp_voltage_lo);
// printf("temp0: %f\n", msg_in.payload.telemetry.temp0);
// printf("temp1: %f\n", msg_in.payload.telemetry.temp1);
break;
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_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;
}
} else {
printf("Error decoding message: %s\n", PB_GET_ERROR(&istream));
}
}
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::lock() {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_connector_lock_tag;
msg_out.payload.connector_lock = true;
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_rcd_test(bool test) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_rcd_cmd_tag;
msg_out.payload.rcd_cmd.test = test;
msg_out.payload.rcd_cmd.reset = false; // reset is false for test command
linkWrite(&msg_out);
}
void evSerial::reset_rcd(bool reset) {
EverestToMcu msg_out = EverestToMcu_init_default;
msg_out.which_payload = EverestToMcu_rcd_cmd_tag;
msg_out.payload.rcd_cmd.test = false; // test is false for reset command
msg_out.payload.rcd_cmd.reset = reset;
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 TIDA-010939 controller to be in a known state
printf("Resetting TIDA-010939 controller via GPIO %s:%d\n", reset_chip.c_str(), reset_line);
Everest::Gpio reset_gpio;
reset_gpio.open(reset_chip, reset_line);
reset_gpio.set_output(true);
reset_gpio.set(false);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
reset_gpio.set(true);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
reset_gpio.set(false);
} else {
// Try to soft reset TIDA-010939 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,80 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef TIDA_010939_SERIAL
#define TIDA_010939_SERIAL
#include "tida010939.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); // Firmware update functionality removed for TIDA010939
void keepAlive();
void setPWM(uint32_t dc);
void allowPowerOn(bool p);
void lock();
void forceUnlock();
void set_rcd_test(bool test);
void reset_rcd(bool reset);
sigslot::signal<KeepAliveLo> signalKeepAliveLo;
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 TIDA_010939_FIRMWARE_VERSION
#define TIDA_010939_FIRMWARE_VERSION
#include <regex>
#include <string>
/*
Helper class to handle TIDA-010939 Firmware Versions of the following style:
Reported by MCU: 2.0-1-g2d51638
Included in file name: TIDA-010939_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 TIDA010939FirmwareVersion {
public:
TIDA010939FirmwareVersion() {
}
TIDA010939FirmwareVersion(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;
}
}
}
TIDA010939FirmwareVersion& operator=(const std::string& s) {
from_string(s);
return *this;
}
friend bool operator<(const TIDA010939FirmwareVersion& l, const TIDA010939FirmwareVersion& 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 TIDA010939FirmwareVersion& lhs, const TIDA010939FirmwareVersion& rhs) {
return rhs < lhs;
}
friend bool operator<=(const TIDA010939FirmwareVersion& lhs, const TIDA010939FirmwareVersion& rhs) {
return not(lhs > rhs);
}
friend bool operator>=(const TIDA010939FirmwareVersion& lhs, const TIDA010939FirmwareVersion& 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 . tida010939.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,41 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9.1 */
#include "tida010939.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(EverestToMcu, EverestToMcu, AUTO)
PB_BIND(McuToEverest, McuToEverest, 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)
PB_BIND(RcdCommand, RcdCommand, AUTO)

View File

@@ -0,0 +1,340 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_TIDA010939_PB_H_INCLUDED
#define PB_TIDA010939_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 {
float cp_voltage_hi;
float cp_voltage_lo;
float temp0;
float temp1;
} 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 {
KeepAliveLo keep_alive;
ResetReason reset;
CpState cp_state;
/* false: relais are off, true: relais are on */
bool relais_state;
ErrorFlags error_flags;
Telemetry telemetry;
PpState pp_state;
LockState lock_state;
} payload;
} McuToEverest;
typedef struct _FirmwareUpdate {
bool invoke_rom_bootloader;
} FirmwareUpdate;
typedef struct _RcdCommand {
bool test; /* true -> set TEST pin high, false -> set TEST pin low */
bool reset; /* reset RCD/emergency off if set to true */
} RcdCommand;
/* *
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 {
FirmwareUpdate firmware_update;
KeepAlive keep_alive;
/* false: unlock, true: lock */
bool connector_lock;
/* in 0.01 %, 0 = State F, 10000 = X1 */
uint32_t pwm_duty_cycle;
bool allow_power_on;
bool reset;
RcdCommand rcd_cmd;
} 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, 0, 0}
#define FirmwareUpdate_init_default {0}
#define RcdCommand_init_default {0, 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, 0, 0}
#define FirmwareUpdate_init_zero {0}
#define RcdCommand_init_zero {0, 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 Telemetry_temp0_tag 3
#define Telemetry_temp1_tag 4
#define McuToEverest_keep_alive_tag 1
#define McuToEverest_reset_tag 2
#define McuToEverest_cp_state_tag 3
#define McuToEverest_relais_state_tag 4
#define McuToEverest_error_flags_tag 5
#define McuToEverest_telemetry_tag 6
#define McuToEverest_pp_state_tag 7
#define McuToEverest_lock_state_tag 8
#define FirmwareUpdate_invoke_rom_bootloader_tag 1
#define RcdCommand_test_tag 1
#define RcdCommand_reset_tag 2
#define EverestToMcu_firmware_update_tag 1
#define EverestToMcu_keep_alive_tag 2
#define EverestToMcu_connector_lock_tag 3
#define EverestToMcu_pwm_duty_cycle_tag 4
#define EverestToMcu_allow_power_on_tag 5
#define EverestToMcu_reset_tag 6
#define EverestToMcu_rcd_cmd_tag 8
/* Struct field encoding specification for nanopb */
#define EverestToMcu_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (payload,firmware_update,payload.firmware_update), 1) \
X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 2) \
X(a, STATIC, ONEOF, BOOL, (payload,connector_lock,payload.connector_lock), 3) \
X(a, STATIC, ONEOF, UINT32, (payload,pwm_duty_cycle,payload.pwm_duty_cycle), 4) \
X(a, STATIC, ONEOF, BOOL, (payload,allow_power_on,payload.allow_power_on), 5) \
X(a, STATIC, ONEOF, BOOL, (payload,reset,payload.reset), 6) \
X(a, STATIC, ONEOF, MESSAGE, (payload,rcd_cmd,payload.rcd_cmd), 8)
#define EverestToMcu_CALLBACK NULL
#define EverestToMcu_DEFAULT NULL
#define EverestToMcu_payload_firmware_update_MSGTYPE FirmwareUpdate
#define EverestToMcu_payload_keep_alive_MSGTYPE KeepAlive
#define EverestToMcu_payload_rcd_cmd_MSGTYPE RcdCommand
#define McuToEverest_FIELDLIST(X, a) \
X(a, STATIC, ONEOF, MESSAGE, (payload,keep_alive,payload.keep_alive), 1) \
X(a, STATIC, ONEOF, UENUM, (payload,reset,payload.reset), 2) \
X(a, STATIC, ONEOF, UENUM, (payload,cp_state,payload.cp_state), 3) \
X(a, STATIC, ONEOF, BOOL, (payload,relais_state,payload.relais_state), 4) \
X(a, STATIC, ONEOF, MESSAGE, (payload,error_flags,payload.error_flags), 5) \
X(a, STATIC, ONEOF, MESSAGE, (payload,telemetry,payload.telemetry), 6) \
X(a, STATIC, ONEOF, UENUM, (payload,pp_state,payload.pp_state), 7) \
X(a, STATIC, ONEOF, UENUM, (payload,lock_state,payload.lock_state), 8)
#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 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, FLOAT, cp_voltage_hi, 1) \
X(a, STATIC, SINGULAR, FLOAT, cp_voltage_lo, 2) \
X(a, STATIC, SINGULAR, FLOAT, temp0, 3) \
X(a, STATIC, SINGULAR, FLOAT, temp1, 4)
#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
#define RcdCommand_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, test, 1) \
X(a, STATIC, SINGULAR, BOOL, reset, 2)
#define RcdCommand_CALLBACK NULL
#define RcdCommand_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 FirmwareUpdate_msg;
extern const pb_msgdesc_t RcdCommand_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 FirmwareUpdate_fields &FirmwareUpdate_msg
#define RcdCommand_fields &RcdCommand_msg
/* Maximum encoded size of messages (where known) */
#define ErrorFlags_size 14
#define EverestToMcu_size 72
#define FirmwareUpdate_size 2
#define KeepAliveLo_size 106
#define KeepAlive_size 70
#define McuToEverest_size 108
#define RcdCommand_size 4
#define TIDA010939_PB_H_MAX_SIZE McuToEverest_size
#define Telemetry_size 20
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -0,0 +1,118 @@
syntax = "proto3";
/**
* This container message is send from EVerest to MCU and may contain any allowed message in that direction.
*/
message EverestToMcu {
oneof payload {
FirmwareUpdate firmware_update = 1;
KeepAlive keep_alive = 2;
// false: unlock, true: lock
bool connector_lock = 3;
// in 0.01 %, 0 = State F, 10000 = X1
uint32 pwm_duty_cycle = 4;
bool allow_power_on = 5;
bool reset = 6;
RcdCommand rcd_cmd = 8;
}
}
/**
* This container message is send from MCU to EVerest and may contain any allowed message in that direction.
*/
message McuToEverest {
oneof payload {
KeepAliveLo keep_alive = 1;
ResetReason reset = 2;
CpState cp_state = 3;
// false: relais are off, true: relais are on
bool relais_state = 4;
ErrorFlags error_flags = 5;
Telemetry telemetry = 6;
PpState pp_state = 7;
LockState lock_state = 8;
}
}
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;
}
message KeepAlive {
uint32 time_stamp = 1;
uint32 hw_type = 2;
uint32 hw_revision = 3;
string sw_version_string = 6;
}
message Telemetry {
float cp_voltage_hi = 1;
float cp_voltage_lo = 2;
float temp0 = 3;
float temp1 = 4;
}
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 FirmwareUpdate {
bool invoke_rom_bootloader = 1;
}
message RcdCommand {
bool test = 1; // true -> set TEST pin high, false -> set TEST pin low
bool reset = 2; // reset RCD/emergency off if set to true
}

Some files were not shown because too many files have changed in this diff Show More