AP_Generator: Split into frontend-backend and add IE fuel cells

This commit is contained in:
Gone4Dirt 2020-10-22 18:02:15 +01:00 committed by Peter Barker
parent 6c136862b8
commit 8cfe4fc9f7
12 changed files with 1206 additions and 89 deletions

View File

@ -0,0 +1,175 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AP_Generator.h"
#if GENERATOR_ENABLED
#include "AP_Generator_IE_650_800.h"
#include "AP_Generator_IE_2400.h"
#include "AP_Generator_RichenPower.h"
const AP_Param::GroupInfo AP_Generator::var_info[] = {
// @Param: TYPE
// @DisplayName: Generator type
// @Description: Generator type
// @Values: 0:Disabled, 1:IE 650w 800w Fuel Cell, 2:IE 2.4kW Fuel Cell, 3: Richenpower
// @User: Standard
// @RebootRequired: True
AP_GROUPINFO_FLAGS("TYPE", 1, AP_Generator, _type, 0, AP_PARAM_FLAG_ENABLE),
AP_GROUPEND
};
// Constructor
AP_Generator::AP_Generator()
{
AP_Param::setup_object_defaults(this, var_info);
if (_singleton) {
#if CONFIG_HAL_BOARD == HAL_BOARD_SITL
AP_HAL::panic("Too many generators");
#endif
return;
}
_singleton = this;
}
void AP_Generator::init()
{
// Select backend
switch (type()) {
case Type::GEN_DISABLED:
// Not using a generator
return;
case Type::IE_650_800:
_driver_ptr = new AP_Generator_IE_650_800(*this);
break;
case Type::IE_2400:
_driver_ptr = new AP_Generator_IE_2400(*this);
break;
case Type::RICHENPOWER:
_driver_ptr = new AP_Generator_RichenPower(*this);
break;
}
if (_driver_ptr != nullptr) {
_driver_ptr->init();
}
}
void AP_Generator::update()
{
// Return immediatly if not enabled. Don't support run-time disabling of generator
if (_driver_ptr == nullptr) {
return;
}
// Calling backend update will cause backend to update the front end variables
_driver_ptr->update();
}
// Helper to get param and cast to Type
enum AP_Generator::Type AP_Generator::type() const
{
return (Type)_type.get();
}
// Pass through to backend
void AP_Generator::send_generator_status(const GCS_MAVLINK &channel)
{
if (_driver_ptr == nullptr) {
return;
}
_driver_ptr->send_generator_status(channel);
}
// Tell backend to perform arming checks
bool AP_Generator::pre_arm_check(char* failmsg, uint8_t failmsg_len) const
{
if (type() == Type::GEN_DISABLED) {
// Don't prevent arming if generator is not enabled and has never been init
if (_driver_ptr == nullptr) {
return true;
}
// Don't allow arming if we have disabled the generator since boot
strncpy(failmsg, "Generator disabled, reboot reqired", failmsg_len);
return false;
}
if (_driver_ptr == nullptr) {
strncpy(failmsg, "No backend driver", failmsg_len);
return false;
}
return _driver_ptr->pre_arm_check(failmsg, failmsg_len);
}
// Tell backend check failsafes
AP_BattMonitor::BatteryFailsafe AP_Generator::update_failsafes()
{
// Don't invoke a failsafe if driver not assigned
if (_driver_ptr == nullptr) {
return AP_BattMonitor::BatteryFailsafe_None;
}
return _driver_ptr->update_failsafes();
}
// Pass through to backend
bool AP_Generator::stop()
{
// Still allow
if (_driver_ptr == nullptr) {
return false;
}
return _driver_ptr->stop();
}
// Pass through to backend
bool AP_Generator::idle()
{
if (_driver_ptr == nullptr) {
return false;
}
return _driver_ptr->idle();
}
// Pass through to backend
bool AP_Generator::run()
{
// Don't allow operators to request generator be set to run if it has been disabled
if (_driver_ptr == nullptr) {
return false;
}
return _driver_ptr->run();
}
// Get the AP_Generator singleton
AP_Generator *AP_Generator::get_singleton()
{
return _singleton;
}
AP_Generator *AP_Generator::_singleton = nullptr;
namespace AP {
AP_Generator *generator()
{
return AP_Generator::get_singleton();
}
};
#endif

View File

