Goby v2
mac_manager.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Goby Underwater Autonomy Project Libraries
8 // ("The Goby Libraries").
9 //
10 // The Goby Libraries are free software: you can redistribute them and/or modify
11 // them under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // The Goby Libraries are distributed in the hope that they will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
22 
23 #include <cmath>
24 #include <iostream>
25 
26 #include <boost/bind.hpp>
27 #include <boost/date_time/gregorian/gregorian_types.hpp>
28 #include <boost/foreach.hpp>
29 
30 #include "goby/acomms/acomms_helpers.h"
31 #include "goby/common/logger.h"
32 
33 #include "mac_manager.h"
34 
36 using goby::util::as;
37 using namespace goby::common::tcolor;
38 using namespace goby::common::logger;
39 using goby::glog;
40 
41 int goby::acomms::MACManager::count_;
42 
44  : timer_(io_), work_(io_), current_slot_(std::list<protobuf::ModemTransmission>::begin()),
45  started_up_(false)
46 {
47  ++count_;
48 
49  glog_mac_group_ = "goby::acomms::amac::" + as<std::string>(count_);
50  goby::glog.add_group(glog_mac_group_, common::Colors::blue);
51 }
52 
53 goby::acomms::MACManager::~MACManager() {}
54 
55 void goby::acomms::MACManager::restart_timer()
56 {
57  // cancel any old timer jobs waiting
58  timer_.cancel();
59  timer_.expires_at(next_slot_t_);
60  timer_.async_wait(boost::bind(&MACManager::begin_slot, this, _1));
61 }
62 
63 void goby::acomms::MACManager::stop_timer() { timer_.cancel(); }
64 
66 {
67  cfg_ = cfg;
68 
69  switch (cfg_.type())
70  {
71  case protobuf::MAC_POLLED:
72  case protobuf::MAC_FIXED_DECENTRALIZED:
73  std::list<protobuf::ModemTransmission>::clear();
74  for (int i = 0, n = cfg_.slot_size(); i < n; ++i)
75  {
76  protobuf::ModemTransmission slot = cfg_.slot(i);
77  slot.set_slot_index(i);
78  std::list<protobuf::ModemTransmission>::push_back(slot);
79  }
80 
81  if (cfg_.type() == protobuf::MAC_POLLED)
82  glog.is(DEBUG1) && glog << group(glog_mac_group_)
83  << "Using the Centralized Polling MAC_POLLED scheme"
84  << std::endl;
85  else if (cfg_.type() == protobuf::MAC_FIXED_DECENTRALIZED)
86  glog.is(DEBUG1) && glog << group(glog_mac_group_)
87  << "Using the Decentralized MAC_FIXED_DECENTRALIZED scheme"
88  << std::endl;
89  break;
90 
91  default: return;
92  }
93 
94  restart();
95 }
96 
98 {
99  glog.is(DEBUG1) && glog << group(glog_mac_group_)
100  << "Goby Acoustic Medium Access Control module starting up."
101  << std::endl;
102 
103  if (started_up_)
104  {
105  glog.is(DEBUG1) && glog << group(glog_mac_group_)
106  << " ... MAC is already started, not restarting." << std::endl;
107  return;
108  }
109 
110  started_up_ = true;
111 
112  update();
113 
114  glog.is(DEBUG1) && glog << group(glog_mac_group_)
115  << "the first MAC TDMA cycle begins at time: " << next_slot_t_
116  << std::endl;
117 }
118 
120 {
121  stop_timer();
122 
123  current_slot_ = std::list<protobuf::ModemTransmission>::begin();
124  started_up_ = false;
125 
126  glog.is(DEBUG1) && glog << group(glog_mac_group_)
127  << "the MAC cycle has been shutdown until restarted." << std::endl;
128 }
129 
130 void goby::acomms::MACManager::begin_slot(const boost::system::error_code& e)
131 {
132  // canceled the last timer
133  if (e == boost::asio::error::operation_aborted)
134  return;
135 
136  // check skew
137  if (std::abs(goby_time<double>() - goby::util::as<double>(next_slot_t_)) > ALLOWED_SKEW_SECONDS)
138  {
139  glog.is(DEBUG1) && glog << group(glog_mac_group_) << warn
140  << "Clock skew detected, updating MAC." << std::endl;
141  update();
142  return;
143  }
144 
145  protobuf::ModemTransmission s = *current_slot_;
146  s.set_time(goby::util::as<uint64>(next_slot_t_));
147 
148  bool we_are_transmitting = true;
149  switch (cfg_.type())
150  {
151  case protobuf::MAC_FIXED_DECENTRALIZED:
152  // we only transmit if the packet source is us
153  we_are_transmitting = (s.src() == cfg_.modem_id()) || s.always_initiate();
154  break;
155 
156  case protobuf::MAC_POLLED:
157  // we always transmit (poll)
158  // but be quiet in the case where src = 0
159  we_are_transmitting = (s.src() != BROADCAST_ID);
160  break;
161 
162  default: break;
163  }
164 
165  if (glog.is(DEBUG1))
166  {
167  glog << group(glog_mac_group_) << "Cycle order: [";
168 
169  for (std::list<protobuf::ModemTransmission>::iterator
170  it = std::list<protobuf::ModemTransmission>::begin(),
171  n = end();
172  it != n; ++it)
173  {
174  if (it == current_slot_)
175  glog << group(glog_mac_group_) << " " << green;
176 
177  switch (it->type())
178  {
179  case protobuf::ModemTransmission::DATA: glog << "d"; break;
180  case protobuf::ModemTransmission::DRIVER_SPECIFIC: glog << "s"; break;
181 
182  default: break;
183  }
184 
185  glog << it->src() << "/" << it->dest() << "@" << it->rate() << " " << nocolor;
186  }
187  glog << " ]" << std::endl;
188  }
189 
190  glog.is(DEBUG1) && glog << group(glog_mac_group_) << "Starting slot: " << s.ShortDebugString()
191  << std::endl;
192 
193  if (we_are_transmitting)
196 
197  increment_slot();
198 
199  glog.is(DEBUG1) && glog << group(glog_mac_group_) << "Next slot at " << next_slot_t_
200  << std::endl;
201 
202  restart_timer();
203 }
204 
205 void goby::acomms::MACManager::increment_slot()
206 {
207  switch (cfg_.type())
208  {
209  case protobuf::MAC_FIXED_DECENTRALIZED:
210  case protobuf::MAC_POLLED:
211  next_slot_t_ += boost::posix_time::microseconds(current_slot_->slot_seconds() * 1e6);
212 
213  ++current_slot_;
214  if (current_slot_ == std::list<protobuf::ModemTransmission>::end())
215  current_slot_ = std::list<protobuf::ModemTransmission>::begin();
216  break;
217 
218  default: break;
219  }
220 }
221 
222 boost::posix_time::ptime goby::acomms::MACManager::next_cycle_time()
223 {
224  using namespace boost::gregorian;
225  using namespace boost::posix_time;
226 
227  ptime now = goby_time();
228 
229  ptime reference;
230  switch (cfg_.ref_time_type())
231  {
232  case protobuf::MACConfig::REFERENCE_START_OF_DAY:
233  reference = ptime(now.date(), seconds(0));
234  break;
235  case protobuf::MACConfig::REFERENCE_FIXED:
236  reference = goby::common::unix_double2ptime(cfg_.fixed_ref_time());
237  break;
238  }
239 
240  time_duration duration_since_ref = now - reference;
241  goby::int64 microsec_since_reference =
242  duration_since_ref.total_seconds() * 1000000ll +
243  (duration_since_ref - seconds(duration_since_ref.total_seconds())).total_microseconds();
244 
245  glog.is(DEBUG2) && glog << group(glog_mac_group_) << "reference: " << reference << std::endl;
246 
247  glog.is(DEBUG2) && glog << group(glog_mac_group_)
248  << "microseconds since reference: " << microsec_since_reference
249  << std::endl;
250 
251  glog.is(DEBUG2) && glog << group(glog_mac_group_) << "cycle duration: " << cycle_duration()
252  << std::endl;
253 
254  cycles_since_reference_ = microsec_since_reference / (cycle_duration() * 1000000) + 1;
255 
256  glog.is(DEBUG2) && glog << group(glog_mac_group_)
257  << "cycles since reference: " << cycles_since_reference_ << std::endl;
258 
259  double secs_to_next = cycles_since_reference_ * cycle_duration();
260 
261  return reference + seconds(secs_to_next) +
262  microseconds((secs_to_next - floor(secs_to_next)) * 1000000);
263 }
264 
266 {
267  glog.is(DEBUG1) && glog << group(glog_mac_group_) << "Updating MAC cycle." << std::endl;
268 
269  if (std::list<protobuf::ModemTransmission>::size() == 0)
270  {
271  glog.is(DEBUG1) && glog << group(glog_mac_group_)
272  << "the MAC TDMA cycle is empty. Stopping timer" << std::endl;
273  stop_timer();
274  return;
275  }
276 
277  // reset the cycle to the beginning
278  current_slot_ = std::list<protobuf::ModemTransmission>::begin();
279  // advance the next slot time to the beginning of the next cycle
280  next_slot_t_ = next_cycle_time();
281 
282  glog.is(DEBUG1) && glog << group(glog_mac_group_)
283  << "The next MAC TDMA cycle begins at time: " << next_slot_t_
284  << std::endl;
285 
286  // if we can start cycles in the middle, do it
287  if (cfg_.start_cycle_in_middle() && std::list<protobuf::ModemTransmission>::size() > 1 &&
288  (cfg_.type() == protobuf::MAC_FIXED_DECENTRALIZED || cfg_.type() == protobuf::MAC_POLLED))
289  {
290  glog.is(DEBUG1) && glog << group(glog_mac_group_)
291  << "Starting next available slot (in middle of cycle)" << std::endl;
292 
293  // step back a cycle
294  next_slot_t_ -= boost::posix_time::microseconds(cycle_duration() * 1e6);
295 
296  boost::posix_time::ptime now = goby_time();
297 
298  // skip slots until we're at a slot that is in the future
299  while (next_slot_t_ < now) increment_slot();
300 
301  glog.is(DEBUG1) && glog << group(glog_mac_group_) << "Next slot at " << next_slot_t_
302  << std::endl;
303  }
304 
305  if (started_up_)
306  restart_timer();
307 }
308 
309 double goby::acomms::MACManager::cycle_duration()
310 {
311  double length = 0;
312  BOOST_FOREACH (const protobuf::ModemTransmission& slot, *this)
313  length += slot.slot_seconds();
314 
315  return length;
316 }
Contains functions for adding color to Terminal window streams.
Definition: term_color.h:54
void startup(const protobuf::MACConfig &cfg)
Starts the MAC with given configuration.
Definition: mac_manager.cpp:65
STL namespace.
ReturnType goby_time()
Returns current UTC time as a boost::posix_time::ptime.
Definition: time.h:104
const int BROADCAST_ID
special modem id for the broadcast destination - no one is assigned this address. Analogous to 192...
void add_group(const std::string &name, Colors::Color color=Colors::nocolor, const std::string &description="")
Add another group to the logger. A group provides related manipulator for categorizing log messages...
google::protobuf::int64 int64
a signed 64 bit integer
MACManager()
Default constructor.
Definition: mac_manager.cpp:43
void shutdown()
Shutdown the MAC.
common::FlexOstream glog
Access the Goby logger through this object.
boost::signals2::signal< void(const protobuf::ModemTransmission &m)> signal_slot_start
Signals the start of all transmissions (even when we don&#39;t transmit)
Definition: mac_manager.h:97
std::ostream & green(std::ostream &os)
All text following this manipulator is green (e.g. std::cout << green << "text";) ...
Definition: term_color.h:68
void restart()
Restarts the MAC with original configuration.
Definition: mac_manager.cpp:97
void update()
You must call this after any change to the underlying list that would invalidate iterators or change ...
boost::posix_time::ptime unix_double2ptime(double given_time)
convert to boost date_time ptime from the number of seconds (including fractional) since 1/1/1970 0:0...
Definition: time.cpp:47
boost::signals2::signal< void(const protobuf::ModemTransmission &m)> signal_initiate_transmission
Signals when it is time for this platform to begin transmission of an acoustic message at the start o...
Definition: mac_manager.h:91
std::ostream & nocolor(std::ostream &os)
All text following this manipulator is uncolored (e.g. std::cout << green << "green" << nocolor << "u...
Definition: term_color.h:104