Goby3 3.2.3
2025.05.13
Loading...
Searching...
No Matches
benthos_atm900_driver_fsm.h
Go to the documentation of this file.
1// Copyright 2016-2023:
2// GobySoft, LLC (2013-)
3// Community contributors (see AUTHORS file)
4// File authors:
5// Toby Schneider <toby@gobysoft.org>
6// Brandon Zoss <brandon.zoss@str.us>
7//
8//
9// This file is part of the Goby Underwater Autonomy Project Libraries
10// ("The Goby Libraries").
11//
12// The Goby Libraries are free software: you can redistribute them and/or modify
13// them under the terms of the GNU Lesser General Public License as published by
14// the Free Software Foundation, either version 2.1 of the License, or
15// (at your option) any later version.
16//
17// The Goby Libraries are distributed in the hope that they will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU Lesser General Public License for more details.
21//
22// You should have received a copy of the GNU Lesser General Public License
23// along with Goby. If not, see <http://www.gnu.org/licenses/>.
24
25#ifndef GOBY_ACOMMS_MODEMDRIVER_BENTHOS_ATM900_DRIVER_FSM_H
26#define GOBY_ACOMMS_MODEMDRIVER_BENTHOS_ATM900_DRIVER_FSM_H
27
28#include <iostream> // for basic_ostrea...
29#include <memory> // for allocator
30#include <string> // for string, oper...
31#include <utility> // for move, pair
32
33#include <boost/circular_buffer.hpp> // for circular_buffer
34#include <boost/date_time/date.hpp> // for date<>::day_...
35#include <boost/date_time/gregorian_calendar.hpp> // for gregorian_ca...
36#include <boost/date_time/posix_time/ptime.hpp> // for ptime
37#include <boost/date_time/time.hpp> // for base_time<>:...
38#include <boost/date_time/time_system_counted.hpp> // for counted_time...
39#include <boost/format.hpp> // for basic_altstr...
40#include <boost/lexical_cast/bad_lexical_cast.hpp> // for bad_lexical_...
41#include <boost/mpl/list.hpp>
42#include <boost/smart_ptr/intrusive_ptr.hpp> // for intrusive_ptr
43#include <boost/statechart/custom_reaction.hpp>
44#include <boost/statechart/deep_history.hpp>
45#include <boost/statechart/event.hpp> // for event
46#include <boost/statechart/event_base.hpp> // for event_base
47#include <boost/statechart/in_state_reaction.hpp> // for in_state_rea...
48#include <boost/statechart/result.hpp> // for reaction_result
49#include <boost/statechart/simple_state.hpp> // for simple_state...
50#include <boost/statechart/state.hpp> // for state, state...
51#include <boost/statechart/state_machine.hpp> // for state_machine
52#include <boost/statechart/transition.hpp> // for transition
53
54#include "goby/acomms/acomms_constants.h" // for BROADCAST_ID
55#include "goby/acomms/protobuf/benthos_atm900.pb.h" // for Config, Mess...
56#include "goby/acomms/protobuf/driver_base.pb.h" // for DriverConfig
57#include "goby/acomms/protobuf/modem_message.pb.h" // for ModemTransmi...
58#include "goby/time/convert.h" // for SystemClock:...
59#include "goby/time/system_clock.h" // for SystemClock
60#include "goby/util/as.h" // for as
61#include "goby/util/debug_logger/flex_ostream.h" // for FlexOstream
62#include "goby/util/debug_logger/flex_ostreambuf.h" // for DEBUG1
63#include "goby/util/debug_logger/logger_manipulators.h" // for operator<<
64
65namespace goby
66{
67namespace acomms
68{
69namespace benthos
70{
71namespace fsm
72{
73// events
75{
76 std::string line;
77};
79{
80};
81
83{
84 EvAck(std::string response) : response_(std::move(response)) {}
85
86 std::string response_;
87};
88
90{
91};
93{
94};
95
97{
98 EvDial(int dest, int rate) : dest_(dest), rate_(rate) {}
99
100 int dest_;
101 int rate_;
102};
103
105{
106 EvRange(int dest) : dest_(dest) {}
107 int dest_;
108};
109
111{
112};
114{
115};
116
118{
119};
121{
122};
123
125{
126};
127
129{
130};
131
133{
134 EvReceive(std::string first) : first_(std::move(first)) {}
135 std::string first_;
136};
137
139{
140};
142{
143};
145{
146};
147
148struct Active;
149struct ReceiveData;
150struct Command;
151struct Ready;
152struct Configure;
153struct SetClock;
154struct Dial;
155struct LowPower;
156struct Range;
157struct Online;
158struct Listen;
159struct TransmitData;
160
161// state machine
162struct BenthosATM900FSM : boost::statechart::state_machine<BenthosATM900FSM, Active>
163{
164 public:
166 : serial_tx_buffer_(SERIAL_BUFFER_CAPACITY),
167 received_(RECEIVED_BUFFER_CAPACITY),
168 driver_cfg_(driver_cfg),
169 data_out_(DATA_BUFFER_CAPACITY)
170 {
171 ++count_;
172 glog_fsm_group_ = "benthosatm900::fsm::" + goby::util::as<std::string>(count_);
173 }
174
176
177 // messages for the serial line at next opportunity
178 boost::circular_buffer<std::string>& serial_tx_buffer() { return serial_tx_buffer_; }
179
180 // received messages to be passed up out of the ModemDriver
181 boost::circular_buffer<goby::acomms::protobuf::ModemTransmission>& received()
182 {
183 return received_;
184 }
185
186 // data that should (eventually) be sent out across the connection
187 boost::circular_buffer<goby::acomms::protobuf::ModemTransmission>& data_out()
188 {
189 return data_out_;
190 }
191
192 const goby::acomms::protobuf::DriverConfig& driver_cfg() const { return driver_cfg_; }
197
198 const std::string& glog_fsm_group() const { return glog_fsm_group_; }
199
200 private:
201 enum
202 {
203 SERIAL_BUFFER_CAPACITY = 10
204 };
205 enum
206 {
207 RECEIVED_BUFFER_CAPACITY = 10
208 };
209
210 boost::circular_buffer<std::string> serial_tx_buffer_;
211 boost::circular_buffer<goby::acomms::protobuf::ModemTransmission> received_;
212 const goby::acomms::protobuf::DriverConfig& driver_cfg_;
213
214 enum
215 {
216 DATA_BUFFER_CAPACITY = 5
217 };
218 boost::circular_buffer<goby::acomms::protobuf::ModemTransmission> data_out_;
219
220 std::string glog_fsm_group_;
221
222 static int count_;
223};
224
226{
227 StateNotify(std::string name) : name_(std::move(name))
228 {
229 glog.is(goby::util::logger::DEBUG1) && glog << group("benthosatm900::fsm") << name_
230 << std::endl;
231 }
233 {
234 glog.is(goby::util::logger::DEBUG1) && glog << group("benthosatm900::fsm") << "~" << name_
235 << std::endl;
236 }
237
238 private:
239 std::string name_;
240};
241
242// Active
243struct Active : boost::statechart::simple_state<Active, BenthosATM900FSM, Command,
244 boost::statechart::has_deep_history>,
246{
247 Active() : StateNotify("Active") {}
248 ~Active() override = default;
249
251
252 typedef boost::mpl::list<
253 boost::statechart::transition<EvReset, Active>,
254 boost::statechart::in_state_reaction<EvRxSerial, Active, &Active::in_state_react>,
255 boost::statechart::transition<EvReceive, ReceiveData> >
257};
258
259struct ReceiveData : boost::statechart::state<ReceiveData, BenthosATM900FSM>, StateNotify
260{
261 ReceiveData(my_context ctx);
262 ~ReceiveData() override = default;
263
265
266 using reactions = boost::mpl::list<
267 boost::statechart::in_state_reaction<EvRxSerial, ReceiveData, &ReceiveData::in_state_react>,
268 boost::statechart::transition<EvReceiveComplete, boost::statechart::deep_history<Command>>>;
269
272 std::string encoded_bytes_; // still base 255 encoded
273};
274
275// Command
276struct Command : boost::statechart::simple_state<Command, Active, Configure,
277 boost::statechart::has_deep_history>,
279{
280 public:
281 void in_state_react(const EvAck&);
283 boost::statechart::result react(const EvConnect&)
284 {
285 if (at_out_.empty() || at_out_.front().second != "+++")
286 {
287 at_out_.clear();
288 return transit<Online>();
289 }
290 else
291 {
292 // connect when trying to send "+++"
293 return discard_event();
294 }
295 }
296
297 Command() : StateNotify("Command"), at_out_(AT_BUFFER_CAPACITY)
298 {
299 // in case we start up in Online mode - likely as the @OpMode=1 is the default
300 context<Command>().push_at_command("+++");
301 // the modem seems to like to reset the OpMode
302 context<Command>().push_clam_command("@OpMode=0");
303 }
304 ~Command() override = default;
305
306 using reactions = boost::mpl::list<
307 boost::statechart::custom_reaction<EvConnect>,
308 boost::statechart::in_state_reaction<EvAck, Command, &Command::in_state_react>,
309 boost::statechart::in_state_reaction<EvTxSerial, Command, &Command::in_state_react>>;
310
312 {
313 ATSentenceMeta() = default;
315 int tries_{0};
316 };
317
318 void push_at_command(std::string cmd)
319 {
320 if (cmd != "+++")
321 cmd = "AT" + cmd;
322
323 at_out_.push_back(std::make_pair(ATSentenceMeta(), cmd));
324 }
325 void push_clam_command(const std::string& cmd)
326 {
327 at_out_.push_back(std::make_pair(ATSentenceMeta(), cmd));
328 }
329
330 boost::circular_buffer<std::pair<ATSentenceMeta, std::string> >& at_out() { return at_out_; }
331
332 private:
333 enum
334 {
335 AT_BUFFER_CAPACITY = 100
336 };
337 boost::circular_buffer<std::pair<ATSentenceMeta, std::string> > at_out_;
338 enum
339 {
340 COMMAND_TIMEOUT_SECONDS = 2
341 };
342
343 enum
344 {
345 RETRIES_BEFORE_RESET = 10
346 };
347};
348
349struct Configure : boost::statechart::state<Configure, Command>, StateNotify
350{
351 using reactions = boost::mpl::list<boost::statechart::transition<EvAtEmpty, SetClock>>;
352
353 Configure(my_context ctx) : my_base(std::move(ctx)), StateNotify("Configure")
354 {
355 context<Command>().push_at_command("");
356
357 // disable local echo to avoid confusing our parser
358 context<Command>().push_clam_command("@P1EchoChar=Dis");
359
360 if (context<BenthosATM900FSM>().benthos_driver_cfg().factory_reset())
361 context<Command>().push_clam_command("factory_reset");
362
363 if (context<BenthosATM900FSM>().benthos_driver_cfg().has_config_load())
364 {
365 context<Command>().push_clam_command(
366 "cfg load " + context<BenthosATM900FSM>().benthos_driver_cfg().config_load());
367 }
368
369 for (int i = 0, n = context<BenthosATM900FSM>().benthos_driver_cfg().config_size(); i < n;
370 ++i)
371 {
372 context<Command>().push_clam_command(
373 context<BenthosATM900FSM>().benthos_driver_cfg().config(i));
374 }
375
376 // ensure serial output is the format we expect
377 context<Command>().push_clam_command("@P1Prompt=7");
378 context<Command>().push_clam_command("@Verbose=3");
379
380 // Goby will handle retries
381 context<Command>().push_clam_command("@DataRetry=0");
382
383 // Send the data immediately after we post it
384 context<Command>().push_clam_command("@FwdDelay=0.05");
385 context<Command>().push_clam_command(
386 "@LocalAddr=" +
387 goby::util::as<std::string>(context<BenthosATM900FSM>().driver_cfg().modem_id()));
388
389 // Hex format for data
390 context<Command>().push_clam_command("@PrintHex=Ena");
391
392 // Wake tones are required so the modem will resume from low power at packet receipt
393 context<Command>().push_clam_command("@WakeTones=Ena");
394
395 // Receive all packets, let Goby deal with discarding them
396 context<Command>().push_clam_command("@RcvAll=Ena");
397
398 // Show data for bad packets so we can stats
399 context<Command>().push_clam_command("@ShowBadData=Ena");
400
401 // start up in Command mode after reboot/lowpower resume
402 context<Command>().push_clam_command("@OpMode=0");
403
404 // store the current configuration for later inspection
405 // context<Command>().push_clam_command("cfg store /ffs/goby.ini");
406 }
407
408 ~Configure() override = default;
409};
410
412{
413 using reactions = boost::mpl::list<boost::statechart::transition<EvAtEmpty, Ready>>;
414
415 SetClock(my_context ctx) : my_base(std::move(ctx)), StateNotify("SetClock")
416 {
417 auto p = time::SystemClock::now<boost::posix_time::ptime>();
418
419 std::string date_str = boost::str(boost::format("-d%02d/%02d/%04d") %
420 (int)p.date().month() % p.date().day() % p.date().year());
421 std::string time_str =
422 boost::str(boost::format("-t%02d:%02d:%02d") % p.time_of_day().hours() %
423 p.time_of_day().minutes() % p.time_of_day().seconds());
424
425 context<Command>().push_clam_command("date " + time_str + " " + date_str);
426 }
427
428 ~SetClock() override = default;
429};
430
432{
433 private:
434 void in_state_react(const EvRequestLowPower&) { context<Command>().push_at_command("L"); }
435
436 public:
437 Ready() : StateNotify("Ready") {}
438 ~Ready() override = default;
439
440 using reactions = boost::mpl::list<
441 boost::statechart::transition<EvDial, Dial>, boost::statechart::transition<EvRange, Range>,
442 boost::statechart::in_state_reaction<EvRequestLowPower, Ready, &Ready::in_state_react>,
443 boost::statechart::transition<EvLowPower, LowPower>>;
444};
445
447{
448 enum
449 {
451 };
452 enum
453 {
456 RATE_MAX = 13
457 };
458
459 Dial(my_context ctx)
460 : my_base(std::move(ctx)),
461 StateNotify("Dial"),
463 rate_(DEFAULT_RATE)
464 {
465 if (const auto* evdial = dynamic_cast<const EvDial*>(triggering_event()))
466 {
467 dest_ = evdial->dest_;
468 if (dest_ == goby::acomms::BROADCAST_ID)
469 dest_ = BENTHOS_BROADCAST_ID;
470
471 if (evdial->rate_ >= RATE_MIN && evdial->rate_ <= RATE_MAX)
472 rate_ = evdial->rate_;
473 }
474 context<Command>().push_clam_command("@RemoteAddr=" + goby::util::as<std::string>(dest_));
475 context<Command>().push_clam_command("@TxRate=" + goby::util::as<std::string>(rate_));
476 context<Command>().push_at_command("O");
477 }
478 ~Dial() override = default;
479
480 private:
481 int dest_;
482 int rate_;
483};
484
486{
487 LowPower(my_context ctx) : my_base(std::move(ctx)), StateNotify("LowPower") {}
488 ~LowPower() override = default;
489};
490
492{
494
495 Range(my_context ctx) : my_base(std::move(ctx)), StateNotify("Range"), dest_(0)
496 {
497 if (const auto* ev = dynamic_cast<const EvRange*>(triggering_event()))
498 {
499 dest_ = ev->dest_;
500 }
501 context<Command>().push_at_command("R" + goby::util::as<std::string>(dest_));
502 }
503 ~Range() override = default;
504
505 using reactions = boost::mpl::list<
506 boost::statechart::transition<EvRangingComplete, Ready>,
507 boost::statechart::in_state_reaction<EvRxSerial, Range, &Range::in_state_react>>;
508
509 private:
510 int dest_;
511};
512
513// Online
514struct Online : boost::statechart::state<Online, Active, Listen>, StateNotify
515{
516 Online(my_context ctx) : my_base(std::move(ctx)), StateNotify("Online") {}
517 ~Online() override = default;
518
519 using reactions = boost::mpl::list<
520 boost::statechart::transition<EvShellPrompt, boost::statechart::deep_history<Command>>>;
521};
522
524{
525 Listen(my_context ctx) : my_base(std::move(ctx)), StateNotify("Listen")
526 {
527 if (!context<BenthosATM900FSM>().data_out().empty())
529 }
530 ~Listen() override = default;
531
532 using reactions = boost::mpl::list<boost::statechart::transition<EvTransmit, TransmitData>>;
533};
534
535struct TransmitData : boost::statechart::state<TransmitData, Online>, StateNotify
536{
537 TransmitData(my_context ctx) : my_base(std::move(ctx)), StateNotify("TransmitData") {}
538 ~TransmitData() override = default;
539
541 void in_state_react(const EvAck&);
542
543 using reactions = boost::mpl::list<
544 boost::statechart::transition<EvTransmitBegun, Ready>,
545 boost::statechart::in_state_reaction<EvTxSerial, TransmitData,
547 boost::statechart::in_state_reaction<EvAck, TransmitData, &TransmitData::in_state_react>>;
548};
549
550} // namespace fsm
551} // namespace benthos
552} // namespace acomms
553} // namespace goby
554
556
557#endif
const event_base * triggering_event() const
void post_event(const event_base_ptr_type &pEvent)
_proto_TypeTraits::Singular::ConstType GetExtension(const ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< DriverConfig, _proto_TypeTraits, _field_type, _is_packed > &id) const
bool is(goby::util::logger::Verbosity verbosity)
goby::util::logger::GroupSetter group(std::string n)
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::goby::acomms::protobuf::DriverConfig, ::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::goby::acomms::benthos::protobuf::Config >, 11, false > config
constexpr int BROADCAST_ID
special modem id for the broadcast destination - no one is assigned this address. Analogous to intern...
The global namespace for the Goby project.
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::PROTOBUF_NAMESPACE_ID::EnumValueOptions, ::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::goby::GobyEnumValueOptions >, 11, false > ev
extern ::PROTOBUF_NAMESPACE_ID::internal::ExtensionIdentifier< ::PROTOBUF_NAMESPACE_ID::MessageOptions, ::PROTOBUF_NAMESPACE_ID::internal::MessageTypeTraits< ::goby::GobyMessageOptions >, 11, false > msg
util::FlexOstream glog
Access the Goby logger through this object.
STL namespace.
void in_state_react(const EvRxSerial &)
boost::mpl::list< boost::statechart::transition< EvReset, Active >, boost::statechart::in_state_reaction< EvRxSerial, Active, &Active::in_state_react >, boost::statechart::transition< EvReceive, ReceiveData > > reactions
void buffer_data_out(const goby::acomms::protobuf::ModemTransmission &msg)
const goby::acomms::protobuf::DriverConfig & driver_cfg() const
boost::circular_buffer< std::string > & serial_tx_buffer()
boost::circular_buffer< goby::acomms::protobuf::ModemTransmission > & received()
BenthosATM900FSM(const goby::acomms::protobuf::DriverConfig &driver_cfg)
const goby::acomms::benthos::protobuf::Config & benthos_driver_cfg() const
boost::circular_buffer< goby::acomms::protobuf::ModemTransmission > & data_out()
boost::mpl::list< boost::statechart::custom_reaction< EvConnect >, boost::statechart::in_state_reaction< EvAck, Command, &Command::in_state_react >, boost::statechart::in_state_reaction< EvTxSerial, Command, &Command::in_state_react > > reactions
boost::statechart::result react(const EvConnect &)
void in_state_react(const EvTxSerial &)
boost::circular_buffer< std::pair< ATSentenceMeta, std::string > > & at_out()
void push_clam_command(const std::string &cmd)
void in_state_react(const EvAck &)
boost::mpl::list< boost::statechart::transition< EvAtEmpty, SetClock > > reactions
boost::mpl::list< boost::statechart::transition< EvTransmit, TransmitData > > reactions
boost::mpl::list< boost::statechart::transition< EvShellPrompt, boost::statechart::deep_history< Command > > > reactions
boost::mpl::list< boost::statechart::transition< EvRangingComplete, Ready >, boost::statechart::in_state_reaction< EvRxSerial, Range, &Range::in_state_react > > reactions
void in_state_react(const EvRxSerial &)
boost::mpl::list< boost::statechart::transition< EvDial, Dial >, boost::statechart::transition< EvRange, Range >, boost::statechart::in_state_reaction< EvRequestLowPower, Ready, &Ready::in_state_react >, boost::statechart::transition< EvLowPower, LowPower > > reactions
goby::acomms::protobuf::ModemTransmission rx_msg_
void in_state_react(const EvRxSerial &)
boost::mpl::list< boost::statechart::in_state_reaction< EvRxSerial, ReceiveData, &ReceiveData::in_state_react >, boost::statechart::transition< EvReceiveComplete, boost::statechart::deep_history< Command > > > reactions
boost::mpl::list< boost::statechart::transition< EvAtEmpty, Ready > > reactions
boost::mpl::list< boost::statechart::transition< EvTransmitBegun, Ready >, boost::statechart::in_state_reaction< EvTxSerial, TransmitData, &TransmitData::in_state_react >, boost::statechart::in_state_reaction< EvAck, TransmitData, &TransmitData::in_state_react > > reactions
void in_state_react(const EvTxSerial &)