@ -0,0 +1,106 @@
#pragma once
#include <AP_HAL/AP_HAL.h>
#ifndef GENERATOR_ENABLED
#define GENERATOR_ENABLED !HAL_MINIMIZE_FEATURES
#endif
#if GENERATOR_ENABLED
#include <AP_Param/AP_Param.h>
#include <GCS_MAVLink/GCS.h>
#include <AP_BattMonitor/AP_BattMonitor.h>
class AP_Generator_Backend;
class AP_Generator_IE_650_800;
class AP_Generator_IE_2400;
class AP_Generator_RichenPower;
class AP_Generator
{
friend class AP_Generator_Backend;
friend class AP_Generator_IE_650_800;
friend class AP_Generator_IE_2400;
friend class AP_Generator_RichenPower;
public:
// Constructor
AP_Generator();
// Do not allow copies
AP_Generator(const AP_Generator &other) = delete;
AP_Generator &operator=(const AP_Generator&) = delete;
static AP_Generator* get_singleton();
void init(void);
void update(void);
bool pre_arm_check(char *failmsg, uint8_t failmsg_len) const;
AP_BattMonitor::BatteryFailsafe update_failsafes(void);
// Helpers to retrieve measurements
float get_voltage(void) const { return _voltage; }
float get_current(void) const { return _current; }
float get_fuel_remain(void) const { return _fuel_remain_pct; }
float get_batt_consumed(void) const { return _consumed_mah; }
uint16_t get_rpm(void) const { return _rpm; }
// Helpers to see if backend has a measurement
bool has_current() const { return _has_current; }
bool has_consumed_energy() const { return _has_consumed_energy; }
bool has_fuel_remaining() const { return _has_fuel_remaining; }
// healthy() returns true if the generator is not present, or it is
// present, providing telemetry and not indicating any errors.
bool healthy(void) const { return _healthy; }
// Generator controls must return true if present in generator type
bool stop(void);
bool idle(void);
bool run(void);
void send_generator_status(const GCS_MAVLINK &channel);
// Parameter block
static const struct AP_Param::GroupInfo var_info[];
private:
// Pointer to chosen driver
AP_Generator_Backend* _driver_ptr;
// Parameters
AP_Int8 _type; // Select which generator to use
enum class Type {
GEN_DISABLED = 0,
IE_650_800 = 1,
IE_2400 = 2,
RICHENPOWER = 3,
};
// Helper to get param and cast to GenType
Type type(void) const;
// Front end variables
float _voltage;
float _current;
float _fuel_remain_pct;
float _consumed_mah;
uint16_t _rpm;
bool _healthy;
bool _has_current;
bool _has_consumed_energy;
bool _has_fuel_remaining;
static AP_Generator *_singleton;
};
namespace AP {
AP_Generator *generator();
};
#endif

View File

@ -0,0 +1,36 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AP_Generator_Backend.h"
#if GENERATOR_ENABLED
// Base class consructor
AP_Generator_Backend::AP_Generator_Backend(AP_Generator& frontend) :
_frontend(frontend)
{
}
// Called from the subclass update function to update the frontend variables for accessing
void AP_Generator_Backend::update_frontend()
{
// Update the values in the front end
_frontend._voltage = _voltage;
_frontend._current = _current;
_frontend._consumed_mah = _consumed_mah;
_frontend._fuel_remain_pct = _fuel_remain_pct;
_frontend._rpm = _rpm;
_frontend._healthy = healthy();
}
#endif

View File

@ -0,0 +1,53 @@
#pragma once
#include "AP_Generator.h"
#if GENERATOR_ENABLED
class AP_Generator_Backend
{
public:
// Constructor
AP_Generator_Backend(AP_Generator& frontend);
// Declare a virtual destructor in case Generator drivers want to override
virtual ~AP_Generator_Backend() {};
virtual void init(void) = 0;
virtual void update(void) = 0;
// Set default to not fail arming checks
virtual bool pre_arm_check(char *failmsg, uint8_t failmsg_len) const { return true; }
// Set default to not fail failsafes
virtual AP_BattMonitor::BatteryFailsafe update_failsafes(void) const {
return AP_BattMonitor::BatteryFailsafe::BatteryFailsafe_None;
}
virtual bool healthy(void) const = 0;
// Generator controls must return true if present in generator type
virtual bool stop(void) { return false; }
virtual bool idle(void) { return false; }
virtual bool run(void) { return false; }
// Use generator mavlink message
virtual void send_generator_status(const GCS_MAVLINK &channel) {}
protected:
// Update frontend
void update_frontend(void);
// Measurements readings to write to front end
float _voltage;
float _current;
float _fuel_remain_pct; // Decimal from 0 to 1
float _consumed_mah;
uint16_t _rpm;
AP_Generator& _frontend;
};
#endif

View File

