Line data Source code
1 : #include "AP_Generator_Loweheiser.h"
2 :
3 : #if AP_GENERATOR_LOWEHEISER_ENABLED
4 :
5 : #include <AP_Vehicle/AP_Vehicle.h>
6 : #include <GCS_MAVLink/GCS.h>
7 :
8 : extern const AP_HAL::HAL& hal;
9 :
10 : #define MASK_LOG_ANY 0xFFFF
11 : #define LOWEHEISER_EFI_COMS_TIMEOUT_MS 1500
12 : #define LOWEHEISER_EFI_VOLT_MIN 11.0
13 : #define LOWEHEISER_EFI_VOLT_MAX 13.5
14 :
15 : const AP_Param::GroupInfo AP_Generator_Loweheiser::var_info[] = {
16 :
17 : // Param indexes must be between 10 and 19 to avoid conflict with other generator param tables loaded by pointer
18 :
19 : // @Param: MNT_TIME
20 : // @DisplayName: Seconds until maintenance required
21 : // @Description: Seconds until maintenance required
22 : // @User: Advanced
23 : AP_GROUPINFO("MNT_TIME", 10, AP_Generator_Loweheiser, time_until_maintenance, 0),
24 :
25 : // @Param: RUNTIME
26 : // @DisplayName: Total runtime
27 : // @Description: Total time this generator has run in seconds
28 : // @User: Advanced
29 : AP_GROUPINFO("RUNTIME", 11, AP_Generator_Loweheiser, total_runtime, 0),
30 :
31 : // @Param: IDLE_TH_H
32 : // @DisplayName: High Idle throttle
33 : // @Description: throttle value to use when warming up or cooling down
34 : // @User: Advanced
35 : AP_GROUPINFO("IDLE_TH_H", 12, AP_Generator_Loweheiser, high_idle_throttle, 15),
36 :
37 : // @Param: IDLE_TH
38 : // @DisplayName: Idle throttle
39 : // @Description: throttle value to use when idling
40 : // @User: Advanced
41 : AP_GROUPINFO("IDLE_TH", 13, AP_Generator_Loweheiser, idle_throttle, 15),
42 :
43 : // @Param: RUN_TEMP
44 : // @DisplayName: Run Temperature
45 : // @Description: temperature required for generator to start producing power in deg celsius
46 : // @User: Advanced
47 : AP_GROUPINFO("RUN_TEMP", 14, AP_Generator_Loweheiser, temp_required_for_run, 60),
48 :
49 : // @Param: IDLE_TEMP
50 : // @DisplayName: Idle Temperature
51 : // @Description: temperature required for generator to return to idle after having run
52 : // @User: Advanced
53 : AP_GROUPINFO("IDLE_TEMP", 15, AP_Generator_Loweheiser, temp_required_for_idle, 110),
54 :
55 : // @Param: OVER_TEMP
56 : // @DisplayName: Cylinder Head Over Temperature Warning Level
57 : // @Description: threshold temperature for the cylinder head above which the mavlink over temperature message gets sent
58 : // @Units: degC
59 : // @User: Advanced
60 : AP_GROUPINFO("OVER_TEMP", 16, AP_Generator_Loweheiser, temp_for_overtemp_warning, 205),
61 :
62 : // Param indexes must be between 10 and 19 to avoid conflict with other generator param tables loaded by pointer
63 :
64 : AP_GROUPEND
65 : };
66 :
67 10 : void AP_Generator_Loweheiser::init()
68 : {
69 10 : AP_Param::setup_object_defaults(this, var_info);
70 :
71 10 : _frontend._has_current = true;
72 10 : _frontend._has_consumed_energy = true;
73 10 : _frontend._has_fuel_remaining = false;
74 :
75 : // nothing in this method may use parameters as AP_Generator loads
76 : // values from eeprom!
77 10 : }
78 :
79 : // healthy returns true if the generator is not present, or it is
80 : // present, providing telemetry and not indicating an errors. Check
81 : // several fields as being within-range.
82 7410 : bool AP_Generator_Loweheiser::healthy() const
83 : {
84 7410 : if (last_packet_received_ms == 0) {
85 0 : return true;
86 : }
87 :
88 7410 : if (AP_HAL::millis() - last_packet_received_ms > LOWEHEISER_EFI_COMS_TIMEOUT_MS) {
89 0 : return false;
90 : }
91 :
92 : // these voltage constants were supplied by the manufacturer:
93 13832 : if (!isnan(packet.efi_batt) &&
94 6422 : (packet.efi_batt < LOWEHEISER_EFI_VOLT_MIN || packet.efi_batt > LOWEHEISER_EFI_VOLT_MAX)) {
95 0 : return false;
96 : }
97 :
98 7410 : return true;
99 : }
100 :
101 : // runstate_string provides a textual representation for the supplied
102 : // runstate. Useful in messages to the user.
103 66 : const char *AP_Generator_Loweheiser::runstate_string(PilotDesiredRunState runstate)
104 : {
105 66 : switch (runstate) {
106 30 : case PilotDesiredRunState::STOP:
107 30 : return "STOP";
108 16 : case PilotDesiredRunState::IDLE:
109 16 : return "IDLE";
110 20 : case PilotDesiredRunState::RUN:
111 20 : return "RUN";
112 : }
113 0 : return "?";
114 : }
115 :
116 33 : void AP_Generator_Loweheiser::set_pilot_desired_runstate(PilotDesiredRunState newstate) {
117 33 : gcs().send_text(MAV_SEVERITY_INFO, "LH: pilot-desired state to (%s) from (%s)", runstate_string(newstate), runstate_string(pilot_desired_runstate));
118 33 : pilot_desired_runstate = newstate;
119 33 : }
120 :
121 : // returns true if the generator should be allowed to move into the
122 : // "run" (high-RPM) state:
123 1295 : bool AP_Generator_Loweheiser::generator_ok_to_run() const
124 : {
125 1295 : if (isnan(packet.efi_clt)) {
126 10 : return false;
127 : }
128 1285 : return packet.efi_clt >= temp_required_for_run;
129 : }
130 :
131 : // returns true if the generator should be allowed to move into the
132 : // "stop" state:
133 451 : bool AP_Generator_Loweheiser::generator_ok_to_stop() const
134 : {
135 451 : return packet.efi_clt <= temp_required_for_idle;
136 : }
137 :
138 : // should_emergency_stop - returns true if the generator must stop
139 : // immediately
140 7410 : bool AP_Generator_Loweheiser::should_emergency_stop()
141 : {
142 7410 : const char *estop_reason = nullptr;
143 :
144 7410 : if (AP::vehicle()->is_crashed()) {
145 0 : estop_reason = "crash";
146 : }
147 :
148 7410 : if (hal.util->safety_switch_state() == AP_HAL::Util::SAFETY_DISARMED) {
149 64 : estop_reason = "safety switch";
150 : }
151 :
152 7410 : if (estop_reason == nullptr) {
153 : // not emergency stopped
154 7346 : estop_reported = false;
155 7346 : return false;
156 : }
157 :
158 : // we are currrently emergency-stopped
159 64 : if (!estop_reported) {
160 2 : gcs().send_text(MAV_SEVERITY_INFO, "LH: %s; stopping generator", estop_reason);
161 2 : estop_reported = true;
162 : }
163 :
164 64 : return true;
165 : }
166 :
167 : // update_runstate updates the commanded runstate - what we are
168 : // telling the generator to do. Which state we request the generator
169 : // move to depends on the RC input control, the temperature the
170 : // generator is at and other information
171 7410 : void AP_Generator_Loweheiser::update_runstate()
172 : {
173 : // If the vehicle crashes then we assume the pilot wants to stop
174 : // the motor. This is done as a once-off when the crash is
175 : // detected to allow the operator to rearm the vehicle, or we end
176 : // up in a catch-22 situation where we force the stop state on the
177 : // generator so they can't arm and can't start the generator
178 : // because the vehicle is crashed.
179 7410 : if (should_emergency_stop()) {
180 64 : commanded_runstate = RunState::STOP;
181 64 : return;
182 : }
183 :
184 7346 : if (hal.util->get_soft_armed()) {
185 : // don't permit transitions while armed
186 3620 : return;
187 : }
188 :
189 : // if we have lost RC input, and the vehicle is disarmed then stop
190 : // the generator - but allow the thing to cool down....
191 3726 : if (!rc().has_valid_input() &&
192 0 : pilot_desired_runstate != PilotDesiredRunState::STOP) {
193 0 : gcs().send_text(MAV_SEVERITY_INFO, "LH: no RC and disarmed; stopping generator");
194 0 : pilot_desired_runstate = PilotDesiredRunState::STOP;
195 : }
196 :
197 : // skip changing the commanded runstate if we're already in the
198 : // target state. Note that this block also stops us forcing the
199 : // runstate to idle when we don't have data from the EFI!
200 3726 : switch (pilot_desired_runstate) {
201 1352 : case PilotDesiredRunState::STOP:
202 1352 : if (commanded_runstate == RunState::STOP) {
203 913 : return;
204 : }
205 439 : break;
206 358 : case PilotDesiredRunState::IDLE:
207 358 : if (commanded_runstate == RunState::IDLE) {
208 339 : return;
209 : }
210 19 : break;
211 2016 : case PilotDesiredRunState::RUN:
212 2016 : if (commanded_runstate == RunState::RUN) {
213 1476 : return;
214 : }
215 540 : break;
216 : }
217 :
218 998 : if (isnan(packet.efi_clt)) {
219 : // we don't know what the temperature is..... command idle
220 : // until we know what the temperature is. This can happen
221 : // because the EFI is actually powered off when we command the
222 : // generator to stop. Moving to IDLE should start it up so we
223 : // can get the data we need:
224 15 : commanded_runstate = RunState::IDLE;
225 15 : return;
226 : }
227 :
228 : // consider changing the commanded runstate to the pilot desired
229 : // runstate:
230 983 : commanded_runstate = RunState::IDLE;
231 983 : switch (pilot_desired_runstate) {
232 439 : case PilotDesiredRunState::STOP:
233 439 : if (!generator_ok_to_stop()) {
234 433 : commanded_runstate = RunState::COOLING_DOWN;
235 433 : break;
236 : }
237 6 : commanded_runstate = RunState::STOP;
238 6 : break;
239 12 : case PilotDesiredRunState::IDLE:
240 12 : if (!generator_ok_to_stop()) {
241 12 : commanded_runstate = RunState::COOLING_DOWN;
242 12 : break;
243 : }
244 0 : commanded_runstate = RunState::IDLE;
245 0 : break;
246 532 : case PilotDesiredRunState::RUN:
247 532 : if (!generator_ok_to_run()) {
248 524 : commanded_runstate = RunState::WARMING_UP;
249 524 : break;
250 : }
251 8 : commanded_runstate = RunState::RUN;
252 8 : break;
253 : }
254 : }
255 :
256 : // update_common_backend_variables changes data in the superclass
257 : // object, reporting our current state
258 7410 : void AP_Generator_Loweheiser::update_common_backend_variables()
259 : {
260 7410 : if (last_packet_received_ms == 0) {
261 : // no data from the generator
262 0 : return;
263 : }
264 :
265 : // common backend variables. These are used by the base class
266 : // (AP_Generator_Backend) to provide data to the battery monitor
267 : // library.
268 7410 : _voltage = packet.volt_batt;
269 7410 : _current = packet.curr_batt;
270 :
271 : // provide our own aggregate data:
272 7410 : _consumed_mah = accumulated_consumed_fuel_litres * 1000;
273 :
274 : // packet.efi_rpm_consumed goes to NaN while the EFI is off
275 : // (which is the case when the generator is in the "off" state).
276 : // In that case we assume the generator isn't turning.
277 7410 : if (isnan(packet.efi_rpm)) {
278 0 : _rpm = 0;
279 : } else {
280 7410 : _rpm = packet.efi_rpm;
281 : }
282 :
283 : // packet.efi_fuel_consumed goes to NaN while the EFI is off
284 : // (which is the case when the generator is in the "off" state).
285 : // In that case we send 0 as the amount of fuel remaining
286 7410 : if (isnan(packet.fuel_level)) {
287 7410 : _fuel_remaining_l = 0;
288 : } else {
289 0 : _fuel_remaining_l = packet.fuel_level;
290 : }
291 : }
292 :
293 7410 : void AP_Generator_Loweheiser::check_second_init()
294 : {
295 7410 : if (second_init_done) {
296 7400 : return;
297 : }
298 10 : second_init_done = true;
299 10 : if (time_until_maintenance == 0) {
300 : // manufacturer-recommended maintenance interval, 300 hours.
301 : // On the off-chance that the user manages to switch the
302 : // vehicle off at exactly 0 seconds remaining this *will*
303 : // reset.
304 2 : time_until_maintenance.set_and_save(300 * 60 * 60);
305 : }
306 : }
307 :
308 7410 : void AP_Generator_Loweheiser::update()
309 : {
310 7410 : check_second_init();
311 :
312 : // periodically check the user's configuration of RC input channels:
313 7410 : check_rc_input_channels();
314 :
315 : // consider our pilot-demanded run state and perhaps change the
316 : // way we are commanding the generator:
317 7410 : update_runstate();
318 :
319 : // update the variables the frontend looksat:
320 7410 : update_common_backend_variables();
321 :
322 : // now ask the frontend to look at that updated state:
323 7410 : update_frontend();
324 :
325 : // consider sending commands to the generator, which we do at
326 : // regular intervals:
327 7410 : command_generator();
328 :
329 : // see if the generator is producing good acks:
330 7410 : if (last_ack_packet_ms != last_ack_packet_processed_ms) {
331 7382 : last_ack_packet_processed_ms = last_ack_packet_ms;
332 7382 : good_ack = ack_packet.result == MAV_RESULT_ACCEPTED;
333 : }
334 :
335 : // onboard logging
336 7410 : Log_Write();
337 :
338 : // record runtime statistics (e.g. total runtime) to backing parameters:
339 7410 : update_stats();
340 7410 : }
341 :
342 7410 : void AP_Generator_Loweheiser::check_rc_input_channels()
343 : {
344 7410 : const uint32_t now_ms = AP_HAL::millis();
345 :
346 : // only check every now and then:
347 7410 : if (now_ms - last_rc_channel_check < 1000) {
348 6667 : return;
349 : }
350 743 : last_rc_channel_check = now_ms;
351 :
352 : // update our manual control throttle channel every now and then
353 : // (for runtime configuration):
354 743 : RC_Channel *x = rc().find_channel_for_option(RC_Channel::AUX_FUNC::LOWEHEISER_THROTTLE);
355 743 : if (x != rc_channel_manual_throttle) {
356 6 : rc_channel_manual_throttle = x;
357 6 : if (rc_channel_manual_throttle != nullptr) {
358 5 : rc_channel_manual_throttle->set_range(4500);
359 : }
360 : }
361 :
362 : // see if the user has defined a starter motor input channel.
363 : // This allows for manual control of the starter motor
364 743 : rc_channel_starter_motor = rc().find_channel_for_option(RC_Channel::AUX_FUNC::LOWEHEISER_STARTER);
365 : }
366 :
367 : // command_generator assembles COMMAND_LONG messages to send to the
368 : // generator. It does this at regular intervals so that in the case
369 : // the MCU dies the generator can undertake sensible failsafe actions.
370 7410 : void AP_Generator_Loweheiser::command_generator()
371 : {
372 7410 : if (mavlink_channel == nullptr) {
373 : // we've never seen a generator, so don't bother with any more work
374 0 : return;
375 : }
376 :
377 7410 : const uint32_t now_ms = AP_HAL::millis();
378 :
379 : // these values are appropriate to command the generator to the
380 : // "STOP state. Note that these are values which are placed
381 : // directly into the packet going to the generator.
382 7410 : float throttle = 0.0;
383 7410 : uint8_t desired_governor_state = 0;
384 7410 : uint8_t run_electric_starter = 0;
385 7410 : uint8_t desired_engine_state = 0;
386 :
387 7410 : bool user_controlled_starter = false;
388 7410 : switch (commanded_runstate) {
389 983 : case RunState::STOP:
390 : // the variable initialisation above is sufficient
391 983 : break;
392 969 : case RunState::COOLING_DOWN:
393 : case RunState::WARMING_UP: {
394 : // because we can spend a deal of time in this state we
395 : // comfort the user periodically by giving them a progress
396 : // message of sorts:
397 969 : if (now_ms - last_waiting_temperature_change_ms > 5000) {
398 27 : if (isnan(packet.efi_clt)) {
399 0 : gcs().send_text(MAV_SEVERITY_INFO, "LH: Waiting for EFI");
400 27 : } else if (commanded_runstate == RunState::WARMING_UP) {
401 16 : gcs().send_text(MAV_SEVERITY_INFO,
402 : "LH: Generator warming up (%f < %f)",
403 16 : packet.efi_clt,
404 16 : temp_required_for_run.get());
405 : } else {
406 11 : gcs().send_text(MAV_SEVERITY_INFO,
407 : "LH: Generator cooling down (%f > %f)",
408 11 : packet.efi_clt,
409 11 : temp_required_for_idle.get());
410 : }
411 27 : last_waiting_temperature_change_ms = now_ms;
412 : }
413 969 : desired_engine_state = 1;
414 969 : throttle = high_idle_throttle;
415 969 : break;
416 : }
417 354 : case RunState::IDLE:
418 : // consider acting on manual throttle input:
419 592 : if (rc_channel_manual_throttle != nullptr &&
420 238 : rc().has_valid_input()) {
421 238 : throttle = rc_channel_manual_throttle->percent_input();
422 : // honour an electric start channel, too:
423 238 : if (rc_channel_starter_motor != nullptr) {
424 238 : user_controlled_starter = true;
425 238 : switch (rc_channel_starter_motor->get_aux_switch_pos()) {
426 110 : case RC_Channel::AuxSwitchPos::LOW:
427 : case RC_Channel::AuxSwitchPos::MIDDLE:
428 110 : break;
429 128 : case RC_Channel::AuxSwitchPos::HIGH:
430 128 : run_electric_starter = 1;
431 128 : break;
432 : }
433 : }
434 : } else {
435 : // no manual throttle in play
436 116 : throttle = idle_throttle;
437 : }
438 354 : desired_engine_state = 1;
439 354 : break;
440 5104 : case RunState::RUN:
441 5104 : desired_engine_state = 1;
442 5104 : desired_governor_state = 1;
443 5104 : break;
444 : }
445 :
446 : // if our desired run state is not "stop", and the RPM is zero,
447 : // then consider running the starter motor. Run motor for 5s.
448 7410 : if (!user_controlled_starter && commanded_runstate != RunState::STOP) {
449 6189 : bool configure_for_start = false;
450 6189 : if (last_start_time_ms == 0) {
451 5404 : if (is_zero(packet.efi_rpm)) {
452 8 : gcs().send_text(MAV_SEVERITY_INFO, "LH: running starter motor");
453 8 : last_start_time_ms = now_ms;
454 8 : configure_for_start = true;
455 : }
456 785 : } else if (now_ms - last_start_time_ms < 5000) {
457 306 : configure_for_start = true;
458 479 : } else if (now_ms - last_start_time_ms > 20000) {
459 4 : last_start_time_ms = 0;
460 : }
461 :
462 6189 : if (configure_for_start) {
463 314 : run_electric_starter = 1;
464 314 : desired_governor_state = 0;
465 : // force the throttle to the idle throttle for starting:
466 314 : throttle = idle_throttle;
467 : }
468 : }
469 :
470 : // actually send the packet:
471 7410 : mavlink_msg_command_long_send(
472 7410 : mavlink_channel->get_chan(),
473 7410 : sysid,
474 7410 : compid,
475 : MAV_CMD_LOWEHEISER_SET_STATE,
476 : 0, // confirmation
477 7410 : efi_index, // p1, EFI index
478 : desired_engine_state, // p2, desired engine state
479 : desired_governor_state, // p3, desired governor state - 1 means governed
480 : throttle, // p4, desired manual throttle
481 : run_electric_starter, // p5, electric starter
482 : 0, // p6, empty
483 : 0 // p7, empty
484 : );
485 :
486 : #if HAL_LOGGING_ENABLED
487 : // log all commands to dataflash:
488 :
489 : // @LoggerMessage: LOEC
490 : // @Description: Gathered Loweheiser EFI/Governor telemetry
491 : // @Field: TimeUS: Time since system startup
492 : // @Field: SI: target system ID
493 : // @Field: CI: target component ID
494 : // @Field: C: command
495 : // @Field: I: efi index
496 : // @Field: ES: desired engine state (0:EFI off 1:EFI on)
497 : // @Field: GS: desired governor state (0:Governor off 1:Governor on)
498 : // @Field: Thr: manual throttle value
499 : // @Field: Strtr: desired electric start
500 :
501 7410 : if (AP::logger().should_log(MASK_LOG_ANY)) {
502 14752 : AP::logger().Write(
503 : "LOEC",
504 : "TimeUS," "SI," "CI," "C," "I," "ES," "GS," "Thr," "Strtr",
505 : "s" "-" "-" "-" "#" "-" "-" "-" "-" ,
506 : "F" "-" "-" "-" "-" "-" "-" "-" "-" ,
507 : "Q" "B" "B" "I" "B" "B" "B" "f" "B" ,
508 : AP_HAL::micros64(),
509 7376 : sysid,
510 7376 : compid,
511 : MAV_CMD_LOWEHEISER_SET_STATE,
512 7376 : efi_index,
513 : desired_engine_state,
514 : desired_governor_state,
515 : throttle,
516 : run_electric_starter
517 : );
518 : }
519 : #endif // HAL_LOGGING_ENABLED
520 : }
521 :
522 : // update (and perhaps persist) statistics (e.g. total runtime) to
523 : // backing parameters:
524 7410 : void AP_Generator_Loweheiser::update_stats()
525 : {
526 7410 : bool running = false;
527 7410 : switch (commanded_runstate) {
528 983 : case RunState::STOP:
529 983 : running = false;
530 983 : break;
531 6427 : case RunState::IDLE:
532 : case RunState::RUN:
533 : case RunState::WARMING_UP:
534 : case RunState::COOLING_DOWN:
535 6427 : running = true;
536 6427 : break;
537 : }
538 :
539 7410 : if (is_zero(packet.efi_rpm)) {
540 990 : running = false;
541 : }
542 :
543 7410 : const uint32_t now_ms = AP_HAL::millis();
544 :
545 7410 : if (running) {
546 6267 : if (was_running) {
547 6253 : runtime_delta_ms += now_ms - run_start_ms;
548 6253 : run_start_ms = now_ms;
549 : } else {
550 14 : run_start_ms = now_ms;
551 14 : was_running = true;
552 : }
553 : } else {
554 1143 : if (was_running) {
555 8 : runtime_delta_ms += now_ms - run_start_ms;
556 8 : was_running = false;
557 : }
558 : }
559 :
560 : // don't record small runtime changes:
561 7410 : if (runtime_delta_ms < 1000) {
562 1064 : return;
563 : }
564 :
565 : // only save to permanent storage every 30 seconds:
566 6346 : if (now_ms - last_stats_saved_ms < 30000) {
567 6324 : return;
568 : }
569 22 : last_stats_saved_ms = now_ms;
570 :
571 22 : uint32_t seconds = runtime_delta_ms / 1000;
572 22 : runtime_delta_ms -= seconds * 1000;
573 :
574 22 : total_runtime.set_and_save_ifchanged(total_runtime + seconds);
575 : // avoid resetting time until maintenance to initial value:
576 22 : if (seconds > 0 && (signed)seconds == time_until_maintenance) {
577 0 : seconds--;
578 : }
579 22 : time_until_maintenance.set_and_save_ifchanged(time_until_maintenance - seconds);
580 : }
581 :
582 : // ensure the generator is running and generally working before
583 : // allowing the vehicle to arm:
584 376 : bool AP_Generator_Loweheiser::pre_arm_check(char *failmsg, uint8_t failmsg_len) const
585 : {
586 376 : if (last_packet_received_ms == 0) {
587 : // allow optional use of generator
588 0 : return true;
589 : }
590 :
591 376 : const uint32_t now_ms = AP_HAL::millis();
592 :
593 376 : if (now_ms - last_packet_received_ms > 2000) { // we expect @1Hz
594 0 : hal.util->snprintf(failmsg, failmsg_len, "LH: no messages in %ums", unsigned(now_ms - last_packet_received_ms));
595 0 : return false;
596 : }
597 :
598 : // we warn the user that maintenance is required, but don't stop
599 : // them arming and flying:
600 376 : if (packet.until_maintenance == 0) {
601 0 : hal.util->snprintf(failmsg, failmsg_len, "LH: requires maintenance");
602 : }
603 :
604 : // check the user is trying to run the generator:
605 376 : if (pilot_desired_runstate != PilotDesiredRunState::RUN) {
606 168 : hal.util->snprintf(failmsg, failmsg_len, "LH: requested state is not RUN");
607 168 : return false;
608 : }
609 :
610 : // check ArduPilot is trying to run the generator:
611 208 : switch (commanded_runstate) {
612 152 : case RunState::RUN:
613 : // this is a good place to be
614 152 : break;
615 4 : case RunState::STOP:
616 : case RunState::IDLE:
617 : case RunState::COOLING_DOWN:
618 4 : break;
619 52 : case RunState::WARMING_UP:
620 52 : if (!isnan(packet.efi_clt)) {
621 52 : hal.util->snprintf(failmsg, failmsg_len, "LH: Generator warming up (%.0f%%)", ((packet.efi_clt*100.0) / temp_required_for_run));
622 : } else {
623 0 : hal.util->snprintf(failmsg, failmsg_len, "LH: Generator warming up (waiting for data)");
624 : }
625 52 : return false;
626 : }
627 :
628 156 : return true;
629 : }
630 :
631 : // handle mavlink packets received from the Loweheiser generator
632 : // control system. We keep an entire packet around for ease of use.
633 11205 : void AP_Generator_Loweheiser::handle_mavlink_msg(const GCS_MAVLINK &channel, const mavlink_message_t &msg)
634 : {
635 11205 : if (seen_good_message) {
636 : // ensure any subsequent messages we accept are only from the
637 : // sysid/compid tuple we initially saw:
638 11195 : if (msg.sysid != sysid || msg.compid != compid) {
639 0 : return;
640 : }
641 : }
642 :
643 : // good_message is set true if we discover a packet from a
644 : // Loweheiser generator.
645 11205 : bool good_message = false;
646 11205 : switch (msg.msgid) {
647 3815 : case MAVLINK_MSG_ID_LOWEHEISER_GOV_EFI: {
648 3815 : mavlink_msg_loweheiser_gov_efi_decode(&msg, &packet);
649 :
650 : // TODO: ensure from correct efi_number
651 3815 : efi_index = packet.efi_index;
652 :
653 3815 : const uint32_t now_ms = AP_HAL::millis();
654 3815 : const uint32_t delta_t_ms = now_ms - last_packet_received_ms;
655 3815 : if (last_packet_received_ms == 0 ||
656 : delta_t_ms > 30000) {
657 10 : gcs().send_text(MAV_SEVERITY_INFO, "LH: Found Loweheiser Generator");
658 : } else {
659 : // update the accumulated fuel, assuming we've been consuming
660 : // at the new rate since the last packet was received:
661 :
662 : // convert from litres/hour to litres/millisecond:
663 3805 : if (!isnan(packet.efi_fuel_flow)) {
664 3204 : const float litres_per_millisecond = packet.efi_fuel_flow/(60*60*1000);
665 3204 : accumulated_consumed_fuel_litres += litres_per_millisecond*delta_t_ms;
666 : }
667 : }
668 3815 : last_packet_received_ms = now_ms;
669 3815 : good_message = true;
670 3815 : break;
671 : }
672 7390 : case MAVLINK_MSG_ID_COMMAND_ACK:
673 : // only accept acks once we've seen a loweheiser packet:
674 7390 : if (!seen_good_message) {
675 0 : return;
676 : }
677 7390 : mavlink_msg_command_ack_decode(&msg, &ack_packet);
678 7390 : last_ack_packet_ms = AP_HAL::millis();
679 7390 : break;
680 : }
681 :
682 11205 : if (!good_message) {
683 7390 : return;
684 : }
685 3815 : if (seen_good_message) {
686 3805 : return;
687 : }
688 10 : seen_good_message = true;
689 :
690 10 : mavlink_channel = &channel;
691 10 : sysid = msg.sysid;
692 10 : compid = msg.compid;
693 : }
694 :
695 : // send mavlink generator status
696 4918 : void AP_Generator_Loweheiser::send_generator_status(const GCS_MAVLINK &channel)
697 : {
698 4918 : if (last_packet_received_ms == 0) {
699 : // nothing to report
700 0 : return;
701 : }
702 :
703 4918 : uint64_t status = 0;
704 4918 : switch (commanded_runstate) {
705 803 : case RunState::STOP:
706 : // we checking RPM here but confounded by starter motor running....
707 803 : status |= MAV_GENERATOR_STATUS_FLAG_OFF;
708 803 : break;
709 763 : case RunState::COOLING_DOWN:
710 : case RunState::IDLE:
711 763 : if (generator_ok_to_run()) {
712 464 : status |= MAV_GENERATOR_STATUS_FLAG_IDLE;
713 : }
714 763 : break;
715 2994 : case RunState::RUN:
716 2994 : status |= MAV_GENERATOR_STATUS_FLAG_GENERATING;
717 2994 : break;
718 358 : case RunState::WARMING_UP:
719 358 : status |= MAV_GENERATOR_STATUS_FLAG_WARMING_UP;
720 358 : break;
721 : }
722 :
723 : // note that the EFI unit can be turned off on the Loweheiser, in
724 : // which case we receive NaN values back. We translate these into
725 : // MAVLink "unknown" values throughout here:
726 :
727 9034 : if (!isnan(packet.efi_clt) &&
728 4116 : packet.efi_clt > temp_for_overtemp_warning) {
729 0 : status |= MAV_GENERATOR_STATUS_FLAG_OVERTEMP_WARNING;
730 : }
731 :
732 4918 : int16_t rectifier_temp = INT16_MAX;
733 4918 : if (!isnan(packet.rectifier_temp)) {
734 0 : rectifier_temp = packet.rectifier_temp;
735 : }
736 4918 : int16_t generator_temp = INT16_MAX;
737 4918 : if (!isnan(packet.generator_temp)) {
738 0 : generator_temp = packet.generator_temp;
739 : }
740 :
741 : // not all loweheiser firmwares provide runtime and
742 : // time-until-maintenance, so provide values we have in our
743 : // parameters for those:
744 4918 : uint32_t runtime = packet.runtime;
745 4918 : if (runtime == UINT32_MAX) {
746 4918 : runtime = total_runtime + runtime_delta_ms/1000;
747 : }
748 4918 : uint32_t maint_time = packet.until_maintenance;
749 4918 : if (maint_time == INT32_MAX) {
750 4918 : maint_time = time_until_maintenance - runtime_delta_ms/1000;
751 : }
752 :
753 4918 : mavlink_msg_generator_status_send(
754 : channel.get_chan(),
755 : status,
756 4918 : _rpm, // generator_speed
757 : packet.curr_batt, // Current into/out of battery
758 : packet.curr_gen, // load_current; Current going to UAV
759 4918 : (packet.curr_rot * _voltage), // power_generated (w)
760 : _voltage, // bus_voltage; Voltage of the bus seen at the generator
761 : rectifier_temp, // rectifier_temperature
762 4918 : std::numeric_limits<double>::quiet_NaN(), // bat_current_setpoint; The target battery current
763 : generator_temp, // generator temperature
764 : runtime, // runtime / time since boot,
765 : maint_time
766 : );
767 : }
768 :
769 : // methods to control the generator state:
770 12 : bool AP_Generator_Loweheiser::stop()
771 : {
772 12 : set_pilot_desired_runstate(PilotDesiredRunState::STOP);
773 12 : return true;
774 : }
775 :
776 8 : bool AP_Generator_Loweheiser::idle()
777 : {
778 8 : set_pilot_desired_runstate(PilotDesiredRunState::IDLE);
779 8 : return true;
780 : }
781 :
782 13 : bool AP_Generator_Loweheiser::run()
783 : {
784 13 : set_pilot_desired_runstate(PilotDesiredRunState::RUN);
785 13 : return true;
786 : }
787 :
788 : // log generator status to the onboard log
789 7410 : void AP_Generator_Loweheiser::Log_Write()
790 : {
791 : #if HAL_LOGGING_ENABLED
792 7410 : if (!AP::logger().should_log(MASK_LOG_ANY)) {
793 34 : return;
794 : }
795 :
796 : // only log new readings:
797 7376 : if (last_logged_reading_ms == last_packet_received_ms) {
798 3686 : return;
799 : }
800 3690 : last_logged_reading_ms = last_packet_received_ms;
801 :
802 : // @LoggerMessage: LOEG
803 : // @Description: Gathered Loweheiser EFI/Governor telemetry
804 : // @Field: TimeUS: Time since system startup
805 : // @Field: I: EFI/Gov sensor instance number
806 : // @Field: VB: battery voltage
807 : // @Field: CB: battery current
808 : // @Field: CG: generator current
809 : // @Field: Th: throttle input
810 : // @Field: EB: EFI battery voltage
811 : // @Field: RPM: generator RPM
812 : // @Field: PW: EFI pulse-width
813 : // @Field: FF: fuel flow
814 : // @Field: FC: fuel consumed
815 : // @Field: EP: EFI pressure
816 : // @Field: EMT: EFI manifold air temperature
817 : // @Field: CHT: cylinder head temperature
818 : // @Field: TPS: throttle position sensor
819 :
820 7380 : AP::logger().Write(
821 : "LOEG",
822 : "TimeUS," "I," "VB," "CB," "CG," "Th," "EB," "RPM," "PW," "FF," "FC," "EP," "EMT," "CHT," "TPS",
823 : "s" "#" "v" "A" "A" "%" "v" "q" "s" "y" "l" "P" "O" "O" "%" ,
824 : "F" "-" "-" "-" "-" "0" "0" "0" "F" "0" "0" "0" "0" "0" "0" ,
825 : "Q" "B" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" "f" ,
826 : AP_HAL::micros64(),
827 3690 : packet.efi_index,
828 3690 : packet.volt_batt,
829 3690 : packet.curr_batt,
830 3690 : packet.curr_gen,
831 3690 : packet.throttle,
832 3690 : packet.efi_batt,
833 3690 : packet.efi_rpm,
834 3690 : packet.efi_pw,
835 3690 : packet.efi_fuel_flow,
836 3690 : packet.efi_fuel_consumed,
837 3690 : packet.efi_baro,
838 3690 : packet.efi_mat,
839 3690 : packet.efi_clt,
840 3690 : packet.efi_tps
841 : );
842 : #endif // HAL_LOGGING_ENABLED
843 : }
844 :
845 : #endif
|