gps tracking reporter

This commit is contained in:
Добрый Ээх 2016-10-07 17:17:03 +03:00
parent a8067e8826
commit a65d86ebcf
18 changed files with 545 additions and 10 deletions

View file

@ -37,9 +37,9 @@ public:
// Version 0:
// Coordinates are truncated and stored as integers. All integers
// are written as varints.
template <typename Writer>
template <typename Writer, typename Collection>
static size_t SerializeDataPoints(uint32_t version, Writer & writer,
vector<DataPoint> const & points)
Collection const & points)
{
ASSERT_LESS_OR_EQUAL(version, kLatestVersion, ());
@ -78,8 +78,8 @@ public:
}
// Deserializes the points from |source| and appends them to |result|.
template <typename Source>
static void DeserializeDataPoints(uint32_t version, Source & src, vector<DataPoint> & result)
template <typename Source, typename Collection>
static void DeserializeDataPoints(uint32_t version, Source & src, Collection & result)
{
ASSERT_LESS_OR_EQUAL(version, kLatestVersion, ());

View file

@ -60,6 +60,7 @@
#include "platform/platform.hpp"
#include "platform/preferred_languages.hpp"
#include "platform/settings.hpp"
#include "platform/socket.hpp"
#include "coding/internal/file_data.hpp"
#include "coding/zip_reader.hpp"
@ -200,6 +201,8 @@ void Framework::OnLocationUpdate(GpsInfo const & info)
CallDrapeFunction(bind(&df::DrapeEngine::SetGpsInfo, _1, rInfo,
m_routingSession.IsNavigable(), routeMatchingInfo));
if (IsTrackingReporterEnabled())
m_trackingReporter.AddLocation(info);
}
void Framework::OnCompassUpdate(CompassInfo const & info)
@ -224,6 +227,19 @@ void Framework::SetMyPositionModeListener(TMyPositionModeChanged && fn)
m_myPositionListener = move(fn);
}
bool Framework::IsTrackingReporterEnabled() const
{
if (m_currentRouterType != routing::RouterType::Vehicle)
return false;
if (!m_routingSession.IsOnRoute())
return false;
bool allowStat = false;
settings::Get(tracking::Reporter::kAllowKey, allowStat);
return allowStat;
}
void Framework::OnUserPositionChanged(m2::PointD const & position)
{
MyPositionMarkPoint * myPosition = UserMarkContainer::UserMarkForMyPostion();
@ -314,6 +330,7 @@ Framework::Framework()
, m_storage(platform::migrate::NeedMigrate() ? COUNTRIES_OBSOLETE_FILE : COUNTRIES_FILE)
, m_bmManager(*this)
, m_isRenderingEnabled(true)
, m_trackingReporter(platform::createMockSocket(), tracking::Reporter::kPushDelayMs)
, m_displacementModeManager([this](bool show) {
int const mode = show ? dp::displacement::kHotelMode : dp::displacement::kDefaultMode;
CallDrapeFunction(bind(&df::DrapeEngine::SetDisplacementMode, _1, mode));

View file

@ -33,6 +33,8 @@
#include "storage/downloading_policy.hpp"
#include "storage/storage.hpp"
#include "tracking/reporter.hpp"
#include "partners_api/booking_api.hpp"
#include "partners_api/uber_api.hpp"
@ -161,6 +163,7 @@ protected:
uber::Api m_uberApi;
bool m_isRenderingEnabled;
tracking::Reporter m_trackingReporter;
/// This function will be called by m_storage when latest local files
/// is downloaded.
@ -398,6 +401,7 @@ public:
void SetMyPositionModeListener(location::TMyPositionModeChanged && fn);
private:
bool IsTrackingReporterEnabled() const;
void OnUserPositionChanged(m2::PointD const & position);
//@}

View file

@ -6,7 +6,7 @@ CONFIG -= app_bundle
TEMPLATE = app
ROOT_DIR = ../..
DEPENDENCIES = map drape_frontend routing search storage drape indexer partners_api platform editor geometry coding base \
DEPENDENCIES = map drape_frontend routing search storage tracking drape indexer partners_api platform editor geometry coding base \
freetype fribidi expat protobuf tomcrypt jansson osrm stats_client minizip succinct pugixml stats_client
DEPENDENCIES *= opening_hours

View file

@ -1,7 +1,7 @@
# mapshot binary
ROOT_DIR = ..
DEPENDENCIES = map drape_frontend routing search storage indexer drape partners_api platform editor geometry coding base \
DEPENDENCIES = map drape_frontend routing search storage tracking indexer drape partners_api platform editor geometry coding base \
freetype expat fribidi tomcrypt gflags jansson protobuf osrm stats_client minizip succinct \
pugixml opening_hours

View file

@ -26,7 +26,7 @@ HEADERS += defines.hpp
SUBDIRS = 3party base coding geometry editor indexer routing search
!CONFIG(osrm) {
SUBDIRS *= platform stats storage
SUBDIRS *= platform stats storage tracking
CONFIG(desktop) {
SUBDIRS *= generator
@ -248,5 +248,10 @@ SUBDIRS = 3party base coding geometry editor indexer routing search
partners_api_tests.subdir = partners_api/partners_api_tests
partners_api_tests.depends = base platform partners_api
SUBDIRS *= partners_api_tests
tracking_tests.subdir = tracking/tracking_tests
tracking_tests.depends = base platform tracking
SUBDIRS *= tracking_tests
} # !no-tests
} # !gtool

View file

@ -105,3 +105,4 @@ SOURCES += \
preferred_languages.cpp \
servers_list.cpp \
settings.cpp \
socket.cpp \

122
platform/socket.cpp Normal file
View file

@ -0,0 +1,122 @@
#include "socket.hpp"
#include "base/assert.hpp"
#include "std/algorithm.hpp"
#include "std/deque.hpp"
#include "std/mutex.hpp"
namespace
{
class MockSocket final : public platform::Socket
{
public:
// Socket overrides
bool Open(string const & host, uint16_t port) override { return false; }
void Close() override {}
bool Read(uint8_t * data, uint32_t count) override { return false; }
bool Write(uint8_t const * data, uint32_t count) override { return false; }
void SetTimeout(uint32_t milliseconds) override {}
};
class TestSocketImpl final : public platform::TestSocket
{
public:
// Socket overrides
~TestSocketImpl();
bool Open(string const & host, uint16_t port) override;
void Close() override;
bool Read(uint8_t * data, uint32_t count) override;
bool Write(uint8_t const * data, uint32_t count) override;
void SetTimeout(uint32_t milliseconds) override;
// TestSocket overrides
bool HasInput() const override;
bool HasOutput() const override;
void WriteServer(uint8_t const * data, uint32_t count) override;
size_t ReadServer(vector<uint8_t> & destination) override;
private:
bool m_isConnected = false;
deque<uint8_t> m_input;
mutable mutex m_inputMutex;
vector<uint8_t> m_output;
mutable mutex m_outputMutex;
};
TestSocketImpl::~TestSocketImpl() { m_isConnected = false; }
bool TestSocketImpl::Open(string const & host, uint16_t port)
{
m_isConnected = true;
return true;
}
void TestSocketImpl::Close() { m_isConnected = false; }
bool TestSocketImpl::Read(uint8_t * data, uint32_t count)
{
if (!m_isConnected)
return false;
lock_guard<mutex> lg(m_inputMutex);
if (m_input.size() < count)
return false;
copy(m_input.begin(), m_input.end(), data);
m_input.erase(m_input.begin(), m_input.begin() + count);
return true;
}
bool TestSocketImpl::Write(uint8_t const * data, uint32_t count)
{
if (!m_isConnected)
return false;
lock_guard<mutex> lg(m_outputMutex);
m_output.insert(m_output.end(), data, data + count);
return true;
}
void TestSocketImpl::SetTimeout(uint32_t milliseconds) {}
bool TestSocketImpl::HasInput() const
{
lock_guard<mutex> lg(m_inputMutex);
return !m_input.empty();
}
bool TestSocketImpl::HasOutput() const
{
lock_guard<mutex> lg(m_outputMutex);
return !m_output.empty();
}
void TestSocketImpl::WriteServer(uint8_t const * data, uint32_t count)
{
ASSERT(m_isConnected, ());
lock_guard<mutex> lg(m_inputMutex);
m_input.insert(m_input.end(), data, data + count);
}
size_t TestSocketImpl::ReadServer(vector<uint8_t> & destination)
{
lock_guard<mutex> lg(m_outputMutex);
size_t const outputSize = m_output.size();
if (outputSize == 0)
return 0;
destination.insert(destination.end(), m_output.begin(), m_output.end());
m_output.clear();
return outputSize;
}
} // namespace
namespace platform
{
unique_ptr<Socket> createMockSocket() { return make_unique<MockSocket>(); }
unique_ptr<TestSocket> createTestSocket() { return make_unique<TestSocketImpl>(); }
} // namespace platform

View file

@ -1,8 +1,8 @@
#pragma once
#include "std/string.hpp"
#include "std/target_os.hpp"
#include "std/unique_ptr.hpp"
#include "std/vector.hpp"
namespace platform
{
@ -26,5 +26,21 @@ public:
virtual void SetTimeout(uint32_t milliseconds) = 0;
};
class TestSocket : public Socket
{
public:
virtual bool HasInput() const = 0;
virtual bool HasOutput() const = 0;
// Simulate server writing
virtual void WriteServer(uint8_t const * data, uint32_t count) = 0;
// Simulate server reading
// returns size of read data
virtual size_t ReadServer(vector<uint8_t> & destination) = 0;
};
unique_ptr<Socket> createSocket();
unique_ptr<Socket> createMockSocket();
unique_ptr<TestSocket> createTestSocket();
} // namespace platform

View file

@ -1,6 +1,6 @@
# Main application in qt.
ROOT_DIR = ..
DEPENDENCIES = map drape_frontend routing search storage indexer drape partners_api platform editor geometry \
DEPENDENCIES = map drape_frontend routing search storage tracking indexer drape partners_api platform editor geometry \
coding base freetype expat fribidi tomcrypt jansson protobuf osrm stats_client \
minizip succinct pugixml oauthcpp

View file

@ -6,7 +6,7 @@ CONFIG -= app_bundle
TEMPLATE = app
ROOT_DIR = ../..
DEPENDENCIES = map drape_frontend routing search storage indexer drape partners_api platform_tests_support platform editor opening_hours geometry \
DEPENDENCIES = map drape_frontend routing search storage tracking indexer drape partners_api platform_tests_support platform editor opening_hours geometry \
coding base freetype expat fribidi tomcrypt jansson protobuf osrm stats_client \
minizip succinct pugixml oauthcpp

44
tracking/connection.cpp Normal file
View file

@ -0,0 +1,44 @@
#include "connection.hpp"
#include "platform/socket.hpp"
namespace
{
uint32_t constexpr kSocketTimeoutMs = 10000;
} // namespace
namespace tracking
{
// static
const char Connection::kHost[] = "gps.host"; // TODO change to real host value
uint16_t Connection::kPort = 666; // TODO change to real port value
Connection::Connection(unique_ptr<platform::Socket> socket, string const & host, uint16_t port,
bool isHistorical)
: m_socket(move(socket)), m_host(host), m_port(port)
{
ASSERT(m_socket.get() != nullptr, ());
m_socket->SetTimeout(kSocketTimeoutMs);
}
// TODO: implement handshake
bool Connection::Reconnect()
{
m_socket->Close();
return m_socket->Open(m_host, m_port);
}
// TODO: implement historical
bool Connection::Send(boost::circular_buffer<DataPoint> const & points)
{
ASSERT(m_buffer.empty(), ());
MemWriter<decltype(m_buffer)> writer(m_buffer);
coding::TrafficGPSEncoder::SerializeDataPoints(coding::TrafficGPSEncoder::kLatestVersion, writer,
points);
bool isSuccess = m_socket->Write(m_buffer.data(), m_buffer.size());
m_buffer.clear();
return isSuccess;
}
} // namespace tracking

35
tracking/connection.hpp Normal file
View file

@ -0,0 +1,35 @@
#pragma once
#include "boost/circular_buffer.hpp"
#include "coding/traffic.hpp"
#include "std/vector.hpp"
namespace platform
{
class Socket;
}
namespace tracking
{
using DataPoint = coding::TrafficGPSEncoder::DataPoint;
class Connection final
{
public:
static const char kHost[];
static uint16_t kPort;
Connection(unique_ptr<platform::Socket> socket, string const & host, uint16_t port,
bool isHistorical);
bool Reconnect();
bool Send(boost::circular_buffer<DataPoint> const & points);
private:
unique_ptr<platform::Socket> m_socket;
string const m_host;
uint16_t const m_port;
vector<uint8_t> m_buffer;
};
} // namespace tracking

147
tracking/reporter.cpp Normal file
View file

@ -0,0 +1,147 @@
#include "reporter.hpp"
#include "base/logging.hpp"
#include "base/thread.hpp"
#include "base/timer.hpp"
#include "boost/circular_buffer.hpp"
#include "platform/location.hpp"
#include "platform/socket.hpp"
#include "std/mutex.hpp"
#include "std/vector.hpp"
#include "tracking/connection.hpp"
using namespace tracking;
namespace
{
double constexpr kMinDelaySeconds = 1.0;
double constexpr kReconnectDelaySeconds = 60.0;
size_t constexpr kRealTimeBufferSize = 60;
class WorkerImpl final : public Reporter::Worker
{
public:
WorkerImpl(unique_ptr<platform::Socket> socket, size_t pushDelayMs);
void Run();
// Worker overrides
void AddLocation(location::GpsInfo const & info);
void Stop();
private:
void FetchInput();
bool SendPoints();
volatile bool m_stop = false;
Connection m_realtimeSender;
size_t m_pushDelayMs;
bool m_wasConnected = false;
double m_lastConnectTryTime = 0.0;
vector<tracking::DataPoint> m_input;
mutex m_inputMutex;
boost::circular_buffer<DataPoint> m_points;
double m_lastGpsTime = 0.0;
};
} // namespace
namespace tracking
{
// static
const char Reporter::kAllowKey[] = "AllowStat";
Reporter::Reporter(unique_ptr<platform::Socket> socket, size_t pushDelayMs)
{
WorkerImpl * worker = new WorkerImpl(move(socket), pushDelayMs);
m_worker = worker;
threads::SimpleThread thread([worker]
{
worker->Run();
delete worker;
});
thread.detach();
}
Reporter::~Reporter() { m_worker->Stop(); }
void Reporter::AddLocation(location::GpsInfo const & info) { m_worker->AddLocation(info); }
} // namespace tracking
namespace
{
WorkerImpl::WorkerImpl(unique_ptr<platform::Socket> socket, size_t pushDelayMs)
: m_realtimeSender(move(socket), Connection::kHost, Connection::kPort, false)
, m_pushDelayMs(pushDelayMs)
, m_points(kRealTimeBufferSize)
{
}
void WorkerImpl::AddLocation(location::GpsInfo const & info)
{
lock_guard<mutex> lg(m_inputMutex);
if (info.m_timestamp < m_lastGpsTime + kMinDelaySeconds)
return;
m_lastGpsTime = info.m_timestamp;
m_input.push_back(DataPoint(info.m_timestamp, ms::LatLon(info.m_latitude, info.m_longitude)));
}
void WorkerImpl::Run()
{
LOG(LINFO, ("Tracking Reporter started"));
my::HighResTimer timer;
while (!m_stop)
{
uint64_t const startMs = timer.ElapsedMillis();
FetchInput();
if (SendPoints())
m_points.clear();
uint64_t const passedMs = timer.ElapsedMillis() - startMs;
if (passedMs < m_pushDelayMs)
threads::Sleep(m_pushDelayMs - passedMs);
}
LOG(LINFO, ("Tracking Reporter finished"));
}
void WorkerImpl::Stop() { m_stop = true; }
void WorkerImpl::FetchInput()
{
lock_guard<mutex> lg(m_inputMutex);
m_points.insert(m_points.end(), m_input.begin(), m_input.end());
m_input.clear();
}
bool WorkerImpl::SendPoints()
{
if (m_points.empty())
return true;
if (m_wasConnected)
m_wasConnected = m_realtimeSender.Send(m_points);
if (m_wasConnected)
return true;
double currentTime = my::Timer::LocalTime();
if (currentTime < m_lastConnectTryTime + kReconnectDelaySeconds)
return false;
m_lastConnectTryTime = currentTime;
m_wasConnected = m_realtimeSender.Reconnect();
if (!m_wasConnected)
return false;
m_wasConnected = m_realtimeSender.Send(m_points);
return m_wasConnected;
}
} // namespace

38
tracking/reporter.hpp Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include "std/unique_ptr.hpp"
namespace location
{
class GpsInfo;
}
namespace platform
{
class Socket;
}
namespace tracking
{
class Reporter final
{
public:
static size_t constexpr kPushDelayMs = 10000;
static const char kAllowKey[];
Reporter(unique_ptr<platform::Socket> socket, size_t pushDelayMs);
~Reporter();
void AddLocation(location::GpsInfo const & info);
class Worker
{
public:
virtual void AddLocation(location::GpsInfo const & info) = 0;
virtual void Stop() = 0;
};
private:
Worker * m_worker;
};
} // namespace tracking

15
tracking/tracking.pro Normal file
View file

@ -0,0 +1,15 @@
TARGET = tracking
TEMPLATE = lib
CONFIG += staticlib warn_on
ROOT_DIR = ..
include($$ROOT_DIR/common.pri)
SOURCES += \
connection.cpp \
reporter.cpp \
HEADERS += \
connection.hpp \
reporter.hpp \

View file

@ -0,0 +1,62 @@
#include "base/thread.hpp"
#include "coding/traffic.hpp"
#include "platform/location.hpp"
#include "platform/socket.hpp"
#include "std/cmath.hpp"
#include "testing/testing.hpp"
#include "tracking/reporter.hpp"
namespace
{
template <class Condition>
bool WaitCondition(Condition condition, size_t durationMs = 1000)
{
size_t sleepMs = 10;
size_t cyclesLimit = durationMs / sleepMs;
for (size_t i = 0; i < cyclesLimit; ++i)
{
threads::Sleep(sleepMs);
if (condition())
return true;
}
return false;
}
} // namespace
using namespace tracking;
UNIT_TEST(Reporter_TransferLocation)
{
unique_ptr<platform::TestSocket> socket = platform::createTestSocket();
platform::TestSocket * testSocket = socket.get();
Reporter reporter(move(socket), 10);
location::GpsInfo gpsInfo;
gpsInfo.m_timestamp = 3.0;
gpsInfo.m_latitude = 4.0;
gpsInfo.m_longitude = 5.0;
reporter.AddLocation(gpsInfo);
TEST(WaitCondition([testSocket] { return testSocket->HasOutput(); }), ());
vector<uint8_t> buffer;
testSocket->ReadServer(buffer);
vector<coding::TrafficGPSEncoder::DataPoint> points;
MemReader memReader(buffer.data(), buffer.size());
ReaderSource<MemReader> src(memReader);
coding::TrafficGPSEncoder::DeserializeDataPoints(coding::TrafficGPSEncoder::kLatestVersion, src,
points);
TEST_EQUAL(points.size(), 1, ());
coding::TrafficGPSEncoder::DataPoint const & point = points[0];
TEST_EQUAL(point.m_timestamp, 3, ());
TEST(abs(point.m_latLon.lat - 4.0) < 0.001, ());
TEST(abs(point.m_latLon.lon - 5.0) < 0.001, ());
}

View file

@ -0,0 +1,29 @@
TARGET = tracking_tests
CONFIG += console warn_on
CONFIG -= app_bundle
TEMPLATE = app
ROOT_DIR = ../..
INCLUDEPATH *= $$ROOT_DIR/3party/jansson/src
DEPENDENCIES = base coding geometry platform routing stats_client tracking
include($$ROOT_DIR/common.pri)
DEFINES *= OMIM_UNIT_TEST_WITH_QT_EVENT_LOOP
QT *= core
macx-* {
QT *= widgets # needed for QApplication with event loop, to test async events
LIBS *= "-framework IOKit" "-framework SystemConfiguration"
}
win*|linux* {
QT *= network
}
SOURCES += \
$$ROOT_DIR/testing/testingmain.cpp \
reporter_tests.cpp \