@ -0,0 +1,206 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AP_Generator_IE_2400.h"
#if GENERATOR_ENABLED
#include <AP_Logger/AP_Logger.h>
extern const AP_HAL::HAL& hal;
void AP_Generator_IE_2400::init()
{
// Call init from base class to do common setup
AP_Generator_IE_FuelCell::init();
// Tell frontend what measurements are available for this generator
_frontend._has_current = true;
_frontend._has_consumed_energy = true;
_frontend._has_fuel_remaining = true;
}
// Update fuel cell, expected to be called at 20hz
void AP_Generator_IE_2400::assign_measurements(const uint32_t now)
{
// Successfully decoded a new valid sentence
// Update internal fuel cell state
_pwr_out = _parsed.pwr_out;
_spm_pwr = _parsed.spm_pwr;
_state = (State)_parsed.state;
_err_code = _parsed.err_code;
// Scale tank pressure linearly to a percentage.
// Min = 5 bar, max = 300 bar, PRESS_GRAD = 1/295.
const float PRESS_GRAD = 0.003389830508f;
_fuel_remain_pct = constrain_float((_parsed.tank_bar-5)*PRESS_GRAD,0,1);
// Update battery voltage
_voltage = _parsed.battery_volt;
/* Calculate battery current. Convention: +current battery is discharging, -current
battery is charging. This is aligned with normal AP behaviour. This is the opposite
of IE's convention hence *-1 */
if (_parsed.battery_volt > 0) {
_current = -1 * _parsed.battery_pwr / _parsed.battery_volt;
} else {
_current = 0;
}
// Calculate consumed current
_consumed_mah += _current * (now - _last_time_ms) * AMS_TO_MAH;
_last_time_ms = now;
}
// Process characters received and extract terms for IE 2.4kW
void AP_Generator_IE_2400::decode_latest_term()
{
// Null terminate and move onto the next term
_term[_term_offset] = 0;
_term_offset = 0;
_term_number++;
switch (_term_number) {
case 1:
// Float
_parsed.tank_bar = atof(_term);
break;
case 2:
// Float
_parsed.battery_volt = atof(_term);
break;
case 3:
// Signed int base 10
_parsed.pwr_out = strtol(_term, nullptr, 10);
break;
case 4:
// Unsigned int base 10
_parsed.spm_pwr = strtoul(_term, nullptr, 10);
break;
case 5:
// Signed int base 10
_parsed.battery_pwr = strtol(_term, nullptr, 10);
break;
case 6:
// Unsigned int base 10
_parsed.state = strtoul(_term, nullptr, 10);
break;
case 7:
// Unsigned int base 10
_parsed.err_code = strtoul(_term, nullptr, 10);
// Sentence only declared valid when we have the expected number of terms
_sentence_valid = true;
break;
default:
// We have received more terms than, something has gone wrong with telemetry data, mark invalid sentence
_sentence_valid = false;
break;
}
}
// Check for failsafes
AP_BattMonitor::BatteryFailsafe AP_Generator_IE_2400::update_failsafes() const
{
// Check for error codes that lead to critical action battery monitor failsafe
if (is_critical_error(_err_code)) {
return AP_BattMonitor::BatteryFailsafe::BatteryFailsafe_Critical;
}
// Check for error codes that lead to low action battery monitor failsafe
if (is_low_error(_err_code)) {
return AP_BattMonitor::BatteryFailsafe::BatteryFailsafe_Low;
}
return AP_BattMonitor::BatteryFailsafe::BatteryFailsafe_None;
}
// Check for error codes that are deemed critical
bool AP_Generator_IE_2400::is_critical_error(const uint32_t err_in) const
{
switch ((ErrorCode)err_in) {
// Error codes that lead to critical action battery monitor failsafe
case ErrorCode::BATTERY_CRITICAL:
case ErrorCode::PRESSURE_CRITICAL:
case ErrorCode::SYSTEM_CRITICAL:
return true;
default:
// Minor internal error is always ignored and caught by the default
return false;
}
}
// Check for error codes that are deemed severe and would be cause to trigger a battery monitor low failsafe action
bool AP_Generator_IE_2400::is_low_error(const uint32_t err_in) const
{
switch ((ErrorCode)err_in) {
// Error codes that lead to critical action battery monitor failsafe
case ErrorCode::START_DENIED:
case ErrorCode::PRESSURE_ALERT:
case ErrorCode::BATTERY_LOW:
case ErrorCode::PRESSURE_LOW:
case ErrorCode::SPM_LOST:
case ErrorCode::REDUCED_POWER:
return true;
default:
// Minor internal error is always ignored and caught by the default
return false;
}
}
// Check error codes and populate message with error code
bool AP_Generator_IE_2400::check_for_err_code(char* msg_txt, uint8_t msg_len) const
{
// Check if we have received an error code
if (!is_critical_error(_err_code) && !is_low_error(_err_code)) {
return false;
}
hal.util->snprintf(msg_txt, msg_len, "Fuel cell err code <%u>", (unsigned)_err_code);
return true;
}
// log generator status to the onboard log
void AP_Generator_IE_2400::log_write()
{
#define MASK_LOG_ANY 0xFFFF
if (!AP::logger().should_log(MASK_LOG_ANY)) {
return;
}
AP::logger().Write(
"IE24",
"TimeUS,FUEL,SPMPWR,POUT,ERR",
"s%WW-",
"F2---",
"Qfiii",
AP_HAL::micros64(),
_fuel_remain_pct,
_spm_pwr,
_pwr_out,
_err_code
);
}
#endif

View File

@ -0,0 +1,56 @@
#pragma once
#include "AP_Generator_IE_FuelCell.h"
#if GENERATOR_ENABLED
class AP_Generator_IE_2400 : public AP_Generator_IE_FuelCell
{
// Inherit constructor
using AP_Generator_IE_FuelCell::AP_Generator_IE_FuelCell;
public:
void init(void) override;
AP_BattMonitor::BatteryFailsafe update_failsafes() const override;
private:
// Assigns the unit specific measurements once a valid sentence is obtained
void assign_measurements(const uint32_t now) override;
// Process characters received and extract terms for IE 2.4kW
void decode_latest_term(void) override;
// Check if we have received an error code and populate message with error code
bool check_for_err_code(char* msg_txt, uint8_t msg_len) const override;
// Check for error codes that are deemed critical
bool is_critical_error(const uint32_t err_in) const;
// Check for error codes that are deemed severe and would be cause to trigger a battery monitor low failsafe action
bool is_low_error(const uint32_t err_in) const;
void log_write(void) override;
// IE 2.4kW failsafes
enum class ErrorCode {
MINOR_INTERNAL = 1, // Minor internal error is to be ignored
REDUCED_POWER = 10,
SPM_LOST = 11,
PRESSURE_LOW = 20,
BATTERY_LOW = 21,
PRESSURE_ALERT = 30,
START_DENIED = 31,
SYSTEM_CRITICAL = 32,
PRESSURE_CRITICAL = 33,
BATTERY_CRITICAL = 40,
};
// These measurements are only available on this unit
int16_t _pwr_out; // Output power (Watts)
uint16_t _spm_pwr; // Stack Power Module (SPM) power draw (Watts)
};
#endif

