LCOV - code coverage report
Current view: top level - SITL - SIM_RichenPower.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 102 105 97.1 %
Date: 2026-09-09 11:02:08 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : /*
      16             :   Simulator for the RichenPower Hybrid generators
      17             : */
      18             : 
      19             : #include <AP_Math/AP_Math.h>
      20             : 
      21             : #include "SIM_RichenPower.h"
      22             : #include "SITL.h"
      23             : #include <AP_HAL/utility/sparse-endian.h>
      24             : 
      25             : #include <stdio.h>
      26             : #include <errno.h>
      27             : 
      28             : using namespace SITL;
      29             : 
      30             : extern const AP_HAL::HAL& hal;
      31             : 
      32             : // table of user settable parameters
      33             : const AP_Param::GroupInfo RichenPower::var_info[] = {
      34             : 
      35             :     // @Param: ENABLE
      36             :     // @DisplayName: RichenPower Generator sim enable/disable
      37             :     // @Description: Allows you to enable (1) or disable (0) the RichenPower simulator
      38             :     // @Values: 0:Disabled,1:Enabled
      39             :     // @User: Advanced
      40             :     AP_GROUPINFO("ENABLE", 0, RichenPower, _enabled, 0),
      41             : 
      42             :     // @Param: CTRL
      43             :     // @DisplayName: Pin RichenPower is connectred to
      44             :     // @Description: The pin number that the RichenPower spinner servo is connected to. (start at 1)
      45             :     // @Range: 0 15
      46             :     // @User: Advanced
      47             :     AP_GROUPINFO("CTRL", 2, RichenPower, _ctrl_pin, -1),
      48             : 
      49             :     AP_GROUPEND
      50             : };
      51             : 
      52        2096 : RichenPower::RichenPower() : SerialDevice::SerialDevice()
      53             : {
      54             :     ASSERT_STORAGE_SIZE(RichenPacket, 70);
      55             : 
      56        2096 :     AP_Param::setup_object_defaults(this, var_info);
      57             : 
      58        2096 :     u.packet.magic1 = 0xAA;
      59        2096 :     u.packet.magic2 = 0x55;
      60             : 
      61        2096 :     u.packet.version_major = 0x0A;
      62        2096 :     u.packet.version_minor = 0x00;
      63             : 
      64        2096 :     u.packet.footermagic1 = 0x55;
      65        2096 :     u.packet.footermagic2 = 0xAA;
      66        2096 : }
      67             : 
      68           6 : void RichenPower::set_run_state(State newstate) {
      69           6 :     hal.console->printf("Moving to state %u from %u\n", (unsigned)newstate, (unsigned)_state);
      70           6 :     _state = newstate;
      71           6 : }
      72             : 
      73      113479 : void RichenPower::update(const struct sitl_input &input)
      74             : {
      75      113479 :     if (!_enabled.get()) {
      76        1468 :         return;
      77             :     }
      78      112011 :     update_control_pin(input);
      79      112011 :     update_send();
      80             : }
      81             : 
      82      112011 : void RichenPower::update_control_pin(const struct sitl_input &input)
      83             : {
      84      112011 :     const uint32_t now = AP_HAL::millis();
      85             : 
      86             :     static const uint16_t INPUT_SERVO_PWM_STOP = 1200;
      87             :     static const uint16_t INPUT_SERVO_PWM_IDLE = 1500;
      88             :     // static const uint16_t INPUT_SERVO_PWM_RUN = 1900;
      89             : 
      90             :     // RICHENPOWER, 13:47
      91             :     // 1100~1300 for engine stop, 1300~1800 for idle, and 1800~2000 for run
      92             : 
      93      112011 :     const uint16_t control_pwm = _ctrl_pin >= 1 ? input.servos[_ctrl_pin-1] : -1;
      94      112011 :     if (_state != State::STOPPING) {
      95       76477 :         if (control_pwm <= INPUT_SERVO_PWM_STOP && _state != State::STOP) {
      96           1 :             if (stop_start_ms == 0) {
      97           1 :                 stop_start_ms = now;
      98             :             }
      99             :         } else {
     100       76476 :             stop_start_ms = 0;
     101             :         }
     102             :     }
     103             :     // ::fprintf(stderr, "stop_start_ms=%u\n", stop_start_ms);
     104             :     State newstate;
     105      112011 :     if (control_pwm <= INPUT_SERVO_PWM_STOP) {
     106             :         // stop
     107       72668 :         if (stop_start_ms == 0 || now - stop_start_ms > 30000) {
     108       37134 :             newstate = State::STOP;
     109             :         } else {
     110       35534 :             newstate = State::STOPPING;
     111             :         }
     112       39343 :     } else if (control_pwm <= INPUT_SERVO_PWM_IDLE) {
     113       33974 :         newstate = State::IDLE;
     114             :     } else {
     115        5369 :         newstate = State::RUN;
     116             :     }
     117      112011 :     if (newstate != _state) {
     118           6 :         set_run_state(newstate);
     119             :     } else {
     120      112005 :         if (_state == State::STOP) {
     121             :             // pass
     122             :         } else {
     123       74871 :             _runtime_ms += now - _last_runtime_ms;
     124             :         }
     125      112005 :         _last_runtime_ms = now;
     126             :     }
     127             : 
     128             :     // RICHENPOWER, 13:49
     129             :     // Idle RMP 4800 +-300, RUN RPM 13000 +- 1500
     130             : 
     131      112011 :     switch (_state) {
     132       37134 :     case State::STOP:
     133       37134 :         generatorengine.desired_rpm = 0;
     134       37134 :         break;
     135       69508 :     case State::IDLE:
     136             :     case State::STOPPING:
     137       69508 :         generatorengine.desired_rpm = 4800; // +/- 300
     138       69508 :         break;
     139        5369 :     case State::RUN:
     140        5369 :         generatorengine.desired_rpm = 13000; // +/- 1500
     141        5369 :         break;
     142             :     }
     143             : 
     144      112011 :     _current_current = AP::sitl()->state.battery_current;
     145      112011 :     _current_current = MIN(_current_current, max_current);
     146             : 
     147      112011 :     generatorengine.current_current = _current_current;
     148      112011 :     generatorengine.max_current = max_current;
     149      112011 :     generatorengine.max_slew_rpm_per_second = 2000;
     150             : 
     151      112011 :     generatorengine.update();
     152             : 
     153      112011 :     _current_rpm = generatorengine.current_rpm;
     154      112011 : }
     155             : 
     156          93 : void RichenPower::RichenUnion::update_checksum()
     157             : {
     158          93 :     packet.checksum = 0;
     159         558 :     for (uint8_t i=1; i<6; i++) {
     160         465 :         packet.checksum += htobe16(checksum_buffer[i]);
     161             :     }
     162          93 :     packet.checksum = htobe16(packet.checksum);
     163          93 : }
     164             : 
     165      112011 : void RichenPower::update_send()
     166             : {
     167             :     // just send a chunk of data at 1Hz:
     168      112011 :     const uint32_t now = AP_HAL::millis();
     169      112011 :     if (now - last_sent_ms < 1000) {
     170      111918 :         return;
     171             :     }
     172          93 :     last_sent_ms = now;
     173             : 
     174          93 :     u.packet.rpm = htobe16(_current_rpm);
     175          93 :     uint32_t runtime_seconds_remainder = _runtime_ms / 1000;
     176          93 :     u.packet.runtime_hours = runtime_seconds_remainder / 3600;
     177          93 :     runtime_seconds_remainder %= 3600;
     178          93 :     u.packet.runtime_minutes = runtime_seconds_remainder / 60;
     179          93 :     runtime_seconds_remainder %= 60;
     180          93 :     u.packet.runtime_seconds = runtime_seconds_remainder;
     181             : 
     182          93 :     u.packet.runtime_seconds = runtime_seconds_remainder;
     183             : 
     184          93 :     const int32_t seconds_until_maintenance = (original_seconds_until_maintenance - _runtime_ms/1000.0f);
     185          93 :     uint16_t errors = htobe16(u.packet.errors);
     186          93 :     if (seconds_until_maintenance <= 0) {
     187           0 :         u.packet.seconds_until_maintenance = htobe32(0);
     188           0 :         errors |= (1U<<(uint8_t(Errors::MaintenanceRequired)));
     189             :     } else {
     190          93 :         u.packet.seconds_until_maintenance = htobe32(seconds_until_maintenance);
     191          93 :         errors &= ~(1U<<(uint8_t(Errors::MaintenanceRequired)));
     192             :     }
     193          93 :     u.packet.errors = htobe16(errors);
     194             : 
     195          93 :     switch (_state) {
     196          34 :     case State::IDLE:
     197             :     case State::RUN:
     198          34 :         u.packet.output_current = htobe16(_current_current * 100);
     199             :         // +/- 3V, depending on draw
     200          34 :         u.packet.output_voltage = htobe16(100*base_supply_voltage - 3 * (_current_current / max_current));
     201          34 :         break;
     202          59 :     case State::STOP:
     203             :     default:
     204          59 :         u.packet.output_current = 0;
     205          59 :         u.packet.output_voltage = 0;
     206          59 :         break;
     207             :     }
     208             : 
     209             :     enum class Mode {
     210             :         IDLE = 0,
     211             :         RUN = 1,
     212             :         CHARGE = 2,
     213             :         BALANCE = 3,
     214             :         OFF = 4,
     215             :     };
     216          93 :     switch (_state) {
     217          30 :     case State::STOP:
     218          30 :         u.packet.mode = (uint8_t)Mode::OFF;
     219          30 :         break;
     220          59 :     case State::STOPPING:
     221             :     case State::IDLE:
     222          59 :         u.packet.mode = (uint8_t)Mode::IDLE;
     223          59 :         break;
     224           4 :     case State::RUN:
     225           4 :         u.packet.mode = (uint8_t)Mode::RUN;
     226           4 :         break;
     227             :     }
     228             : 
     229          93 :     u.update_checksum();
     230             : 
     231             :     // const uint8_t data[] = {
     232             :     //     0xAA, 0x55, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x1E, 0xB0, 0x00, 0x10, 0x00, 0x00, 0x23, 0x7A, 0x23,
     233             :     //     0x7A, 0x11, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     234             :     //     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     235             :     //     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0xBE, 0x55, 0xAA,
     236             :     // };
     237             : 
     238          93 :     if (write_to_autopilot((char*)u.parse_buffer, ARRAY_SIZE(u.parse_buffer)) != ARRAY_SIZE(u.parse_buffer)) {
     239           0 :         AP_HAL::panic("Failed to write to autopilot: %s", strerror(errno));
     240             :     }
     241             : }

Generated by: LCOV version 1.14