LCOV - code coverage report
Current view: top level - SITL - SIM_FETtecOneWireESC.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 189 250 75.6 %
Date: 2026-09-09 11:02:08 Functions: 15 18 83.3 %

          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 FETtecOneWireESC
      17             : 
      18             :   TODO:
      19             :  - verify the assertion that DMA is required
      20             :  - stop ignoring REQ_TYPE while in bootloader?
      21             :  - correct visibility of members in simulation
      22             :  - half-duplex will require the use of a thread as every time we call update() we expect to send out a configuration message
      23             :  - tidy break vs return in AP_FETtec::handle_message
      24             :  - determine if we should have a "REQ_OK" as well as an "OK"
      25             :  - should rename simulated ESC "pwm" field to "value" or "fettec_value" or something
      26             :  - periodically log _unknown_esc_message, _message_invalid_in_state_count, _period_too_short, _receive_buf_used to dataflash using a low prio thread.
      27             :  - log type, version, subversion and sn to dataflash once.
      28             : 
      29             : 
      30             : 
      31             : Protocol:
      32             :  - SET_FAST_COM_LENGTH could set a 32-bit bitmask that will be present rather than requring consecutive motors
      33             :  - Use two magic bytes in the header instead of just one
      34             :  - Use a 16bit CRC
      35             :  - the reply request needs to repeat the data that it replies to, to make sure the reply can be clearly assigned to a request
      36             :  - need to cope with reversals
      37             :  - in the case that we don't have ESC telemetry, consider probing ESCs periodically with an "OK"-request while disarmed
      38             : */
      39             : 
      40             : #include <AP_HAL/AP_HAL.h>
      41             : 
      42             : extern const AP_HAL::HAL& hal;
      43             : 
      44             : #include <AP_Math/AP_Math.h>
      45             : 
      46             : #include "SIM_FETtecOneWireESC.h"
      47             : #include "SITL.h"
      48             : #include <AP_HAL/utility/sparse-endian.h>
      49             : 
      50             : #include "SIM_Aircraft.h"
      51             : 
      52             : #include <stdio.h>
      53             : #include <errno.h>
      54             : 
      55             : using namespace SITL;
      56             : 
      57             : // table of user settable parameters
      58             : const AP_Param::GroupInfo FETtecOneWireESC::var_info[] = {
      59             : 
      60             :     // @Param: ENA
      61             :     // @DisplayName: FETtec OneWire ESC simulator enable/disable
      62             :     // @Description: Allows you to enable (1) or disable (0) the FETtecOneWireESC simulator
      63             :     // @Values: 0:Disabled,1:Enabled
      64             :     // @User: Advanced
      65             :     AP_GROUPINFO("ENA", 1, FETtecOneWireESC, _enabled, 0),
      66             : 
      67             :     // @Param: POW
      68             :     // @DisplayName: Power off FETtec ESC mask
      69             :     // @Description: Allows you to turn power off to the simulated ESCs.  Bits correspond to the ESC ID, *NOT* their servo channel.
      70             :     // @User: Advanced
      71             :     AP_GROUPINFO("POW", 2, FETtecOneWireESC, _powered_mask, 0xfff),
      72             : 
      73             :     AP_GROUPEND
      74             : };
      75             : 
      76       35632 : FETtecOneWireESC::FETtecOneWireESC() : SerialDevice::SerialDevice()
      77             : {
      78        2096 :     AP_Param::setup_object_defaults(this, var_info);
      79             : 
      80             :     // initialise serial numbers and IDs
      81       35632 :     for (uint8_t n=0; n<ARRAY_SIZE(escs); n++) {
      82       33536 :         ESC &esc = escs[n];
      83       33536 :         esc.ofs = n;  // so we can index for RPM, for example
      84       33536 :         esc.id = n+1;  // really should parameterise this
      85      435968 :         for (uint8_t i=0; i<ARRAY_SIZE(esc.sn); i++) {
      86      402432 :             esc.sn[i] = n+1;
      87             :         }
      88             :     }
      89        2096 : }
      90             : 
      91      657015 : void FETtecOneWireESC::update_escs()
      92             : {
      93             :     // process the power-off mask
      94    11169255 :     for (auto  &esc : escs) {
      95    10512240 :         bool should_be_on = _powered_mask & (1U<<(esc.id-1));
      96    10512240 :         switch (esc.state) {
      97     2662060 :         case ESC::State::POWERED_OFF:
      98     2662060 :             if (should_be_on) {
      99         208 :                 esc.state = ESC::State::IN_BOOTLOADER;
     100         208 :                 esc.pwm = 0;
     101         208 :                 esc.fast_com = {};
     102         208 :                 esc.telem_request = false;
     103             :             }
     104     2662060 :             break;
     105     7850180 :         case ESC::State::IN_BOOTLOADER:
     106             :         case ESC::State::RUNNING:
     107             :         case ESC::State::RUNNING_START:
     108     7850180 :             if (!should_be_on) {
     109          16 :                 esc.state = ESC::State::POWERED_OFF;
     110          16 :                 break;
     111             :             }
     112             :         }
     113             :     }
     114             : 
     115    11169255 :     for (auto  &esc : escs) {
     116    10512240 :         switch (esc.state) {
     117    10512175 :         case ESC::State::POWERED_OFF:
     118             :         case ESC::State::IN_BOOTLOADER:
     119             :         case ESC::State::RUNNING:
     120    10512175 :             continue;
     121          65 :         case ESC::State::RUNNING_START:
     122          65 :             esc.set_state(ESC::State::RUNNING);
     123          65 :             send_response(PackedMessage<OK> {
     124          65 :                 esc.id,
     125             :                 OK{}
     126             :             });
     127             :         }
     128             :     }
     129             : 
     130    11169255 :     for (auto  &esc : escs) {
     131    10512240 :         if (esc.state != ESC::State::RUNNING) {
     132     8580482 :             continue;
     133             :         }
     134             :         // FIXME: this may not be an entirely accurate model of the
     135             :         // temperature profile of these ESCs.
     136     1931758 :         esc.temperature += esc.pwm/100000;
     137     1931758 :         esc.temperature *= 0.95;
     138             :     }
     139      657015 : }
     140             : 
     141      658525 : void FETtecOneWireESC::update(const class Aircraft &aircraft)
     142             : {
     143      658525 :     if (!_enabled.get()) {
     144        1510 :         return;
     145             :     }
     146             : 
     147      657015 :     update_escs();
     148             : 
     149      657015 :     update_input();
     150      657015 :     update_send(aircraft);
     151             : }
     152             : 
     153         989 : void FETtecOneWireESC::handle_config_message()
     154             : {
     155         989 :     ESC &esc = escs[u.config_message_header.target_id-1];
     156             :     simfet_debug("Config message type=%u esc=%u", (unsigned)u.config_message_header.request_type, (unsigned)u.config_message_header.target_id);
     157         989 :     if ((ResponseFrameHeaderID)u.config_message_header.header != ResponseFrameHeaderID::MASTER) {
     158           0 :         AP_HAL::panic("Unexpected header ID");
     159             :     }
     160         989 :     switch (esc.state) {
     161         729 :     case ESC::State::POWERED_OFF:
     162         729 :         return;
     163         130 :     case ESC::State::IN_BOOTLOADER:
     164         130 :         return bootloader_handle_config_message(esc);
     165           0 :     case ESC::State::RUNNING_START:
     166           0 :         return;
     167         130 :     case ESC::State::RUNNING:
     168         130 :         return running_handle_config_message(esc);
     169             :     }
     170           0 :     AP_HAL::panic("Unknown state");
     171             : }
     172             : 
     173             : template <typename T>
     174      188422 : void FETtecOneWireESC::send_response(const T &r)
     175             : {
     176             :     // simfet_debug("Sending response");
     177      188422 :     if (write_to_autopilot((char*)&r, sizeof(r)) != sizeof(r)) {
     178           0 :         AP_HAL::panic("short write");
     179             :     }
     180      188422 : }
     181             : 
     182         130 : void FETtecOneWireESC::bootloader_handle_config_message(FETtecOneWireESC::ESC &esc)
     183             : {
     184         130 :     switch ((ConfigMessageType)u.config_message_header.request_type) {
     185          65 :     case ConfigMessageType::OK: {
     186             :         PackedMessage<OK> msg {
     187          65 :             esc.id,
     188             :             OK{},
     189          65 :         };
     190          65 :         msg.header = (uint8_t)ResponseFrameHeaderID::BOOTLOADER;
     191          65 :         msg.update_checksum();
     192          65 :         send_response(msg);
     193          65 :         return;
     194             :     }
     195           0 :     case ConfigMessageType::BL_PAGE_CORRECT:   // BL only
     196             :     case ConfigMessageType::NOT_OK:
     197           0 :         break;
     198          65 :     case ConfigMessageType::BL_START_FW:       // BL only
     199          65 :         esc.set_state(ESC::State::RUNNING_START);
     200             :         // the main firmware sends an OK
     201          65 :         return;
     202           0 :     case ConfigMessageType::BL_PAGES_TO_FLASH: // BL only
     203           0 :         break;
     204           0 :     case ConfigMessageType::REQ_TYPE:
     205             :         // ignore this for now
     206           0 :         return;
     207           0 :     case ConfigMessageType::REQ_SN:
     208             :     case ConfigMessageType::REQ_SW_VER:
     209             :     case ConfigMessageType::BEEP:
     210             :     case ConfigMessageType::SET_FAST_COM_LENGTH:
     211             :     case ConfigMessageType::SET_TLM_TYPE: //1 for alternative telemetry. ESC sends full telem per ESC: Temp, Volt, Current, ERPM, Consumption, CrcErrCount
     212             :     case ConfigMessageType::SET_LED_TMP_COLOR:
     213           0 :         break;
     214             :     }
     215           0 :     return;
     216             :     AP_HAL::panic("Unhandled config message in bootloader (%u)",
     217             :                   (unsigned)u.config_message_header.request_type);
     218             : }
     219             : 
     220         130 : void FETtecOneWireESC::running_handle_config_message(FETtecOneWireESC::ESC &esc)
     221             : {
     222         130 :     switch ((ConfigMessageType)u.config_message_header.request_type) {
     223             : 
     224           0 :     case ConfigMessageType::OK:
     225           0 :         return send_response(PackedMessage<OK> {
     226           0 :             esc.id,
     227             :             OK{}
     228           0 :         });
     229             : 
     230           0 :     case ConfigMessageType::BL_PAGE_CORRECT:   // BL only
     231             :     case ConfigMessageType::NOT_OK:
     232           0 :         break;
     233           0 :     case ConfigMessageType::BL_START_FW:       // BL only
     234           0 :         hal.console->printf("received unexpected BL_START_FW message\n");
     235           0 :         AP_HAL::panic("received unexpected BL_START_FW message");
     236             :         return;
     237           0 :     case ConfigMessageType::BL_PAGES_TO_FLASH: // BL only
     238           0 :         break;
     239             : 
     240           0 :     case ConfigMessageType::REQ_TYPE:
     241           0 :         return send_response(PackedMessage<ESC_TYPE> {
     242           0 :             esc.id,
     243           0 :             ESC_TYPE{esc.type}
     244           0 :         });
     245             : 
     246           0 :     case ConfigMessageType::REQ_SN:
     247           0 :         return send_response(PackedMessage<SN> {
     248           0 :             esc.id,
     249           0 :             SN{esc.sn, ARRAY_SIZE(esc.sn)}
     250           0 :         });
     251             : 
     252           0 :     case ConfigMessageType::REQ_SW_VER:
     253           0 :         return send_response(PackedMessage<SW_VER> {
     254           0 :             esc.id,
     255             :             SW_VER{esc.sw_version, esc.sw_subversion}
     256           0 :         });
     257             : 
     258           0 :     case ConfigMessageType::BEEP:
     259           0 :         break;
     260             : 
     261          65 :     case ConfigMessageType::SET_FAST_COM_LENGTH:
     262          65 :         esc.fast_com.length = u.packed_set_fast_com_length.msg.length;
     263          65 :         esc.fast_com.byte_count = u.packed_set_fast_com_length.msg.byte_count;
     264          65 :         esc.fast_com.min_esc_id = u.packed_set_fast_com_length.msg.min_esc_id;
     265          65 :         esc.fast_com.id_count = u.packed_set_fast_com_length.msg.id_count;
     266          65 :         return send_response(PackedMessage<OK> {
     267          65 :             esc.id,
     268             :             OK{}
     269          65 :         });
     270             : 
     271          65 :     case ConfigMessageType::SET_TLM_TYPE: //1 for alternative telemetry. ESC sends full telem per ESC: Temp, Volt, Current, ERPM, Consumption, CrcErrCount
     272          65 :         return handle_config_message_set_tlm_type(esc);
     273             : 
     274           0 :     case ConfigMessageType::SET_LED_TMP_COLOR:
     275           0 :         break;
     276             :     }
     277           0 :     AP_HAL::panic("Unknown config message (%u)", (unsigned)u.config_message_header.request_type);
     278             : }
     279             : 
     280             : 
     281          65 : void FETtecOneWireESC::handle_config_message_set_tlm_type(ESC &esc)
     282             : {
     283          65 :     const TLMType type = (TLMType)u.packed_set_tlm_type.msg.type;
     284          65 :     switch (type) {
     285          65 :     case TLMType::NORMAL:
     286             :     case TLMType::ALTERNATIVE:
     287          65 :         esc.telem_type = type;
     288          65 :         send_response(PackedMessage<OK> {
     289          65 :             esc.id,
     290             :             OK{}
     291             :         });
     292          65 :         return;
     293             :     }
     294           0 :     AP_HAL::panic("unknown telem type=%u", (unsigned)type);
     295             : }
     296             : 
     297      188960 : void FETtecOneWireESC::handle_fast_esc_data()
     298             : {
     299             :     // decode first byte - see driver for details
     300      188960 :     const uint8_t telem_request = u.buffer[0] >> 4;
     301             : 
     302             :     // offset into escs array for first esc involved in fast-throttle
     303             :     // command:
     304      188960 :     const uint8_t esc0_ofs = fast_com.min_esc_id - 1;
     305             : 
     306             :     // ::fprintf(stderr, "telem_request=%u\n", (unsigned)telem_request);
     307             :     uint16_t esc0_pwm;
     308      188960 :     esc0_pwm = ((u.buffer[0] >> 3) & 0x1) << 10;
     309      188960 :     if ((u.buffer[0] & 0b111) != 0x1) {
     310           0 :         AP_HAL::panic("expected fast-throttle command");
     311             :     }
     312             : 
     313             :     // decode second byte
     314      188960 :     esc0_pwm |= (u.buffer[1] >> 5) << 7;
     315      188960 :     if ((u.buffer[1] & 0b00011111) != 0x1f) {
     316           0 :         AP_HAL::panic("Unexpected 5-bit target id");
     317             :     }
     318             : 
     319             :     // decode enough of third byte to complete pwm[0]
     320      188960 :     esc0_pwm |= u.buffer[2] >> 1;
     321             : 
     322      188960 :     if (escs[esc0_ofs].state == ESC::State::RUNNING) {
     323      188562 :         ESC &esc { escs[esc0_ofs] };
     324      188562 :         esc.pwm = esc0_pwm;
     325             : 
     326      188562 :         if (telem_request == esc.id) {
     327       68082 :             esc.telem_request = true;
     328             :         }
     329             :         simfet_debug("esc=%u out: %u", esc.id, (unsigned)esc.pwm);
     330             :     }
     331             : 
     332             :     // decode remainder of ESC values
     333             : 
     334             :     // slides a window across the input buffer, extracting 11-bit ESC
     335             :     // values.  The top 11 bits in "window" are the ESC value.
     336      188960 :     uint8_t byte_ofs = 2;
     337      188960 :     uint32_t window = u.buffer[byte_ofs++]<<24;
     338      188960 :     window <<= 7;
     339      188960 :     uint8_t bits_free = 32-1;
     340      638593 :     for (uint8_t i=esc0_ofs+1; i<esc0_ofs+fast_com.id_count; i++) {
     341     1382393 :         while (bits_free > 7) {
     342      932760 :             window |= u.buffer[byte_ofs++] << (bits_free-8);
     343      932760 :             bits_free -= 8;
     344             :         }
     345      449633 :         ESC &esc { escs[i] };
     346      449633 :         if (esc.state == ESC::State::RUNNING) {
     347      446844 :             if (telem_request == esc.id) {
     348      120080 :                 esc.telem_request = true;
     349             :             }
     350      446844 :             esc.pwm = window >> 21;
     351             :             simfet_debug("esc=%u out: %u", esc.id, (unsigned)esc.pwm);
     352             :         }
     353      449633 :         window <<= 11;
     354      449633 :         bits_free += 11;
     355             :     }
     356             : 
     357     3212320 :     for (uint8_t i=0; i<ARRAY_SIZE(escs); i++) {
     358     3023360 :         const ESC &esc { escs[i] };
     359     3023360 :         if (esc.pwm == 0) {
     360     2384767 :             continue;
     361             :         }
     362             :         // this will need to adjust for reversals.  We should also set
     363             :         // one of the simulated ESCs up to have a pair of motor wires
     364             :         // crossed i.e. spin backwards.  Maybe a mask for it
     365      638593 :         if (esc.pwm >= 1000 && esc.pwm <= 2000) {
     366      638593 :             continue;
     367             :         }
     368           0 :         AP_HAL::panic("transmitted value out of range (%u)", esc.pwm);
     369             :     }
     370      188960 : }
     371             : 
     372      189949 : void FETtecOneWireESC::consume_bytes(uint8_t count)
     373             : {
     374      189949 :     if (count > buflen) {
     375           0 :         AP_HAL::panic("Consuming more bytes than in buffer?");
     376             :     }
     377      189949 :     if (buflen == count) {
     378      189949 :         buflen = 0;
     379      189949 :         return;
     380             :     }
     381           0 :     memmove(&u.buffer[0], &u.buffer[count], buflen - count);
     382           0 :     buflen -= count;
     383             : }
     384             : 
     385      657015 : void FETtecOneWireESC::update_input()
     386             : {
     387      657015 :     const ssize_t n = read_from_autopilot((char*)&u.buffer[buflen], ARRAY_SIZE(u.buffer) - buflen);
     388      657015 :     if (n < 0) {
     389             :         // TODO: do better here
     390           0 :         if (errno != EAGAIN && errno != EWOULDBLOCK && errno != 0) {
     391           0 :             AP_HAL::panic("Failed to read from autopilot");
     392             :         }
     393             :     } else {
     394      657015 :         buflen += n;
     395             :     }
     396             : 
     397             :     // bool config_message_checksum_fail = false;
     398             : 
     399      657015 :     if (buflen > offsetof(ConfigMessageHeader, header) &&
     400      189949 :         u.config_message_header.header == 0x01 &&
     401         989 :         buflen > offsetof(ConfigMessageHeader, frame_len) &&
     402         989 :         buflen >= u.config_message_header.frame_len) {
     403         989 :         const uint8_t calculated_checksum = crc8_dvb_update(0, u.buffer, u.config_message_header.frame_len-1);
     404         989 :         const uint8_t received_checksum = u.buffer[u.config_message_header.frame_len-1];
     405         989 :         if (calculated_checksum == received_checksum) {
     406         989 :             handle_config_message();
     407             :             // consume the message:
     408         989 :             consume_bytes(u.config_message_header.frame_len);
     409         989 :             return;
     410             :         } else {
     411             :             simfet_debug("Checksum mismatch");
     412           0 :             abort();
     413             :             // config_message_checksum_fail = true;
     414             :         }
     415             :         return; // 1 message/loop....
     416             :     }
     417             : 
     418             :     // no config message, so let's see if there's fast PWM input.
     419      656026 :     if (fast_com.id_count == 255) {
     420             :         // see if any ESC has been configured:
     421     1381028 :         for (uint8_t i=0; i<ARRAY_SIZE(escs); i++) {
     422     1299792 :             if (escs[i].fast_com.id_count == 255) {
     423     1299776 :                 continue;
     424             :             }
     425          16 :             fast_com.id_count = escs[i].fast_com.id_count;
     426          16 :             fast_com.min_esc_id = escs[i].fast_com.min_esc_id;
     427          16 :             break;
     428             :         }
     429             :     }
     430      656026 :     if (fast_com.id_count == 255) {
     431             :         // no ESC is configured.  Ummm.
     432       81236 :         buflen = 0;
     433       81236 :         return;
     434             :     }
     435             : 
     436      574790 :     const uint16_t total_bits_required = 4 + 1 + 7  + (fast_com.id_count*11);
     437      574790 :     const uint8_t bytes_required = (total_bits_required + 7) / 8 + 1;
     438             : 
     439      574790 :     if (buflen < bytes_required) {
     440      385830 :         return;
     441             :     }
     442             : 
     443      188960 :     if (buflen == bytes_required) {
     444      188960 :         const uint8_t calculated_checksum = crc8_dvb_update(0, u.buffer, buflen-1);
     445      188960 :         if (u.buffer[buflen-1] != calculated_checksum) {
     446           0 :             AP_HAL::panic("checksum failure");
     447             :         }
     448             : 
     449      188960 :         handle_fast_esc_data();
     450      188960 :         consume_bytes(bytes_required);
     451      188960 :         return;
     452             :     }
     453             : 
     454             :     // debug("Read (%d) bytes from autopilot (%u)", (signed)n, config_message_checksum_fail);
     455           0 :     if (n >= 0) {
     456           0 :         abort();
     457             :     }
     458           0 :     buflen = 0;
     459             : }
     460             : 
     461      657815 : void FETtecOneWireESC::update_sitl_input_pwm(struct sitl_input &input)
     462             : {
     463             :     // overwrite the SITL input values passed through from
     464             :     // sitl_model->update_model with those we're receiving serially
     465    11182855 :     for (auto &esc : escs) {
     466    10525040 :         if (esc.id > ARRAY_SIZE(input.servos)) {
     467             :             // silently ignore; input.servos is 12-long, we are
     468             :             // usually 16-long
     469           0 :             continue;
     470             :         }
     471    10525040 :         input.servos[esc.id-1] = esc.pwm;
     472             :     }
     473      657815 : }
     474             : 
     475      657015 : void FETtecOneWireESC::send_esc_telemetry(const Aircraft &aircraft)
     476             : {
     477    11169255 :     for (auto &esc : escs) {
     478    10512240 :         if (!esc.telem_request) {
     479    10324078 :             continue;
     480             :         }
     481      188162 :         esc.telem_request = false;
     482      188162 :         if (esc.state != ESC::State::RUNNING) {
     483           0 :             continue;
     484             :         }
     485      188162 :         if (esc.telem_type != TLMType::ALTERNATIVE) {
     486             :             // no idea what "normal" looks like
     487           0 :             abort();
     488             :         }
     489             : 
     490      188162 :         const int8_t temp_cdeg = esc.temperature * 100;
     491      188162 :         const uint16_t voltage = aircraft.get_battery_voltage() * 100;
     492      188162 :         const uint16_t current = (6 + esc.id * 100);
     493             : 
     494             :         // FIXME: the vehicle models should be supplying this RPM!
     495      188162 :         const uint16_t Kv = 1000;
     496      188162 :         const float p = (esc.pwm-1000)/1000.0;
     497      188162 :         int16_t rpm = aircraft.get_battery_voltage() * Kv * p;
     498             : 
     499      188162 :         const uint16_t consumption_mah = 0;
     500      188162 :         const uint16_t errcount = 17;
     501      188162 :         send_response(PackedMessage<ESCTelem> {
     502      188162 :             esc.id,
     503             :             ESCTelem{temp_cdeg, voltage, current, rpm, consumption_mah, errcount}
     504             :         });
     505             :     }
     506      657015 : }
     507             : 
     508      657015 : void FETtecOneWireESC::update_send(const Aircraft &aircraft)
     509             : {
     510      657015 :     send_esc_telemetry(aircraft);
     511      657015 : }

Generated by: LCOV version 1.14