View File

@ -0,0 +1,125 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AP_Generator_IE_650_800.h"
#if GENERATOR_ENABLED
extern const AP_HAL::HAL& hal;
void AP_Generator_IE_650_800::init()
{
// Call init from base class to do common setup
AP_Generator_IE_FuelCell::init();
// Tell frontend what measurements are available for this generator
// This unit does not have current but this needs to be true to make use of consumed_mah in BattMonitor
_frontend._has_current = true;
_frontend._has_consumed_energy = true;
_frontend._has_fuel_remaining = false;
}
// Update fuel cell, expected to be called at 20hz
void AP_Generator_IE_650_800::assign_measurements(const uint32_t now)
{
// Successfully decoded a new valid sentence
// Update internal fuel cell state
_state = (State)_parsed.state;
_err_code = _parsed.err_code;
// Update variables to be returned to front end
_fuel_remain_pct = _parsed.tank_pct * 0.01;
// Invert bat remaining percent to match AP_BattMonitor convention
_consumed_mah = 100.0f - _parsed.battery_pct;
// This unit does not report voltage, always report 1 volt
_voltage = 1;
_last_time_ms = now;
}
// Process characters received and extract terms for IE 650/800 W
void AP_Generator_IE_650_800::decode_latest_term()
{
// Null terminate and move onto the next term
_term[_term_offset] = 0;
_term_offset = 0;
_term_number++;
switch (_term_number) {
case 1:
_parsed.tank_pct = atof(_term);
// Out of range values
if (_parsed.tank_pct > 100.0f || _parsed.tank_pct < 0.0f) {
_data_valid = false;
}
break;
case 2:
_parsed.battery_pct = atof(_term);
// Out of range values
if (_parsed.battery_pct > 100.0f || _parsed.battery_pct < 0.0f) {
_data_valid = false;
}
break;
case 3:
_parsed.state = strtoul(_term, nullptr, 10);
break;
case 4:
_parsed.err_code = strtoul(_term, nullptr, 16);
// Sentence only declared valid when we have the expected number of terms
_sentence_valid = true && _data_valid;
break;
default:
// We have received more terms than, something has gone wrong with telemetry data, mark invalid sentence
_sentence_valid = false;
break;
}
}
// Check error codes and populate message with error code
bool AP_Generator_IE_650_800::check_for_err_code(char* msg_txt, uint8_t msg_len) const
{
// Check for any valid error codes
if ((_err_code & (fs_crit_mask | fs_low_mask)) == 0) {
return false;
}
// Error codes are converted to hex to make it easier to compare to user manual for these units
hal.util->snprintf(msg_txt, msg_len, "Fuel cell err code <0x%x>", (unsigned)_err_code);
return true;
}
// Check for failsafes
AP_BattMonitor::BatteryFailsafe AP_Generator_IE_650_800::update_failsafes() const
{
// Check if we are in a critical failsafe
if ((_err_code & fs_crit_mask) != 0) {
return AP_BattMonitor::BatteryFailsafe::BatteryFailsafe_Critical;
}
// Check if we are in a low failsafe
if ((_err_code & fs_low_mask) != 0) {
return AP_BattMonitor::BatteryFailsafe::BatteryFailsafe_Low;
}
return AP_BattMonitor::BatteryFailsafe::BatteryFailsafe_None;
}
#endif

View File

