//
// Copyright 2015-2016 Ettus Research LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#include
#include
#include
#include
#include
#include "boost/date_time/gregorian/gregorian.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "gpsd_iface.hpp"
namespace uhd { namespace usrp {
static const size_t TIMEOUT = 240;
static const size_t CLICK_RATE = 250000;
class gpsd_iface_impl : public virtual gpsd_iface {
public:
gpsd_iface_impl(const std::string &addr, boost::uint16_t port)
: _detected(false), _bthread(), _timeout_cnt(0)
{
boost::unique_lock l(_d_mutex);
if (gps_open(addr.c_str(),
str(boost::format("%u") % port).c_str(),
&_gps_data) < 0) {
throw uhd::runtime_error(
str((boost::format("Failed to connect to gpsd: %s")
% gps_errstr(errno))));
}
// register for updates, we don't specify a specific device,
// therefore no WATCH_DEVICE
gps_stream(&_gps_data, WATCH_ENABLE, NULL);
// create background thread talking to gpsd
boost::thread t(boost::bind(&gpsd_iface_impl::_thread_fcn ,this));
_bthread.swap(t);
_sensors = boost::assign::list_of("gps_locked")("gps_time") \
("gps_position")("gps_gpgga")("gps_gprmc").to_container(_sensors);
}
virtual ~gpsd_iface_impl(void)
{
// interrupt the background thread and wait for it to finish
_bthread.interrupt();
_bthread.join();
// clean up ...
{
boost::unique_lock l(_d_mutex);
gps_stream(&_gps_data, WATCH_DISABLE, NULL);
gps_close(&_gps_data);
}
}
uhd::sensor_value_t get_sensor(std::string key)
{
if (key == "gps_locked") {
return sensor_value_t(
"GPS lock status", _gps_locked(), "locked", "unlocked");
} else if (key == "gps_time") {
return sensor_value_t(
"GPS epoch time", int(_epoch_time()), "seconds");
} else if (key == "gps_gpgga") {
return sensor_value_t(
"GPGGA", _gps_gpgga(), "");
} else if (key == "gps_gprmc") {
return sensor_value_t(
"GPRMC", _gps_gprmc(), "");
} else if (key == "gps_position") {
return sensor_value_t(
"GPS Position", str(
boost::format("%s %s %s")
% _gps_position()["lat"]
% _gps_position()["lon"]
% _gps_position()["alt"]), "lat/lon/alt");
} else
throw uhd::key_error(
str(boost::format("sensor %s unknown.") % key));
}
bool gps_detected(void) { return _detected; };
std::vector get_sensors(void) { return _sensors; };
private: // member functions
void _thread_fcn()
{
while (not boost::this_thread::interruption_requested()) {
if (!gps_waiting(&_gps_data, CLICK_RATE)) {
if (TIMEOUT < _timeout_cnt++)
_detected = false;
} else {
boost::unique_lock l(_d_mutex);
_timeout_cnt = 0;
_detected = true;
if (gps_read(&_gps_data) < 0)
throw std::runtime_error("error while reading");
}
}
}
bool _gps_locked(void)
{
boost::shared_lock l(_d_mutex);
return _gps_data.fix.mode >= MODE_2D;
}
std::time_t _epoch_time(void)
{
boost::shared_lock l(_d_mutex);
return (boost::posix_time::from_time_t(_gps_data.fix.time)
- boost::posix_time::from_time_t(0)).total_seconds();
}
boost::gregorian::date _gregorian_date(void)
{
boost::shared_lock l(_d_mutex);
return boost::posix_time::from_time_t(_gps_data.fix.time).date();
}
uhd::dict _gps_position(void)
{
boost::shared_lock l(_d_mutex);
uhd::dict tmp;
if (_gps_data.fix.mode >= MODE_2D) {
tmp["lon"] = str(boost::format("%f deg")
% _gps_data.fix.longitude);
tmp["lat"] = str(boost::format("%f deg")
% _gps_data.fix.latitude);
tmp["alt"] = str(boost::format("%fm")
% _gps_data.fix.altitude);
} else {
tmp["lon"] = "n/a";
tmp["lat"] = "n/a";
tmp["alt"] = "n/a";
}
return tmp;
}
float _zeroize(float x)
{
return boost::math::isnan(x) ? 0.0 : x;
}
int _nmea_checksum(const std::string &s)
{
if ((s.at(0) != '$'))
return 0;
boost::uint8_t sum = '\0';
for (size_t i = 1; i < s.size(); i++)
sum ^= static_cast(s.at(i));
return sum;
}
double _deg_to_dm(double angle)
{
double fraction, integer;
fraction = std::modf(angle, &integer);
return std::floor(angle) * 100 + fraction * 60;
}
std::string _gps_gprmc(void)
{
struct tm tm;
time_t intfixtime;
boost::shared_lock l(_d_mutex);
tm.tm_mday = tm.tm_mon = tm.tm_year = 0;
tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
if (boost::math::isnan(_gps_data.fix.time) == 0) {
intfixtime = (time_t) _gps_data.fix.time;
(void)gmtime_r(&intfixtime, &tm);
tm.tm_mon++;
tm.tm_year %= 100;
}
std::string string = str(boost::format(
"$GPRMC,%02d%02d%02d,%c,%09.4f,%c,%010.4f,%c,%.4f,%.3f,%02d%02d%02d,,")
% tm.tm_hour
% tm.tm_min
% tm.tm_sec
% (_gps_data.status ? 'A' : 'V')
% _zeroize(_deg_to_dm(std::fabs(_gps_data.fix.latitude)))
% ((_gps_data.fix.latitude > 0) ? 'N' : 'S')
% _zeroize(_deg_to_dm(std::fabs(_gps_data.fix.longitude)))
% ((_gps_data.fix.longitude > 0) ? 'E' : 'W')
% _zeroize(_gps_data.fix.speed * MPS_TO_KNOTS)
% _zeroize(_gps_data.fix.track)
% tm.tm_mday % tm.tm_mon % tm.tm_year);
string.append(str(
boost::format("*%02X") % _nmea_checksum(string)));
return string;
}
std::string _gps_gpgga(void)
{
struct tm tm;
time_t intfixtime;
// currently not supported, make it blank
float mag_var = NAN;
boost::shared_lock l(_d_mutex);
intfixtime = (time_t) _gps_data.fix.time;
(void) gmtime_r(&intfixtime, &tm);
std::string string = str(boost::format(
"$GPGGA,%02d%02d%02d,%09.4f,%c,%010.4f,%c,%d,%02d,")
% tm.tm_hour
% tm.tm_min
% tm.tm_sec
% _deg_to_dm(std::fabs(_gps_data.fix.latitude))
% ((_gps_data.fix.latitude > 0) ? 'N' : 'S')
% _deg_to_dm(std::fabs(_gps_data.fix.longitude))
% ((_gps_data.fix.longitude > 0) ? 'E' : 'W')
% _gps_data.status
% _gps_data.satellites_used);
if (boost::math::isnan(_gps_data.dop.hdop))
string.append(",");
else
string.append(
str(boost::format("%.2f,") % _gps_data.dop.hdop));
if (boost::math::isnan(_gps_data.fix.altitude))
string.append(",");
else
string.append(
str(boost::format("%.2f,M,") % _gps_data.fix.altitude));
if (boost::math::isnan(_gps_data.separation))
string.append(",");
else
string.append(
str(boost::format("%.3f,M,") % _gps_data.separation));
if (boost::math::isnan(mag_var))
string.append(",");
else {
string.append(
str(boost::format("%3.2f,%s") % std::fabs(mag_var)
% (mag_var > 0 ? "E" : "W")));
}
string.append(str(
boost::format("*%02X") % _nmea_checksum(string)));
return string;
}
private: // members
std::vector _sensors;
bool _detected;
gps_data_t _gps_data;
boost::shared_mutex _d_mutex;
boost::thread _bthread;
size_t _timeout_cnt;
};
}} //namespace
using namespace uhd::usrp;
gpsd_iface::sptr gpsd_iface::make(const std::string &addr, const boost::uint16_t port)
{
return gpsd_iface::sptr(new gpsd_iface_impl(addr, port));
}