2019-11-06 20:32:08 -04:00
|
|
|
// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
|
|
|
|
#pragma once
|
|
|
|
|
2020-10-22 14:02:15 -03:00
|
|
|
#include "AP_Generator_Backend.h"
|
|
|
|
|
2022-06-21 22:30:12 -03:00
|
|
|
#ifndef AP_GENERATOR_RICHENPOWER_ENABLED
|
|
|
|
#define AP_GENERATOR_RICHENPOWER_ENABLED 0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if AP_GENERATOR_RICHENPOWER_ENABLED
|
2020-10-22 14:02:15 -03:00
|
|
|
|
2019-11-06 20:32:08 -04:00
|
|
|
#include <AP_Common/AP_Common.h>
|
|
|
|
#include <SRV_Channel/SRV_Channel.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Example setup:
|
2020-06-29 04:18:20 -03:00
|
|
|
* param set SERIAL2_PROTOCOL 30 # Generator protocol
|
2019-11-06 20:32:08 -04:00
|
|
|
* param set SERIAL2_BAUD 9600
|
2020-06-29 04:18:20 -03:00
|
|
|
* param set RC9_OPTION 85 # pilot directive for generator
|
|
|
|
* param set SERVO8_FUNCTION 42 # autopilot directive to generator
|
2019-11-06 20:32:08 -04:00
|
|
|
*/
|
|
|
|
|
2020-10-22 14:02:15 -03:00
|
|
|
class AP_Generator_RichenPower : public AP_Generator_Backend
|
2019-11-06 20:32:08 -04:00
|
|
|
{
|
|
|
|
|
|
|
|
public:
|
2020-10-22 14:02:15 -03:00
|
|
|
// constructor
|
|
|
|
using AP_Generator_Backend::AP_Generator_Backend;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// init should be called at vehicle startup to get the generator library ready
|
2020-10-22 14:02:15 -03:00
|
|
|
void init(void) override;
|
|
|
|
// update should be called regularly to update the generator state
|
|
|
|
void update(void) override;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// methods to control the generator state:
|
2020-10-22 14:02:15 -03:00
|
|
|
bool stop(void) override;
|
|
|
|
bool idle(void) override;
|
|
|
|
bool run(void) override;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// method to send a GENERATOR_STATUS mavlink message
|
2020-10-22 14:02:15 -03:00
|
|
|
void send_generator_status(const GCS_MAVLINK &channel) override;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// prearm checks to ensure generator is good for arming. Note
|
|
|
|
// that if the generator has never sent us a message then these
|
|
|
|
// automatically pass!
|
2020-10-22 14:02:15 -03:00
|
|
|
bool pre_arm_check(char *failmsg, uint8_t failmsg_len) const override;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
2020-10-22 14:02:15 -03:00
|
|
|
// Update front end with voltage, current, and rpm values
|
|
|
|
void update_frontend_readings(void);
|
2019-11-06 20:32:08 -04:00
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// healthy returns true if the generator is not present, or it is
|
|
|
|
// present, providing telemetry and not indicating an errors.
|
2020-10-22 14:02:15 -03:00
|
|
|
bool healthy() const override;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
|
|
|
private:
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// read - read serial port, return true if a new reading has been found
|
2019-11-06 20:32:08 -04:00
|
|
|
bool get_reading();
|
2020-06-29 04:18:20 -03:00
|
|
|
AP_HAL::UARTDriver *uart;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// methods and state to record pilot desired runstate and actual runstate:
|
2019-11-06 20:32:08 -04:00
|
|
|
enum class RunState {
|
|
|
|
STOP = 17,
|
|
|
|
IDLE = 18,
|
|
|
|
RUN = 19,
|
|
|
|
};
|
2020-06-29 04:18:20 -03:00
|
|
|
RunState pilot_desired_runstate = RunState::STOP;
|
|
|
|
RunState commanded_runstate = RunState::STOP; // output is based on this
|
|
|
|
void set_pilot_desired_runstate(RunState newstate) {
|
2019-11-06 20:32:08 -04:00
|
|
|
// gcs().send_text(MAV_SEVERITY_INFO, "RichenPower: Moving to state (%u) from (%u)\n", (unsigned)newstate, (unsigned)runstate);
|
2020-06-29 04:18:20 -03:00
|
|
|
pilot_desired_runstate = newstate;
|
2019-11-06 20:32:08 -04:00
|
|
|
}
|
|
|
|
void update_runstate();
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// boolean so we can emit the RichenPower protocol version once
|
2019-11-06 20:32:08 -04:00
|
|
|
bool protocol_information_anounced;
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// reported mode from the generator:
|
2019-11-06 20:32:08 -04:00
|
|
|
enum class Mode {
|
|
|
|
IDLE = 0,
|
|
|
|
RUN = 1,
|
|
|
|
CHARGE = 2,
|
|
|
|
BALANCE = 3,
|
2020-06-29 04:18:20 -03:00
|
|
|
OFF = 4,
|
2019-11-06 20:32:08 -04:00
|
|
|
};
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// un-packed data from the generator:
|
2019-11-06 20:32:08 -04:00
|
|
|
struct Reading {
|
|
|
|
uint32_t runtime; //seconds
|
|
|
|
uint32_t seconds_until_maintenance;
|
|
|
|
uint16_t errors;
|
|
|
|
uint16_t rpm;
|
|
|
|
float output_voltage;
|
|
|
|
float output_current;
|
|
|
|
Mode mode;
|
|
|
|
};
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// method and state to write and entry to the onboard log:
|
2019-11-06 20:32:08 -04:00
|
|
|
void Log_Write();
|
2020-06-29 04:18:20 -03:00
|
|
|
uint32_t last_logged_reading_ms;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
|
|
|
struct Reading last_reading;
|
|
|
|
uint32_t last_reading_ms;
|
|
|
|
|
|
|
|
const uint8_t HEADER_MAGIC1 = 0xAA;
|
|
|
|
const uint8_t HEADER_MAGIC2 = 0x55;
|
|
|
|
|
|
|
|
const uint8_t FOOTER_MAGIC1 = 0x55;
|
|
|
|
const uint8_t FOOTER_MAGIC2 = 0xAA;
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// reported errors from the generator:
|
2019-11-06 20:32:08 -04:00
|
|
|
enum class Errors { // bitmask
|
|
|
|
MaintenanceRequired = 0,
|
|
|
|
StartDisabled = 1,
|
|
|
|
Overload = 2,
|
|
|
|
LowVoltageOutput = 3,
|
|
|
|
LowBatteryVoltage = 4,
|
|
|
|
LAST
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *error_strings[5] = {
|
|
|
|
"MaintenanceRequired",
|
|
|
|
"StartDisabled",
|
|
|
|
"Overload",
|
|
|
|
"LowVoltageOutput",
|
|
|
|
"LowBatteryVoltage",
|
|
|
|
};
|
|
|
|
static_assert(ARRAY_SIZE(error_strings) == (uint8_t)Errors::LAST,
|
|
|
|
"have error string for each error");
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// RichenPower data packet format:
|
2019-11-06 20:32:08 -04:00
|
|
|
struct PACKED RichenPacket {
|
|
|
|
uint8_t headermagic1;
|
|
|
|
uint8_t headermagic2;
|
|
|
|
uint16_t version;
|
|
|
|
uint8_t runtime_minutes;
|
2020-06-29 04:18:20 -03:00
|
|
|
uint8_t runtime_seconds;
|
2019-11-06 20:32:08 -04:00
|
|
|
uint16_t runtime_hours;
|
2020-06-29 04:18:20 -03:00
|
|
|
uint16_t seconds_until_maintenance_high;
|
|
|
|
uint16_t seconds_until_maintenance_low;
|
2019-11-06 20:32:08 -04:00
|
|
|
uint16_t errors;
|
|
|
|
uint16_t rpm;
|
|
|
|
uint16_t throttle;
|
|
|
|
uint16_t idle_throttle;
|
|
|
|
uint16_t output_voltage;
|
|
|
|
uint16_t output_current;
|
|
|
|
uint16_t dynamo_current;
|
|
|
|
uint8_t unknown1;
|
2020-06-29 04:18:20 -03:00
|
|
|
uint8_t mode;
|
2019-11-06 20:32:08 -04:00
|
|
|
uint8_t unknown6[38]; // "data"?!
|
|
|
|
uint16_t checksum;
|
|
|
|
uint8_t footermagic1;
|
|
|
|
uint8_t footermagic2;
|
|
|
|
};
|
2022-02-26 09:53:46 -04:00
|
|
|
assert_storage_size<RichenPacket, 70> _assert_storage_size_RichenPacket UNUSED_PRIVATE_MEMBER;
|
2019-11-06 20:32:08 -04:00
|
|
|
|
|
|
|
union RichenUnion {
|
|
|
|
uint8_t parse_buffer[70];
|
|
|
|
struct RichenPacket packet;
|
|
|
|
};
|
|
|
|
RichenUnion u;
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// number of bytes currently in the buffer
|
2019-11-06 20:32:08 -04:00
|
|
|
uint8_t body_length;
|
|
|
|
|
|
|
|
// move the expected header bytes into &buffer[0], adjusting
|
|
|
|
// body_length as appropriate.
|
|
|
|
void move_header_in_buffer(uint8_t initial_offset);
|
|
|
|
|
2020-06-29 04:18:20 -03:00
|
|
|
// a simple heat model to avoid the motor moving to run too fast
|
|
|
|
// or being stopped before cooldown. The generator itself does
|
|
|
|
// not supply temperature via telemetry, so we fake one based on
|
|
|
|
// RPM.
|
|
|
|
uint32_t last_heat_update_ms;
|
|
|
|
float heat;
|
|
|
|
void update_heat();
|
|
|
|
|
|
|
|
// returns true if the generator should be allowed to move into
|
|
|
|
// the "run" (high-RPM) state:
|
|
|
|
bool generator_ok_to_run() const;
|
|
|
|
// returns an amount of synthetic heat required for the generator
|
|
|
|
// to move into the "run" state:
|
|
|
|
static constexpr float heat_required_for_run();
|
|
|
|
|
|
|
|
// approximate run and idle speeds for the generator:
|
2019-11-06 20:32:08 -04:00
|
|
|
static const uint16_t RUN_RPM = 15000;
|
2020-06-29 04:18:20 -03:00
|
|
|
static const uint16_t IDLE_RPM = 6000;
|
|
|
|
|
|
|
|
static constexpr float heat_environment_loss_factor = 0.005f;
|
|
|
|
// powf is not constexpr, so we create a const for it:
|
|
|
|
// powf(1.0f-heat_environment_loss_factor, 30)
|
|
|
|
static constexpr float heat_environment_loss_30s = 0.860384;
|
|
|
|
static constexpr float heat_environment_loss_60s = 0.740261;
|
|
|
|
|
|
|
|
// boolean so we can announce we've stopped the generator due to a
|
|
|
|
// crash just once:
|
|
|
|
bool vehicle_was_crashed;
|
|
|
|
|
|
|
|
// data and methods to handle time-in-idle-state:
|
|
|
|
uint32_t idle_state_start_ms;
|
|
|
|
|
|
|
|
uint32_t time_in_idle_state_ms() const {
|
|
|
|
if (idle_state_start_ms == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return AP_HAL::millis() - idle_state_start_ms;
|
|
|
|
}
|
2022-05-03 21:06:57 -03:00
|
|
|
|
|
|
|
// check if the generator requires maintenance and send a message if it does:
|
|
|
|
void check_maintenance_required();
|
|
|
|
// if we are emitting warnings about the generator requiring
|
|
|
|
// maintenamce, this is the last time we sent the warning:
|
|
|
|
uint32_t last_maintenance_warning_ms;
|
2019-11-06 20:32:08 -04:00
|
|
|
};
|
|
|
|
#endif
|