@ -0,0 +1,98 @@
#pragma once
#include "AP_Generator_IE_FuelCell.h"
#if GENERATOR_ENABLED
class AP_Generator_IE_650_800 : public AP_Generator_IE_FuelCell
{
// Inherit constructor
using AP_Generator_IE_FuelCell::AP_Generator_IE_FuelCell;
public:
void init(void) override;
AP_BattMonitor::BatteryFailsafe update_failsafes() const override;
private:
// Assigns the unit specific measurements once a valid sentence is obtained
void assign_measurements(const uint32_t now) override;
// Process characters received and extract terms for IE 650/800 W
void decode_latest_term(void) override;
// Convert error code from fuel cell to AP failsafe bitmask
void set_failsafe_mask(uint32_t err_in);
// Check if we have received an error code and populate message with error code
bool check_for_err_code(char* msg_txt, uint8_t msg_len) const override;
// IE 650/800 W error codes
// Note: The error codes for this unit are defined as a bitmask
static const uint32_t ERROR_STACK_OT1_ALRT = (1U << 31); // (0x80000000) Stack 1 over temperature alert (>58 degC)
static const uint32_t ERROR_STACK_OT2_ALRT = (1U << 30); // (0x40000000) Stack 2 over temperature alert (>58 degC)
static const uint32_t ERROR_BAT_UV_ALRT = (1U << 29); // (0x20000000) Battery under volt alert (<19 V)
static const uint32_t ERROR_BAT_OT_ALRT = (1U << 28); // (0x10000000) Battery over temperature alert (>65 degC)
static const uint32_t ERROR_NO_FAN = (1U << 27); // (0x8000000), No fan current detected (<0.01 A)
static const uint32_t ERROR_FAN_OVERRUN = (1U << 26); // (0x4000000), Fan over current (>0.25 A)
static const uint32_t ERROR_STACK_OT1_CRT = (1U << 25); // (0x2000000), Stack 1 over temperature critical (>57 degC)
static const uint32_t ERROR_STACK_OT2_CRT = (1U << 24); // (0x1000000), Stack 2 over temperature critical (>57 degC)
static const uint32_t ERROR_BAT_UV_CRT = (1U << 23); // (0x800000), Battery under volt warning (<19.6 V)
static const uint32_t ERROR_BAT_OT_CRT = (1U << 22); // (0x400000), Battery over temperature warning (>60 degC)
static const uint32_t ERROR_START_TIMEOUT = (1U << 21); // (0x200000), Fuel cell's internal State == start for > 30 s
static const uint32_t ERROR_STOP_TIMEOUT = (1U << 20); // (0x100000), Fuel cell's internal State == stop for > 15 s
static const uint32_t ERROR_START_UNDER_PRESS = (1U << 19); // (0x80000), Tank pressure < 6 barg
static const uint32_t ERROR_TANK_UNDER_PRESS = (1U << 18); // (0x40000), Tank pressure < 5 barg
static const uint32_t ERROR_TANK_LOW_PRESS = (1U << 17); // (0x20000), Tank pressure < 15 barg
static const uint32_t ERROR_SAFETY_FLAG = (1U << 16); // (0x10000), Fuel cell's internal saftey flags not set true
static const uint32_t ERROR_DENY_START1 = (1U << 15); // (0x8000), Stack 1 denied start
static const uint32_t ERROR_DENY_START2 = (1U << 14); // (0x4000), Stack 2 denied start
static const uint32_t ERROR_STACK_UT1 = (1U << 13); // (0x2000), Stack 1 under temperature (<5 degC)
static const uint32_t ERROR_STACK_UT2 = (1U << 12); // (0x1000), Stack 2 under temperature (<5 degC)
static const uint32_t ERROR_BAT_UV_WRN = (1U << 11); // (0x800), Battery under voltage warning (21.6 V)
static const uint32_t ERROR_BAT_UV_DENY = (1U << 10); // (0x400), Battery under voltage (21.6 V) and master denied
static const uint32_t ERROR_FAN_PULSE = (1U << 9); // (0x200), Fan pulse aborted
static const uint32_t ERROR_STACK_UV = (1U << 8); // (0x100), Stack under voltage (650W < 17.4 V, 800W < 21.13 V)
static const uint32_t ERROR_SYS_OVERLOAD = (1U << 7); // (0x80), Stack under voltage and battery power below threshold (<-200 W)
static const uint32_t ERROR_SYS_OV_OC = (1U << 6); // (0x40), Over voltage and over current protection
static const uint32_t ERROR_INVALID_SN = (1U << 5); // (0x20), Invalid serial number
static const uint32_t ERROR_CHARGE_FAULT = (1U << 4); // (0x10), Battery charger fault
static const uint32_t ERROR_BAT_UT = (1U << 3); // (0x8), Battery under temperature (<-15 degC)
// Assign error codes to critical FS mask
static const uint32_t fs_crit_mask = ERROR_STACK_OT1_ALRT // (0x80000000) Stack 1 over temperature alert (>58 degC)
| ERROR_STACK_OT2_ALRT // (0x40000000) Stack 2 over temperature alert (>58 degC)
| ERROR_BAT_UV_ALRT // (0x20000000) Battery undervolt alert (<19 V)
| ERROR_BAT_OT_ALRT // (0x10000000) Battery over temperature alert (>65 degC)
| ERROR_NO_FAN // (0x8000000), No fan current detected (<0.01 A)
| ERROR_STACK_OT1_CRT // (0x2000000), Stack 1 over temperature critical (>57 degC)
| ERROR_STACK_OT2_CRT // (0x1000000), Stack 2 over temperature critical (>57 degC)
| ERROR_BAT_UV_CRT // (0x800000), Battery undervolt warning (<19.6 V)
| ERROR_BAT_OT_CRT // (0x400000), Battery over temperature warning (>60 degC)
| ERROR_START_TIMEOUT // (0x200000), Fuel cell's internal State == start for > 30 s
| ERROR_START_UNDER_PRESS // (0x80000), Tank pressure < 6 barg
| ERROR_TANK_UNDER_PRESS // (0x40000), Tank pressure < 5 barg
| ERROR_SAFETY_FLAG // (0x10000), Fuel cell's internal saftey flags not set true
| ERROR_DENY_START1 // (0x8000), Stack 1 denied start
| ERROR_DENY_START2 // (0x4000), Stack 2 denied start
| ERROR_BAT_UV_DENY // (0x400), Battery under voltage (21.6 V) and master denied
| ERROR_SYS_OV_OC // (0x40), Over voltage and over current protection
| ERROR_INVALID_SN; // (0x20), Invalid serial number
// Assign error codes to low FS mask
static const uint32_t fs_low_mask = ERROR_FAN_OVERRUN // (0x4000000), Fan over current (>0.25 A)
| ERROR_STOP_TIMEOUT // (0x100000), Fuel cell's internal State == stop for > 15 s
| ERROR_TANK_LOW_PRESS // (0x20000), Tank pressure < 15 barg
| ERROR_STACK_UT1 // (0x2000), Stack 1 under temperature (<5 degC)
| ERROR_STACK_UT2 // (0x1000), Stack 2 under temperature (<5 degC)
| ERROR_BAT_UV_WRN // (0x800), Battery under voltage warning (21.6 V)
| ERROR_FAN_PULSE // (0x200), Fan pulse aborted
| ERROR_STACK_UV // (0x100), Stack under voltage (650W < 17.4 V, 800W < 21.13 V)
| ERROR_SYS_OVERLOAD // (0x80), Stack under voltage and battery power below threshold (<-200 W)
| ERROR_CHARGE_FAULT // (0x10), Battery charger fault
| ERROR_BAT_UT; // (0x8), Battery undertemperature (<-15 degC)
};
#endif

