6#include "onboardstatusmanager_p.h"
9#include "backend/scriptedrestonboardbackend_p.h"
13#include <QJsonDocument>
15#include <QMetaProperty>
16#include <QNetworkAccessManager>
22 Q_INIT_RESOURCE(data);
25OnboardStatusManager::OnboardStatusManager(
QObject *parent)
31 m_positionUpdateTimer.setSingleShot(
true);
34 m_journeyUpdateTimer.setSingleShot(
true);
37 connect(&m_wifiMonitor, &WifiMonitor::statusChanged,
this, &OnboardStatusManager::wifiChanged);
38 connect(&m_wifiMonitor, &WifiMonitor::wifiChanged,
this, &OnboardStatusManager::wifiChanged);
42OnboardStatusManager::~OnboardStatusManager() =
default;
44OnboardStatusManager* OnboardStatusManager::instance()
46 static OnboardStatusManager mgr;
68 Q_EMIT statusChanged();
71PositionData OnboardStatusManager::currentPosition()
const
76bool OnboardStatusManager::supportsPosition()
const
78 return m_backend && m_backend->supportsPosition();
81Journey OnboardStatusManager::currentJourney()
const
86bool OnboardStatusManager::supportsJourney()
const
88 return m_backend && m_backend->supportsJourney();
93 qCDebug(
Log) <<
"registering onboard frontend";
94 connect(
status, &OnboardStatus::updateIntervalChanged,
this, &OnboardStatusManager::requestForceUpdate);
95 m_frontends.push_back(
status);
101 qCDebug(
Log) <<
"unregistering onboard frontend";
102 disconnect(
status, &OnboardStatus::updateIntervalChanged,
this, &OnboardStatusManager::requestUpdate);
103 const auto it = std::find(m_frontends.begin(), m_frontends.end(),
status);
104 if (it != m_frontends.end()) {
105 m_frontends.erase(it);
110void OnboardStatusManager::requestPosition()
112 if (m_backend && !m_pendingPositionUpdate) {
113 m_pendingPositionUpdate =
true;
114 m_backend->requestPosition(nam());
118void OnboardStatusManager::requestJourney()
120 if (m_backend && !m_pendingJourneyUpdate) {
121 m_pendingJourneyUpdate =
true;
122 m_backend->requestJourney(nam());
126void OnboardStatusManager::wifiChanged()
128 auto ssid = m_wifiMonitor.ssid();
129 auto status = m_wifiMonitor.status();
131 if (Q_UNLIKELY(qEnvironmentVariableIsSet(
"KPUBLICTRANSPORT_ONBOARD_FAKE_CONFIG"))) {
132 QFile f(qEnvironmentVariable(
"KPUBLICTRANSPORT_ONBOARD_FAKE_CONFIG"));
134 qCWarning(
Log) << f.errorString() << f.fileName();
143 case WifiMonitor::NotAvailable:
146 case WifiMonitor::Available:
148 if (ssid.isEmpty()) {
152 loadAccessPointData();
153 const auto it = std::lower_bound(m_accessPointData.begin(), m_accessPointData.end(), ssid);
154 if (it == m_accessPointData.end() || (*it).ssid != ssid) {
158 loadBackend((*it).backendId);
164 requestForceUpdate();
167 case WifiMonitor::NoPermission:
170 case WifiMonitor::WifiNotEnabled:
173 case WifiMonitor::LocationServiceNotEnabled:
179void OnboardStatusManager::loadAccessPointData()
181 if (!m_accessPointData.empty()) {
185 QFile f(QStringLiteral(
":/org.kde.kpublictransport.onboard/accesspoints.json"));
187 qCWarning(
Log) <<
"Failed to load access point database:" << f.errorString() << f.fileName();
194 qCWarning(
Log) <<
"Failed to parse access point data:" <<
error.errorString();
198 m_accessPointData.reserve(aps.size());
199 for (
const auto &apVal : aps) {
200 const auto ap = apVal.toObject();
201 AccessPointInfo info;
204 m_accessPointData.push_back(std::move(info));
207 std::sort(m_accessPointData.begin(), m_accessPointData.end());
210void OnboardStatusManager::loadBackend(
const QString &
id)
212 const bool oldSupportsPosition = supportsPosition();
213 const bool oldSupportsJourney = supportsJourney();
215 m_backend = createBackend(
id);
220 connect(m_backend.get(), &AbstractOnboardBackend::positionReceived,
this, &OnboardStatusManager::positionUpdated);
221 connect(m_backend.get(), &AbstractOnboardBackend::journeyReceived,
this, &OnboardStatusManager::journeyUpdated);
223 if (oldSupportsPosition != supportsPosition()) {
224 Q_EMIT supportsPositionChanged();
226 if (oldSupportsJourney != supportsJourney()) {
227 Q_EMIT supportsJourneyChanged();
231std::unique_ptr<AbstractOnboardBackend> OnboardStatusManager::createBackend(
const QString&
id)
233 std::unique_ptr<AbstractOnboardBackend> backend;
237 qCWarning(
Log) <<
"Failed to open onboard API configuration:" << f.errorString() << f.fileName();
243 if (backendTypeName ==
QLatin1String(
"ScriptedRestOnboardBackend")) {
244 backend.reset(
new ScriptedRestOnboardBackend);
248 qCWarning(
Log) <<
"Failed to create onboard API backend:" << backendTypeName;
252 const auto mo = backend->metaObject();
253 const auto options = config.value(
QLatin1String(
"options")).toObject();
254 for (
auto it = options.begin(); it != options.end(); ++it) {
255 const auto idx = mo->indexOfProperty(it.key().toUtf8().constData());
257 qCWarning(
Log) <<
"Unknown backend setting:" << it.key();
260 const auto mp = mo->property(idx);
261 mp.write(backend.get(), it.value().toVariant());
267constexpr inline double degToRad(
double deg)
269 return deg / 180.0 * M_PI;
272constexpr inline double radToDeg(
double rad)
274 return rad / M_PI * 180.0;
277void OnboardStatusManager::positionUpdated(
const PositionData &pos)
279 m_pendingPositionUpdate =
false;
280 m_previousPos = m_currentPos;
282 if (!m_currentPos.timestamp.isValid()) {
287 if (std::isnan(m_currentPos.heading) &&
288 m_previousPos.hasCoordinate() &&
289 m_currentPos.hasCoordinate() &&
290 Location::distance(m_currentPos.latitude, m_currentPos.longitude, m_previousPos.latitude, m_previousPos.longitude) > 10.0)
292 const auto deltaLon =
degToRad(m_currentPos.longitude) -
degToRad(m_previousPos.longitude);
293 const auto y = std::cos(
degToRad(m_currentPos.latitude)) * std::sin(deltaLon);
294 const auto x = std::cos(
degToRad(m_previousPos.latitude)) * std::sin(
degToRad(m_previousPos.latitude)) - std::sin(
degToRad(m_previousPos.latitude)) * std::cos(
degToRad(m_currentPos.latitude)) * std::cos(deltaLon);
295 m_currentPos.heading = std::fmod(
radToDeg(std::atan2(y, x)) + 360.0, 360.0);
299 if (std::isnan(m_currentPos.speed) && m_previousPos.hasCoordinate() && m_currentPos.hasCoordinate())
301 const auto dist =
Location::distance(m_currentPos.latitude, m_currentPos.longitude, m_previousPos.latitude, m_previousPos.longitude);
302 const double timeDelta = m_previousPos.timestamp.secsTo(m_currentPos.timestamp);
304 m_currentPos.speed = 3.6 * dist / timeDelta;
308 Q_EMIT positionChanged();
312void OnboardStatusManager::journeyUpdated(
const Journey &jny)
314 m_pendingJourneyUpdate =
false;
318 if (Q_LIKELY(qEnvironmentVariableIsEmpty(
"KPUBLICTRANSPORT_ONBOARD_FAKE_CONFIG"))) {
327 Q_EMIT journeyChanged();
340void OnboardStatusManager::requestUpdate()
342 scheduleUpdate(
false);
345void OnboardStatusManager::requestForceUpdate()
347 scheduleUpdate(
true);
350void OnboardStatusManager::scheduleUpdate(
bool force)
352 if (!m_backend || m_frontends.empty()) {
353 m_positionUpdateTimer.stop();
354 m_journeyUpdateTimer.stop();
358 if (!m_pendingPositionUpdate) {
359 int interval = std::numeric_limits<int>::max();
360 for (
auto f : m_frontends) {
361 if (f->positionUpdateInterval() > 0) {
362 interval = std::min(interval, f->positionUpdateInterval());
365 if (m_positionUpdateTimer.isActive()) {
366 interval = std::min(m_positionUpdateTimer.remainingTime() / 1000, interval);
368 if (interval < std::numeric_limits<int>::max()) {
369 qCDebug(
Log) <<
"next position update:" << interval << force;
370 m_positionUpdateTimer.start(std::chrono::seconds(force ? 0 : interval));
374 if (!m_pendingJourneyUpdate) {
375 int interval = std::numeric_limits<int>::max();
376 for (
auto f : m_frontends) {
377 if (f->journeyUpdateInterval() > 0) {
378 interval = std::min(interval, f->journeyUpdateInterval());
381 if (m_journeyUpdateTimer.isActive()) {
382 interval = std::min(m_journeyUpdateTimer.remainingTime() / 1000, interval);
384 if (interval < std::numeric_limits<int>::max()) {
385 qCDebug(
Log) <<
"next journey update:" << interval << force;
386 m_journeyUpdateTimer.start(std::chrono::seconds(force ? 0 : interval));
391void OnboardStatusManager::requestPermissions()
393 m_wifiMonitor.requestPermissions();
QDateTime expectedArrivalTime
Actual arrival time, if available.
static double distance(double lat1, double lon1, double lat2, double lon2)
Compute the distance between two geo coordinates, in meters.
Access to public transport onboard API.
@ LocationServiceNotEnabled
Wi-Fi monitoring is not possible due to the location service being disabled (Android only).
@ MissingPermissions
Wi-Fi monitoring not functional due to missing application permissions.
@ NotConnected
Wi-Fi monitoring functional, but currently not connected to an onboard Wi-Fi.
@ NotAvailable
Wi-Fi monitoring is generally not available on this platform.
@ WifiNotEnabled
Wi-Fi monitoring is not possible due to Wi-Fi being disabled.
@ Onboard
currently connected to a known onboard Wi-Fi system.
Q_SCRIPTABLE CaptureState status()
char * toString(const EngineQuery &query)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
Query operations and data types for accessing realtime public transport information from online servi...
constexpr double radToDeg(double rad)
constexpr double degToRad(double deg)
QDateTime addSecs(qint64 s) const const
QDateTime currentDateTime()
QJsonArray array() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
QJsonValue value(QLatin1StringView key) const const
QString toString() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)