mirror of https://github.com/ArduPilot/ardupilot
SITL: new Loweheiser generator/efi sim
Co-authored-by: Joshua Henderson <hendjoshsr71@gmail.com> SITL: break out common GeneratorEngine code
This commit is contained in:
parent
940b6fd1fb
commit
e8708227a1
|
@ -1014,6 +1014,13 @@ void Aircraft::update_external_payload(const struct sitl_input &input)
|
||||||
richenpower->update(input);
|
richenpower->update(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if AP_SIM_LOWEHEISER_ENABLED
|
||||||
|
// update Loweheiser generator
|
||||||
|
if (loweheiser) {
|
||||||
|
loweheiser->update();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (fetteconewireesc) {
|
if (fetteconewireesc) {
|
||||||
fetteconewireesc->update(*this);
|
fetteconewireesc->update(*this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "SIM_Parachute.h"
|
#include "SIM_Parachute.h"
|
||||||
#include "SIM_Precland.h"
|
#include "SIM_Precland.h"
|
||||||
#include "SIM_RichenPower.h"
|
#include "SIM_RichenPower.h"
|
||||||
|
#include "SIM_Loweheiser.h"
|
||||||
#include "SIM_FETtecOneWireESC.h"
|
#include "SIM_FETtecOneWireESC.h"
|
||||||
#include "SIM_I2C.h"
|
#include "SIM_I2C.h"
|
||||||
#include "SIM_Buzzer.h"
|
#include "SIM_Buzzer.h"
|
||||||
|
@ -146,6 +147,9 @@ public:
|
||||||
void set_parachute(Parachute *_parachute) { parachute = _parachute; }
|
void set_parachute(Parachute *_parachute) { parachute = _parachute; }
|
||||||
void set_richenpower(RichenPower *_richenpower) { richenpower = _richenpower; }
|
void set_richenpower(RichenPower *_richenpower) { richenpower = _richenpower; }
|
||||||
void set_adsb(class ADSB *_adsb) { adsb = _adsb; }
|
void set_adsb(class ADSB *_adsb) { adsb = _adsb; }
|
||||||
|
#if AP_SIM_LOWEHEISER_ENABLED
|
||||||
|
void set_loweheiser(Loweheiser *_loweheiser) { loweheiser = _loweheiser; }
|
||||||
|
#endif
|
||||||
void set_fetteconewireesc(FETtecOneWireESC *_fetteconewireesc) { fetteconewireesc = _fetteconewireesc; }
|
void set_fetteconewireesc(FETtecOneWireESC *_fetteconewireesc) { fetteconewireesc = _fetteconewireesc; }
|
||||||
void set_ie24(IntelligentEnergy24 *_ie24) { ie24 = _ie24; }
|
void set_ie24(IntelligentEnergy24 *_ie24) { ie24 = _ie24; }
|
||||||
void set_gripper_servo(Gripper_Servo *_gripper) { gripper = _gripper; }
|
void set_gripper_servo(Gripper_Servo *_gripper) { gripper = _gripper; }
|
||||||
|
@ -341,6 +345,9 @@ private:
|
||||||
Gripper_EPM *gripper_epm;
|
Gripper_EPM *gripper_epm;
|
||||||
Parachute *parachute;
|
Parachute *parachute;
|
||||||
RichenPower *richenpower;
|
RichenPower *richenpower;
|
||||||
|
#if AP_SIM_LOWEHEISER_ENABLED
|
||||||
|
Loweheiser *loweheiser;
|
||||||
|
#endif
|
||||||
FETtecOneWireESC *fetteconewireesc;
|
FETtecOneWireESC *fetteconewireesc;
|
||||||
|
|
||||||
IntelligentEnergy24 *ie24;
|
IntelligentEnergy24 *ie24;
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include "SIM_GeneratorEngine.h"
|
||||||
|
|
||||||
|
#include <AP_Math/AP_Math.h>
|
||||||
|
|
||||||
|
#include <GCS_MAVLink/GCS.h>
|
||||||
|
|
||||||
|
using namespace SITL;
|
||||||
|
|
||||||
|
void SIM_GeneratorEngine::update()
|
||||||
|
{
|
||||||
|
if (current_current > 1 && is_zero(desired_rpm)) {
|
||||||
|
AP_HAL::panic("Generator stalled due to high current demand (%u > 1)", (unsigned)current_current);
|
||||||
|
} else if (current_current > max_current) {
|
||||||
|
AP_HAL::panic("Generator stalled due to high current demand (run)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// linear degradation in RPM up to maximum load
|
||||||
|
if (!is_zero(desired_rpm)) {
|
||||||
|
desired_rpm -= 1500 * (current_current/max_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t now = AP_HAL::millis();
|
||||||
|
|
||||||
|
const float max_slew_rpm = max_slew_rpm_per_second * ((now - last_rpm_update_ms) / 1000.0f);
|
||||||
|
last_rpm_update_ms = now;
|
||||||
|
const float rpm_delta = current_rpm - desired_rpm;
|
||||||
|
if (rpm_delta > 0) {
|
||||||
|
current_rpm -= MIN(max_slew_rpm, rpm_delta);
|
||||||
|
} else {
|
||||||
|
current_rpm += MIN(max_slew_rpm, abs(rpm_delta));
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the temperature
|
||||||
|
const uint32_t time_delta_ms = now - last_heat_update_ms;
|
||||||
|
last_heat_update_ms = now;
|
||||||
|
|
||||||
|
constexpr float heat_environment_loss_factor = 0.15f;
|
||||||
|
|
||||||
|
const float factor = 0.0035;
|
||||||
|
temperature += (current_rpm * time_delta_ms * (1/1000.0f) * factor);
|
||||||
|
// cap the heat of the motor:
|
||||||
|
temperature = MIN(temperature, 150);
|
||||||
|
// now lose some heat to the environment
|
||||||
|
const float heat_loss = ((temperature * heat_environment_loss_factor * (time_delta_ms * (1/1000.0f)))); // lose some % of heat per second
|
||||||
|
// gcs().send_text(MAV_SEVERITY_INFO, "heat=%f loss=%f", temperature, heat_loss);
|
||||||
|
temperature -= heat_loss;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace SITL {
|
||||||
|
|
||||||
|
class SIM_GeneratorEngine
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// input variables:
|
||||||
|
float desired_rpm;
|
||||||
|
float current_current;
|
||||||
|
float max_current;
|
||||||
|
float max_slew_rpm_per_second;
|
||||||
|
float max_rpm = 8000;
|
||||||
|
|
||||||
|
// output variables:
|
||||||
|
float current_rpm;
|
||||||
|
float temperature = 20;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t last_rpm_update_ms;
|
||||||
|
uint32_t last_heat_update_ms;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // namespace SITL
|
|
@ -0,0 +1,344 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Simulator for the Loweheiser generators
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SIM_config.h"
|
||||||
|
|
||||||
|
#if AP_SIM_LOWEHEISER_ENABLED
|
||||||
|
|
||||||
|
#include <AP_Math/AP_Math.h>
|
||||||
|
|
||||||
|
#include "SIM_Loweheiser.h"
|
||||||
|
#include "SITL.h"
|
||||||
|
|
||||||
|
#include <GCS_MAVLink/GCS.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
using namespace SITL;
|
||||||
|
|
||||||
|
Loweheiser::Loweheiser() : SerialDevice::SerialDevice()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loweheiser::update()
|
||||||
|
{
|
||||||
|
// if (!_enabled.get()) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
maybe_send_heartbeat();
|
||||||
|
update_receive();
|
||||||
|
update_send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loweheiser::maybe_send_heartbeat()
|
||||||
|
{
|
||||||
|
const uint32_t now = AP_HAL::millis();
|
||||||
|
|
||||||
|
if (now - last_heartbeat_ms < 100) {
|
||||||
|
// we only provide a heartbeat every so often
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_heartbeat_ms = now;
|
||||||
|
|
||||||
|
mavlink_message_t msg;
|
||||||
|
mavlink_msg_heartbeat_pack(system_id,
|
||||||
|
component_id,
|
||||||
|
&msg,
|
||||||
|
MAV_TYPE_GCS,
|
||||||
|
MAV_AUTOPILOT_INVALID,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
|
uint8_t buf[300];
|
||||||
|
uint16_t buf_len = mavlink_msg_to_send_buffer(buf, &msg);
|
||||||
|
|
||||||
|
if (write_to_autopilot((const char*)&buf, buf_len) != buf_len) {
|
||||||
|
// ::fprintf(stderr, "write failure\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loweheiser::handle_message(const mavlink_message_t &msg)
|
||||||
|
{
|
||||||
|
switch (msg.msgid) {
|
||||||
|
case MAVLINK_MSG_ID_COMMAND_LONG: {
|
||||||
|
mavlink_command_long_t pkt;
|
||||||
|
mavlink_msg_command_long_decode(&msg, &pkt);
|
||||||
|
|
||||||
|
if (pkt.target_system != system_id ||
|
||||||
|
pkt.target_component != component_id) {
|
||||||
|
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Not for me");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (pkt.command) {
|
||||||
|
case MAV_CMD_LOWEHEISER_SET_STATE:
|
||||||
|
// decode the desired run state:
|
||||||
|
// param2 physically turns power on/off to the EFI!
|
||||||
|
switch ((uint8_t)pkt.param2) {
|
||||||
|
case 0:
|
||||||
|
autopilot_desired_engine_run_state = EngineRunState::OFF;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
autopilot_desired_engine_run_state = EngineRunState::ON;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AP_HAL::panic("Bad desired engine run state");
|
||||||
|
}
|
||||||
|
switch ((uint8_t)pkt.param3) {
|
||||||
|
case 0:
|
||||||
|
autopilot_desired_governor_state = GovernorState::OFF;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
autopilot_desired_governor_state = GovernorState::ON;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AP_HAL::panic("Bad desired governor state");
|
||||||
|
}
|
||||||
|
switch ((uint8_t)pkt.param5) {
|
||||||
|
case 0:
|
||||||
|
autopilot_desired_startup_state = StartupState::OFF;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
autopilot_desired_startup_state = StartupState::ON;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AP_HAL::panic("Bad electronic startup state");
|
||||||
|
}
|
||||||
|
manual_throttle_pct = pkt.param4;
|
||||||
|
mavlink_message_t ack;
|
||||||
|
mavlink_msg_command_ack_pack(
|
||||||
|
system_id,
|
||||||
|
component_id,
|
||||||
|
&ack,
|
||||||
|
MAV_CMD_LOWEHEISER_SET_STATE,
|
||||||
|
MAV_RESULT_ACCEPTED,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
msg.sysid,
|
||||||
|
msg.compid
|
||||||
|
);
|
||||||
|
uint8_t buf[300];
|
||||||
|
uint16_t buf_len = mavlink_msg_to_send_buffer(buf, &ack);
|
||||||
|
|
||||||
|
if (write_to_autopilot((const char*)&buf, buf_len) != buf_len) {
|
||||||
|
// ::fprintf(stderr, "write failure\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loweheiser::update_receive()
|
||||||
|
{
|
||||||
|
char buf[128];
|
||||||
|
const ssize_t bytes_read = read_from_autopilot(buf, sizeof(buf));
|
||||||
|
if (bytes_read <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (uint16_t i=0; i<bytes_read; i++) {
|
||||||
|
if (mavlink_frame_char_buffer(
|
||||||
|
&rxmsg,
|
||||||
|
&rxstatus,
|
||||||
|
buf[i],
|
||||||
|
&rxmsg,
|
||||||
|
&rxstatus) == MAVLINK_FRAMING_OK) {
|
||||||
|
handle_message(rxmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loweheiser::update_fuel_level()
|
||||||
|
{
|
||||||
|
const uint32_t now_ms = AP_HAL::millis();
|
||||||
|
|
||||||
|
const uint32_t tdelta = now_ms - last_fuel_update_ms;
|
||||||
|
last_fuel_update_ms = now_ms;
|
||||||
|
|
||||||
|
const float fuel_consumption_lperh_at_8000rpm = 10;
|
||||||
|
const float fuel_consumption_lpers_at_8000rpm = fuel_consumption_lperh_at_8000rpm / 3600;
|
||||||
|
|
||||||
|
fuel_flow_lps = generatorengine.current_rpm/8000 * fuel_consumption_lpers_at_8000rpm;
|
||||||
|
const float fuel_delta = tdelta/1000.0 * fuel_flow_lps; // litres
|
||||||
|
fuel_consumed += fuel_delta;
|
||||||
|
fuel_level -= fuel_delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Loweheiser::update_send()
|
||||||
|
{
|
||||||
|
const uint32_t now = AP_HAL::millis();
|
||||||
|
if (now - last_sent_ms < 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last_sent_ms = now;
|
||||||
|
|
||||||
|
auto sitl = AP::sitl();
|
||||||
|
if (!sitl || sitl->efi_type == SIM::EFIType::EFI_TYPE_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float throttle = 0;
|
||||||
|
float throttle_output = 0;
|
||||||
|
|
||||||
|
switch (autopilot_desired_engine_run_state) {
|
||||||
|
case EngineRunState::OFF:
|
||||||
|
generatorengine.desired_rpm = 0;
|
||||||
|
break;
|
||||||
|
case EngineRunState::ON:
|
||||||
|
switch (autopilot_desired_governor_state) {
|
||||||
|
case GovernorState::OFF: {
|
||||||
|
throttle = manual_throttle_pct;
|
||||||
|
throttle_output = throttle;
|
||||||
|
if (is_positive(generatorengine.desired_rpm) ||
|
||||||
|
autopilot_desired_startup_state == StartupState::ON) {
|
||||||
|
const uint16_t manual_rpm_min = 0;
|
||||||
|
const uint16_t manual_rpm_max = 8000;
|
||||||
|
generatorengine.desired_rpm = linear_interpolate(
|
||||||
|
manual_rpm_min,
|
||||||
|
manual_rpm_max,
|
||||||
|
throttle,
|
||||||
|
0,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GovernorState::ON:
|
||||||
|
// should probably base this on current draw from battery
|
||||||
|
// / motor throttle output?
|
||||||
|
throttle = 80;
|
||||||
|
throttle_output = 80;
|
||||||
|
generatorengine.desired_rpm = 8000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a simulation for a stuck throttle - once the egine starts it
|
||||||
|
// won't stop based on servo position,, only engine run state
|
||||||
|
const bool stuck_throttle_failure_simulation = false;
|
||||||
|
static bool throttle_is_stuck;
|
||||||
|
if (stuck_throttle_failure_simulation) {
|
||||||
|
if (generatorengine.desired_rpm > 7000) {
|
||||||
|
throttle_is_stuck = true;
|
||||||
|
}
|
||||||
|
// if the throttle is stuck then the engine runs - except if
|
||||||
|
// the autopilot is saying the desired runstate is off,
|
||||||
|
// because that just shuts down the spark entirely, so the
|
||||||
|
// engine will not run
|
||||||
|
if (throttle_is_stuck &&
|
||||||
|
autopilot_desired_engine_run_state == EngineRunState::ON) {
|
||||||
|
// same numbers as "on" case, above
|
||||||
|
throttle = 80;
|
||||||
|
throttle_output = 80;
|
||||||
|
generatorengine.desired_rpm = 8000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_current_current = AP::sitl()->state.battery_current;
|
||||||
|
_current_current = MIN(_current_current, max_current);
|
||||||
|
|
||||||
|
generatorengine.current_current = _current_current;
|
||||||
|
generatorengine.max_current = max_current;
|
||||||
|
generatorengine.max_slew_rpm_per_second = 2000;
|
||||||
|
|
||||||
|
generatorengine.update();
|
||||||
|
|
||||||
|
update_fuel_level();
|
||||||
|
|
||||||
|
float efi_pw = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_fuel_flow = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_fuel_consumed = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_fuel_level = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_baro = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_mat = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_clt = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_tps = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_egt = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
float efi_batt = std::numeric_limits<float>::quiet_NaN();
|
||||||
|
|
||||||
|
float curr_batt = -0.3;
|
||||||
|
float curr_gen = 10.12;
|
||||||
|
|
||||||
|
// Current from the generator is the battery charging current (defined to be negative) plus the generator current
|
||||||
|
float curr_rot = (curr_batt < 0 ? -curr_batt : 0.0) + curr_gen;
|
||||||
|
|
||||||
|
// controlled by param2, this turns on/off the DC/DC component which
|
||||||
|
// powers the efi
|
||||||
|
if (autopilot_desired_engine_run_state == EngineRunState::ON) {
|
||||||
|
efi_baro = AP::baro().get_pressure() / 1000.0;
|
||||||
|
efi_mat = AP::baro().get_temperature();
|
||||||
|
efi_clt = generatorengine.temperature;
|
||||||
|
efi_tps = MAX(throttle_output, 40);
|
||||||
|
efi_batt = 12.5;
|
||||||
|
efi_fuel_flow = fuel_flow_lps * 3600; // litres/second -> litres/hour
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false) {
|
||||||
|
efi_fuel_level = fuel_level;
|
||||||
|
efi_fuel_consumed = fuel_consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// +/- 3V, depending on draw
|
||||||
|
const float volt_batt = base_supply_voltage - (3 * (_current_current / max_current));
|
||||||
|
|
||||||
|
const mavlink_loweheiser_gov_efi_t loweheiser_efi_gov{
|
||||||
|
volt_batt, // volt_batt
|
||||||
|
curr_batt, // curr_batt
|
||||||
|
curr_gen, // curr_gen
|
||||||
|
curr_rot, // curr_rot
|
||||||
|
efi_fuel_level, // fuel_level in litres
|
||||||
|
throttle, // throttle
|
||||||
|
UINT32_MAX, // runtime in seconds
|
||||||
|
INT32_MAX, // time until maintenance
|
||||||
|
std::numeric_limits<float>::quiet_NaN(), // rectifier temperature
|
||||||
|
std::numeric_limits<float>::quiet_NaN(), // generator temperature
|
||||||
|
efi_batt, // efi_batt
|
||||||
|
generatorengine.current_rpm, // efi_rpm
|
||||||
|
efi_pw, // efi_pw
|
||||||
|
efi_fuel_flow, // efi_fuelflow
|
||||||
|
efi_fuel_consumed, // efi_fuel_consumed
|
||||||
|
efi_baro, // efi_baro
|
||||||
|
efi_mat, // efi_mat
|
||||||
|
efi_clt, // efi_clt
|
||||||
|
efi_tps, // efi_tps
|
||||||
|
efi_egt, // efi_exhaust_gas_temperature
|
||||||
|
1, // EFI index
|
||||||
|
0, // generator_status
|
||||||
|
0 // EFI status
|
||||||
|
};
|
||||||
|
|
||||||
|
mavlink_message_t msg;
|
||||||
|
mavlink_msg_loweheiser_gov_efi_encode_status(
|
||||||
|
system_id,
|
||||||
|
component_id,
|
||||||
|
&mav_status,
|
||||||
|
&msg,
|
||||||
|
&loweheiser_efi_gov
|
||||||
|
);
|
||||||
|
|
||||||
|
uint8_t buf[300];
|
||||||
|
uint16_t buf_len = mavlink_msg_to_send_buffer(buf, &msg);
|
||||||
|
|
||||||
|
if (write_to_autopilot((char*)buf, buf_len) != buf_len) {
|
||||||
|
AP_HAL::panic("Failed to write to autopilot: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // AP_SIM_LOWEHEISER_ENABLED
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Simulator for the Loweheiser EFI/generator
|
||||||
|
|
||||||
|
./Tools/autotest/sim_vehicle.py --gdb --debug -v ArduCopter -A --uartF=sim:loweheiser --speedup=1 --console
|
||||||
|
|
||||||
|
param set SIM_EFI_TYPE 2
|
||||||
|
param set SERIAL5_PROTOCOL 2
|
||||||
|
param set GEN_TYPE 4
|
||||||
|
param set EFI_TYPE 4
|
||||||
|
param set BATT2_MONITOR 17 # generator (elec)
|
||||||
|
param set BATT3_MONITOR 18 # generator (fuel level)
|
||||||
|
param fetch
|
||||||
|
|
||||||
|
param set BATT3_CAPACITY 10000
|
||||||
|
param set BATT3_LOW_MAH 1000
|
||||||
|
param set BATT3_CRT_MAH 500
|
||||||
|
param set BATT3_FS_LOW_ACT 2 # RTL
|
||||||
|
param set BATT3_FS_CRT_ACT 1 # LAND
|
||||||
|
param set BATT3_LOW_VOLT 0
|
||||||
|
|
||||||
|
param set RC9_OPTION 85 # generator control
|
||||||
|
|
||||||
|
param set GEN_IDLE_TH_H 40 # NOTE without this the engine never warms up past 36 deg C
|
||||||
|
param set GEN_IDLE_TH 25
|
||||||
|
|
||||||
|
param set RC10_OPTION 212 # loweheiser manual throttle control
|
||||||
|
param set RC10_DZ 20
|
||||||
|
param set RC10_TRIM 1000
|
||||||
|
param set RC10_MIN 1000
|
||||||
|
param set RC10_MAX 2000
|
||||||
|
|
||||||
|
param set RC11_OPTION 109 # loweheiser starter channel
|
||||||
|
|
||||||
|
reboot
|
||||||
|
|
||||||
|
# for testing failsafes:
|
||||||
|
param set BATT3_CAPACITY 200
|
||||||
|
param set BATT3_LOW_MAH 100
|
||||||
|
param set BATT3_CRT_MAH 50
|
||||||
|
|
||||||
|
# stream EFI_STATUS at 10Hz:
|
||||||
|
long SET_MESSAGE_INTERVAL 225 100000
|
||||||
|
|
||||||
|
# run SITL against real generator:
|
||||||
|
DEV=/dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0
|
||||||
|
./Tools/autotest/sim_vehicle.py --gdb --debug -v ArduCopter -A --uartF=uart:$DEV:115200 --speedup=1 --console
|
||||||
|
|
||||||
|
# run generator test script against simulator:
|
||||||
|
python ./libraries/AP_Generator/scripts/test-loweheiser.py tcp:localhost:5762
|
||||||
|
|
||||||
|
# use the generator test script to control the generator:
|
||||||
|
./libraries/AP_Generator/scripts/test-loweheiser.py $DEV
|
||||||
|
|
||||||
|
# observe RPM
|
||||||
|
|
||||||
|
# observe remaining fuel:
|
||||||
|
graph BATTERY_STATUS[2].battery_remaining
|
||||||
|
graph BATTERY_STATUS[2].current_consumed
|
||||||
|
|
||||||
|
# autotest suite:
|
||||||
|
|
||||||
|
./Tools/autotest/autotest.py --gdb --debug build.Copter test.Copter.Loweheiser
|
||||||
|
|
||||||
|
# use a usb-ttl cable to connect directly to mavlink-speaking generator:
|
||||||
|
DEV=/dev/serial/by-id/usb-Prolific_Technology_Inc._USB-Serial_Controller-if00-port0
|
||||||
|
mavproxy.py --master $DEV --baud 115200
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SIM_config.h"
|
||||||
|
|
||||||
|
#if AP_SIM_LOWEHEISER_ENABLED
|
||||||
|
|
||||||
|
#include <AP_Param/AP_Param.h>
|
||||||
|
|
||||||
|
#include "SITL_Input.h"
|
||||||
|
|
||||||
|
#include "SIM_SerialDevice.h"
|
||||||
|
#include "SIM_GeneratorEngine.h"
|
||||||
|
|
||||||
|
#include <GCS_MAVLink/GCS_MAVLink.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
namespace SITL {
|
||||||
|
|
||||||
|
class Loweheiser : public SerialDevice {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Loweheiser();
|
||||||
|
|
||||||
|
// update state
|
||||||
|
void update();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// TODO: make these parameters:
|
||||||
|
const uint8_t system_id = 17;
|
||||||
|
const uint8_t component_id = 18;
|
||||||
|
|
||||||
|
const float max_current = 50.0f;
|
||||||
|
const float base_supply_voltage = 50.0;
|
||||||
|
|
||||||
|
// we share channels with the ArduPilot binary!
|
||||||
|
// Beware: the mavlink rangefinder and other stuff shares this channel.
|
||||||
|
const mavlink_channel_t mavlink_ch = (mavlink_channel_t)(MAVLINK_COMM_0+5);
|
||||||
|
|
||||||
|
class SIM *_sitl;
|
||||||
|
|
||||||
|
uint32_t last_sent_ms;
|
||||||
|
|
||||||
|
void update_receive();
|
||||||
|
void update_send();
|
||||||
|
|
||||||
|
void maybe_send_heartbeat();
|
||||||
|
uint32_t last_heartbeat_ms;
|
||||||
|
|
||||||
|
void handle_message(const mavlink_message_t &msg);
|
||||||
|
|
||||||
|
enum class EngineRunState : uint8_t {
|
||||||
|
OFF = 0,
|
||||||
|
ON = 1,
|
||||||
|
};
|
||||||
|
EngineRunState autopilot_desired_engine_run_state = EngineRunState::OFF;
|
||||||
|
|
||||||
|
enum class GovernorState : uint8_t {
|
||||||
|
OFF = 0,
|
||||||
|
ON = 1,
|
||||||
|
};
|
||||||
|
GovernorState autopilot_desired_governor_state = GovernorState::OFF;
|
||||||
|
|
||||||
|
float manual_throttle_pct;
|
||||||
|
|
||||||
|
enum class StartupState : uint8_t {
|
||||||
|
OFF = 0,
|
||||||
|
ON = 1,
|
||||||
|
};
|
||||||
|
StartupState autopilot_desired_startup_state = StartupState::OFF;
|
||||||
|
|
||||||
|
mavlink_message_t rxmsg;
|
||||||
|
mavlink_status_t rxstatus;
|
||||||
|
|
||||||
|
SIM_GeneratorEngine generatorengine;
|
||||||
|
|
||||||
|
float _current_current;
|
||||||
|
|
||||||
|
// fuel
|
||||||
|
const float initial_fuel_level = 10; // litres, must match battery setup
|
||||||
|
float fuel_level = initial_fuel_level; // litres
|
||||||
|
float fuel_consumed = 0; // litres
|
||||||
|
float fuel_flow_lps = 0; // litres/second
|
||||||
|
void update_fuel_level();
|
||||||
|
|
||||||
|
uint32_t last_fuel_update_ms;
|
||||||
|
|
||||||
|
mavlink_status_t mav_status;
|
||||||
|
|
||||||
|
// parameters
|
||||||
|
// AP_Int8 _enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // AP_SIM_LOWEHEISER_ENABLED
|
|
@ -128,47 +128,29 @@ void RichenPower::update_control_pin(const struct sitl_input &input)
|
||||||
// RICHENPOWER, 13:49
|
// RICHENPOWER, 13:49
|
||||||
// Idle RMP 4800 +-300, RUN RPM 13000 +- 1500
|
// Idle RMP 4800 +-300, RUN RPM 13000 +- 1500
|
||||||
|
|
||||||
uint16_t desired_rpm = 0;
|
|
||||||
switch (_state) {
|
switch (_state) {
|
||||||
case State::STOP:
|
case State::STOP:
|
||||||
desired_rpm = 0;
|
generatorengine.desired_rpm = 0;
|
||||||
break;
|
break;
|
||||||
case State::IDLE:
|
case State::IDLE:
|
||||||
case State::STOPPING:
|
case State::STOPPING:
|
||||||
desired_rpm = 4800; // +/- 300
|
generatorengine.desired_rpm = 4800; // +/- 300
|
||||||
break;
|
break;
|
||||||
case State::RUN:
|
case State::RUN:
|
||||||
desired_rpm = 13000; // +/- 1500
|
generatorengine.desired_rpm = 13000; // +/- 1500
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
_current_current = AP::sitl()->state.battery_current;
|
_current_current = AP::sitl()->state.battery_current;
|
||||||
_current_current = MIN(_current_current, max_current);
|
_current_current = MIN(_current_current, max_current);
|
||||||
if (_current_current > 1 && _state != State::RUN) {
|
|
||||||
AP_HAL::panic("Generator stalled due to high current demand");
|
|
||||||
} else if (_current_current > max_current) {
|
|
||||||
AP_HAL::panic("Generator stalled due to high current demand (run)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// linear degradation in RPM up to maximum load
|
generatorengine.current_current = _current_current;
|
||||||
if (desired_rpm) {
|
generatorengine.max_current = max_current;
|
||||||
desired_rpm -= 1500 * (_current_current/max_current);
|
generatorengine.max_slew_rpm_per_second = 2000;
|
||||||
}
|
|
||||||
|
|
||||||
const float max_slew_rpm_per_second = 2000;
|
generatorengine.update();
|
||||||
const float max_slew_rpm = max_slew_rpm_per_second * ((now - last_rpm_update_ms) / 1000.0f);
|
|
||||||
last_rpm_update_ms = now;
|
|
||||||
const float rpm_delta = _current_rpm - desired_rpm;
|
|
||||||
if (rpm_delta > 0) {
|
|
||||||
_current_rpm -= MIN(max_slew_rpm, rpm_delta);
|
|
||||||
} else {
|
|
||||||
_current_rpm += MIN(max_slew_rpm, abs(rpm_delta));
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (!is_zero(rpm_delta)) {
|
|
||||||
// ::fprintf(stderr, "richenpower pwm: %f\n", _current_rpm);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
_current_rpm = generatorengine.current_rpm;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RichenPower::RichenUnion::update_checksum()
|
void RichenPower::RichenUnion::update_checksum()
|
||||||
|
|
|
@ -45,6 +45,7 @@ arm throttle (denied because generator not running)
|
||||||
#include "SITL_Input.h"
|
#include "SITL_Input.h"
|
||||||
|
|
||||||
#include "SIM_SerialDevice.h"
|
#include "SIM_SerialDevice.h"
|
||||||
|
#include "SIM_GeneratorEngine.h"
|
||||||
|
|
||||||
namespace SITL {
|
namespace SITL {
|
||||||
|
|
||||||
|
@ -94,8 +95,6 @@ private:
|
||||||
|
|
||||||
float _current_current;
|
float _current_current;
|
||||||
|
|
||||||
uint32_t last_rpm_update_ms;
|
|
||||||
|
|
||||||
enum class Errors {
|
enum class Errors {
|
||||||
MaintenanceRequired = 0,
|
MaintenanceRequired = 0,
|
||||||
};
|
};
|
||||||
|
@ -136,6 +135,8 @@ private:
|
||||||
|
|
||||||
// time we were asked to stop;
|
// time we were asked to stop;
|
||||||
uint32_t stop_start_ms;
|
uint32_t stop_start_ms;
|
||||||
|
|
||||||
|
SIM_GeneratorEngine generatorengine;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <AP_HAL/AP_HAL_Boards.h>
|
#include <AP_HAL/AP_HAL_Boards.h>
|
||||||
|
#include <GCS_MAVLink/GCS_config.h>
|
||||||
|
|
||||||
#ifndef HAL_SIM_ADSB_ENABLED
|
#ifndef HAL_SIM_ADSB_ENABLED
|
||||||
#define HAL_SIM_ADSB_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
|
#define HAL_SIM_ADSB_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
|
||||||
|
@ -22,6 +23,10 @@
|
||||||
#define AP_SIM_IS31FL3195_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
|
#define AP_SIM_IS31FL3195_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef AP_SIM_LOWEHEISER_ENABLED
|
||||||
|
#define AP_SIM_LOWEHEISER_ENABLED AP_SIM_ENABLED && HAL_MAVLINK_BINDINGS_ENABLED
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef AP_SIM_SHIP_ENABLED
|
#ifndef AP_SIM_SHIP_ENABLED
|
||||||
#define AP_SIM_SHIP_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
|
#define AP_SIM_SHIP_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "SIM_ToneAlarm.h"
|
#include "SIM_ToneAlarm.h"
|
||||||
#include "SIM_EFI_MegaSquirt.h"
|
#include "SIM_EFI_MegaSquirt.h"
|
||||||
#include "SIM_RichenPower.h"
|
#include "SIM_RichenPower.h"
|
||||||
|
#include "SIM_Loweheiser.h"
|
||||||
#include "SIM_FETtecOneWireESC.h"
|
#include "SIM_FETtecOneWireESC.h"
|
||||||
#include "SIM_IntelligentEnergy24.h"
|
#include "SIM_IntelligentEnergy24.h"
|
||||||
#include "SIM_Ship.h"
|
#include "SIM_Ship.h"
|
||||||
|
@ -298,6 +299,7 @@ public:
|
||||||
enum EFIType {
|
enum EFIType {
|
||||||
EFI_TYPE_NONE = 0,
|
EFI_TYPE_NONE = 0,
|
||||||
EFI_TYPE_MS = 1,
|
EFI_TYPE_MS = 1,
|
||||||
|
EFI_TYPE_LOWEHEISER = 2,
|
||||||
EFI_TYPE_HIRTH = 8,
|
EFI_TYPE_HIRTH = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -455,6 +457,9 @@ public:
|
||||||
ToneAlarm tonealarm_sim;
|
ToneAlarm tonealarm_sim;
|
||||||
SIM_Precland precland_sim;
|
SIM_Precland precland_sim;
|
||||||
RichenPower richenpower_sim;
|
RichenPower richenpower_sim;
|
||||||
|
#if AP_SIM_LOWEHEISER_ENABLED
|
||||||
|
Loweheiser loweheiser_sim;
|
||||||
|
#endif
|
||||||
IntelligentEnergy24 ie24_sim;
|
IntelligentEnergy24 ie24_sim;
|
||||||
FETtecOneWireESC fetteconewireesc_sim;
|
FETtecOneWireESC fetteconewireesc_sim;
|
||||||
#if AP_TEST_DRONECAN_DRIVERS
|
#if AP_TEST_DRONECAN_DRIVERS
|
||||||
|
|
Loading…
Reference in New Issue