View File

@ -0,0 +1,192 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AP_Generator_IE_FuelCell.h"
#include <AP_SerialManager/AP_SerialManager.h>
#if GENERATOR_ENABLED
// Initialize the fuelcell object and prepare it for use
void AP_Generator_IE_FuelCell::init()
{
_uart = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_Generator, 0);
if (_uart == nullptr) {
gcs().send_text(MAV_SEVERITY_INFO, "Generator: No serial port found");
return;
}
_uart->begin(AP::serialmanager().find_baudrate(AP_SerialManager::SerialProtocol_Generator, 0));
_health_warn_sent = false;
}
// Update fuelcell, expected to be called at 20hz
void AP_Generator_IE_FuelCell::update()
{
if (_uart == nullptr) {
return;
}
const uint32_t now = AP_HAL::millis();
// Read any available data
uint32_t nbytes = MIN(_uart->available(),30u);
while (nbytes-- > 0) {
const int16_t c = _uart->read();
if (c < 0) {
// Nothing to decode
break;
}
if (!decode(c)) {
// Sentence not yet valid, don't assign state and output
continue;
}
// We have a valid sentence, write the parsed values to unit specific measurements
assign_measurements(now);
}
_healthy = (now - _last_time_ms) < HEALTHY_TIMEOUT_MS;
// Check if we should notify gcs off any change of fuel cell state
check_status();
update_frontend();
log_write();
}
// Add a single character to the buffer and attempt to decode
// Returns true if a complete sentence was successfully decoded
bool AP_Generator_IE_FuelCell::decode(char c)
{
// Start of a string
if (c == '<') {
_sentence_valid = false;
_data_valid = true;
_term_number = 0;
_term_offset = 0;
_in_string = true;
return false;
}
if (!_in_string) {
return false;
}
// End of a string
if (c == '>') {
decode_latest_term();
_in_string = false;
return _sentence_valid;
}
// End of a term in the string
if (c == ',') {
decode_latest_term();
return false;
}
// Otherwise add the char to the current term
_term[_term_offset++] = c;
// We have overrun the expected sentence
if (_term_offset >TERM_BUFFER) {
_in_string = false;
}
return false;
}
// Check for arming
bool AP_Generator_IE_FuelCell::pre_arm_check(char *failmsg, uint8_t failmsg_len) const
{
// Refuse arming if not healthy
if (!healthy()) {
strncpy(failmsg, "Driver not healthy", failmsg_len);
return false;
}
// Refuse arming if not in running state
if (_state != State::RUNNING) {
strncpy(failmsg, "Status not running", failmsg_len);
return false;
}
// Check for error codes
if (check_for_err_code(failmsg, failmsg_len)) {
return false;
}
return true;
}
// Lookup table for running state. State code is the same for all IE units.
const AP_Generator_IE_FuelCell::Lookup_State AP_Generator_IE_FuelCell::lookup_state[] = {
{ State::STARTING,"Starting"},
{ State::READY,"Ready"},
{ State::RUNNING,"Running"},
{ State::FAULT,"Fault"},
{ State::BATTERY_ONLY,"Battery Only"},
};
// Check for any change in error state or status and report to gcs
void AP_Generator_IE_FuelCell::check_status()
{
// Check driver health
if (!healthy() && !_health_warn_sent) {
// Don't spam GCS with unhealthy message
_health_warn_sent = true;
gcs().send_text(MAV_SEVERITY_ALERT, "Generator: Driver not healthy");
} else if (healthy()) {
_health_warn_sent = false;
}
// If fuel cell state has changed send gcs message
if (_state != _last_state) {
for (const struct Lookup_State entry : lookup_state) {
if (_state == entry.option) {
gcs().send_text(MAV_SEVERITY_INFO, "Generator: %s", entry.msg_txt);
break;
}
}
_last_state = _state;
}
// Check error codes
char msg_txt[32];
if (check_for_err_code_if_changed(msg_txt, sizeof(msg_txt))) {
gcs().send_text(MAV_SEVERITY_ALERT, "%s", msg_txt);
}
}
// Check error codes and populate message with error code
bool AP_Generator_IE_FuelCell::check_for_err_code_if_changed(char* msg_txt, uint8_t msg_len)
{
// Only check if there has been a change in error code
if (_err_code == _last_err_code) {
return false;
}
if (check_for_err_code(msg_txt, msg_len)) {
_last_err_code = _err_code;
return true;
}
return false;
}
#endif

View File

@ -0,0 +1,106 @@
#pragma once
#include "AP_Generator_Backend.h"
#if GENERATOR_ENABLED
#include <AP_Param/AP_Param.h>
#include <GCS_MAVLink/GCS.h>
class AP_Generator_IE_FuelCell : public AP_Generator_Backend
{
public:
// Constructor
using AP_Generator_Backend::AP_Generator_Backend;
// Initialize the fuel cell driver
void init(void) override;
// Check if readings are healthy
bool healthy(void) const override { return _healthy; }
// Check for arming
bool pre_arm_check(char *failmsg, uint8_t failmsg_len) const override;
// Update fuel cell, expected to be called at 20hz
void update(void) override;
protected:
// Pointer to serial uart
AP_HAL::UARTDriver *_uart = nullptr;
// IE fuel cell running states
enum class State {
STARTING = 0,
READY = 1,
RUNNING = 2,
FAULT = 3,
BATTERY_ONLY = 8,
};
// State enum to string lookup
struct Lookup_State {
State option;
const char *msg_txt;
};
static const Lookup_State lookup_state[];
uint32_t _err_code; // The error code from fuel cell
uint32_t _last_err_code; // The previous error code from fuel cell
State _state; // The PSU state
State _last_state; // The previous PSU state
uint32_t _last_time_ms; // Time we got a reading
bool _healthy; // Is the driver working
bool _health_warn_sent;
// Temporary state params
struct ParsedValue {
float tank_pct;
uint16_t tank_bar;
float battery_pct;
float battery_volt;
int16_t pwr_out;
uint16_t spm_pwr;
int16_t battery_pwr;
uint8_t state;
uint32_t err_code;
} _parsed;
// Constants
static const uint8_t TERM_BUFFER = 12; // Max length of term we expect
static const uint16_t HEALTHY_TIMEOUT_MS = 5000; // Time for driver to be marked un-healthy
// Decoding vars
char _term[TERM_BUFFER]; // Term buffer
bool _sentence_valid; // Is current sentence valid
bool _data_valid; // Is data within expected limits
uint8_t _term_number; // Term index within the current sentence
uint8_t _term_offset; // Offset within the _term buffer where the next character should be placed
bool _in_string; // True if we should be decoding
// Assigns the unit specific measurements once a valid sentence is obtained
virtual void assign_measurements(const uint32_t now) = 0;
virtual void log_write(void) {}
// Add a single character to the buffer and attempt to decode.
// Returns true if a complete sentence was successfully decoded or if the buffer is full.
bool decode(char c);
// Unit specific decoding to process characters recieved and build sentence
virtual void decode_latest_term(void) = 0;
// Check if we should notify on any change of fuel cell state
void check_status(void);
// Check error codes and populate message with error code
virtual bool check_for_err_code(char* msg_txt, uint8_t msg_len) const = 0;
// Only check the error code if it has changed since we last checked
bool check_for_err_code_if_changed(char* msg_txt, uint8_t msg_len);
};
#endif

View File

@ -17,28 +17,14 @@
#if GENERATOR_ENABLED
#include <AP_HAL/AP_HAL.h>
#include <AP_Logger/AP_Logger.h>
#include <AP_SerialManager/AP_SerialManager.h>
#include <GCS_MAVLink/GCS.h>
#include <AP_Vehicle/AP_Vehicle.h>
#include <AP_HAL/utility/sparse-endian.h>
extern const AP_HAL::HAL& hal;
// Generator constructor; only one generator is curently permitted
AP_Generator_RichenPower::AP_Generator_RichenPower()
{
if (_singleton) {
#if CONFIG_HAL_BOARD == HAL_BOARD_SITL
AP_HAL::panic("Too many richenpower generators");
#endif
return;
}
_singleton = this;
}
// init method; configure communications with the generator
void AP_Generator_RichenPower::init()
{
@ -49,8 +35,12 @@ void AP_Generator_RichenPower::init()
const uint32_t baud = serial_manager.find_baudrate(AP_SerialManager::SerialProtocol_Generator, 0);
uart->begin(baud, 256, 256);
}
}
// Tell frontend what measurements are available for this generator
_frontend._has_current = true;
_frontend._has_consumed_energy = false;
_frontend._has_fuel_remaining = false;
}
// find a RichenPower message in the buffer, starting at
// initial_offset. If dound, that message (or partial message) will
@ -221,6 +211,8 @@ void AP_Generator_RichenPower::update(void)
update_heat();
update_frontend_readings();
Log_Write();
}
@ -340,14 +332,14 @@ bool AP_Generator_RichenPower::pre_arm_check(char *failmsg, uint8_t failmsg_len)
const uint32_t now = AP_HAL::millis();
if (now - last_reading_ms > 2000) { // we expect @1Hz
snprintf(failmsg, failmsg_len, "no messages in %ums", unsigned(now - last_reading_ms));
hal.util->snprintf(failmsg, failmsg_len, "no messages in %ums", unsigned(now - last_reading_ms));
return false;
}
if (last_reading.seconds_until_maintenance == 0) {
snprintf(failmsg, failmsg_len, "requires maintenance");
hal.util->snprintf(failmsg, failmsg_len, "requires maintenance");
}
if (SRV_Channels::get_channel_for(SRV_Channel::k_generator_control) == nullptr) {
snprintf(failmsg, failmsg_len, "need a servo output channel");
hal.util->snprintf(failmsg, failmsg_len, "need a servo output channel");
return false;
}
@ -355,51 +347,42 @@ bool AP_Generator_RichenPower::pre_arm_check(char *failmsg, uint8_t failmsg_len)
for (uint16_t i=0; i<16; i++) {
if (last_reading.errors & (1U << (uint16_t)i)) {
if (i < (uint16_t)Errors::LAST) {
snprintf(failmsg, failmsg_len, "error: %s", error_strings[i]);
hal.util->snprintf(failmsg, failmsg_len, "error: %s", error_strings[i]);
} else {
snprintf(failmsg, failmsg_len, "unknown error: 1U<<%u", i);
hal.util->snprintf(failmsg, failmsg_len, "unknown error: 1U<<%u", i);
}
}
}
return false;
}
if (pilot_desired_runstate != RunState::RUN) {
snprintf(failmsg, failmsg_len, "requested state is not RUN");
hal.util->snprintf(failmsg, failmsg_len, "requested state is not RUN");
return false;
}
if (commanded_runstate != RunState::RUN) {
snprintf(failmsg, failmsg_len, "Generator warming up (%.0f%%)", (heat *100 / heat_required_for_run()));
hal.util->snprintf(failmsg, failmsg_len, "Generator warming up (%.0f%%)", (heat *100 / heat_required_for_run()));
return false;
}
if (last_reading.mode != Mode::RUN &&
last_reading.mode != Mode::CHARGE &&
last_reading.mode != Mode::BALANCE) {
snprintf(failmsg, failmsg_len, "not running");
hal.util->snprintf(failmsg, failmsg_len, "not running");
return false;
}
return true;
}
// voltage returns the last voltage read from the generator telemetry
bool AP_Generator_RichenPower::voltage(float &v) const
// Update front end with voltage, current, and rpm values
void AP_Generator_RichenPower::update_frontend_readings(void)
{
if (last_reading_ms == 0) {
return false;
}
v = last_reading.output_voltage;
return true;
_voltage = last_reading.output_voltage;
_current = last_reading.output_current;
_rpm = last_reading.rpm;
update_frontend();
}
// current returns the last current read from the generator telemetry
bool AP_Generator_RichenPower::current(float &curr) const
{
if (last_reading_ms == 0) {
return false;
}
curr = last_reading.output_current;
return true;
}
// healthy returns true if the generator is not present, or it is
// present, providing telemetry and not indicating an errors.
@ -486,22 +469,22 @@ void AP_Generator_RichenPower::send_generator_status(const GCS_MAVLINK &channel)
);
}
/*
* Get the AP_Generator singleton
*/
AP_Generator_RichenPower *AP_Generator_RichenPower::_singleton;
AP_Generator_RichenPower *AP_Generator_RichenPower::get_singleton()
// methods to control the generator state:
bool AP_Generator_RichenPower::stop()
{
return _singleton;
set_pilot_desired_runstate(RunState::STOP);
return true;
}
namespace AP {
AP_Generator_RichenPower *generator()
bool AP_Generator_RichenPower::idle()
{
return AP_Generator_RichenPower::get_singleton();
set_pilot_desired_runstate(RunState::IDLE);
return true;
}
};
bool AP_Generator_RichenPower::run()
{
set_pilot_desired_runstate(RunState::RUN);
return true;
}
#endif

View File

@ -1,19 +1,15 @@
// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
#pragma once
#include <AP_Common/AP_Common.h>
#include <AP_HAL/AP_HAL.h>
#include <GCS_MAVLink/GCS.h>
#include <SRV_Channel/SRV_Channel.h>
#include "AP_Generator_Backend.h"
#if GENERATOR_ENABLED
#include <AP_Common/AP_Common.h>
#include <SRV_Channel/SRV_Channel.h>
#include <stdint.h>
#include <stdio.h>
#ifndef GENERATOR_ENABLED
#define GENERATOR_ENABLED !HAL_MINIMIZE_FEATURES
#endif
#if GENERATOR_ENABLED
/*
* Example setup:
@ -23,50 +19,40 @@
* param set SERVO8_FUNCTION 42 # autopilot directive to generator
*/
class AP_Generator_RichenPower
class AP_Generator_RichenPower : public AP_Generator_Backend
{
public:
AP_Generator_RichenPower();
/* Do not allow copies */
AP_Generator_RichenPower(const AP_Generator_RichenPower &other) = delete;
AP_Generator_RichenPower &operator=(const AP_Generator_RichenPower&) = delete;
static AP_Generator_RichenPower *get_singleton();
// constructor
using AP_Generator_Backend::AP_Generator_Backend;
// init should be called at vehicle startup to get the generator library ready
void init();
// update should be called regulkarly to update the generator state
void update(void);
void init(void) override;
// update should be called regularly to update the generator state
void update(void) override;
// methods to control the generator state:
void stop() { set_pilot_desired_runstate(RunState::STOP); }
void idle() { set_pilot_desired_runstate(RunState::IDLE); }
void run() { set_pilot_desired_runstate(RunState::RUN); }
bool stop(void) override;
bool idle(void) override;
bool run(void) override;
// method to send a GENERATOR_STATUS mavlink message
void send_generator_status(const GCS_MAVLINK &channel);
void send_generator_status(const GCS_MAVLINK &channel) override;
// prearm checks to ensure generator is good for arming. Note
// that if the generator has never sent us a message then these
// automatically pass!
bool pre_arm_check(char *failmsg, uint8_t failmsg_len) const;
bool pre_arm_check(char *failmsg, uint8_t failmsg_len) const override;
// these return false if a reading is not available. They do not
// modify the passed-in value if they return false.
bool voltage(float &voltage) const;
bool current(float &current) const;
// Update front end with voltage, current, and rpm values
void update_frontend_readings(void);
// healthy returns true if the generator is not present, or it is
// present, providing telemetry and not indicating an errors.
bool healthy() const;
bool healthy() const override;
private:
static AP_Generator_RichenPower *_singleton;
// read - read serial port, return true if a new reading has been found
bool get_reading();
AP_HAL::UARTDriver *uart;
@ -219,9 +205,4 @@ private:
return AP_HAL::millis() - idle_state_start_ms;
}
};
namespace AP {
AP_Generator_RichenPower *generator();
};
#endif