Torc  0.1
torciotlogger.cpp
Go to the documentation of this file.
1 /* Class TorcIOTLogger
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2018
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 // Torc
24 #include "torciotlogger.h"
25 #include "torcnetwork.h"
26 #include "torclogging.h"
27 #include "torchttprequest.h"
28 
29 TorcIOTLogger::TorcIOTLogger(const QVariantMap &Details)
30  : TorcNotifier(Details),
31  m_description(QStringLiteral("IOTLogger")),
32  m_timer(),
33  m_networkAbort(0),
34  m_apiKey(QStringLiteral("")),
35  m_badRequestCount(0),
36  m_maxBadRequests(5),
37  m_maxUpdateInterval(15),
38  m_lastUpdate(QDateTime::fromMSecsSinceEpoch(0)),
39  m_requests(),
40  m_maxFields(32),
41  m_fields(),
42  m_reverseFields()
43 
44 {
45  // clear fields
46  for (int i = 0; i < 32; i++)
47  m_fieldValues[i] = QStringLiteral("");
48 }
49 
51 {
52  QMutexLocker locker(&lock);
53 
54  m_networkAbort = 1;
55 
56  foreach (TorcNetworkRequest* request, m_requests)
57  {
58  TorcNetwork::Cancel(request);
59  request->DownRef();
60  }
61  m_requests.clear();
62 }
63 
64 bool TorcIOTLogger::Initialise(const QVariantMap &Details)
65 {
66  if (Details.contains(QStringLiteral("apikey")))
67  m_apiKey = Details.value(QStringLiteral("apikey")).toString().trimmed();
68 
69  if (m_apiKey.isEmpty())
70  {
71  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 logger has no api key - disabling").arg(m_description));
72  return false;
73  }
74 
75  if (Details.contains(QStringLiteral("fields")))
76  {
77  QVariantMap fields = Details.value(QStringLiteral("fields")).toMap();
78  for (int i = 0; i < m_maxFields; i++)
79  {
80  QString field = QStringLiteral("field%1").arg(i +1);
81  if (fields.contains(field))
82  {
83  QString value = fields.value(field).toString().trimmed();
84  if (!value.isEmpty())
85  {
86  m_fields.insert(value, i);
87  m_reverseFields.insert(i, value);
88  }
89  }
90  }
91  }
92 
93  if (m_fields.isEmpty())
94  {
95  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 logger has no valid fields - disabling").arg(m_description));
96  return false;
97  }
98 
99  // at this point, all should be good. Set up the timer
100  connect(this, &TorcIOTLogger::TryNotify, this, &TorcIOTLogger::DoNotify);
101  m_timer.setSingleShot(true);
102  connect(this, &TorcIOTLogger::StartTimer, &m_timer, static_cast<void (QTimer::*)(int)>(&QTimer::start));
103  connect(&m_timer, &QTimer::timeout, this, &TorcIOTLogger::DoNotify);
104 
105  return true;
106 }
107 
109 {
110  QStringList result;
111  result << m_description;
112  QMap<QString,int>::const_iterator it = m_fields.constBegin();
113  for ( ; it != m_fields.constEnd(); ++it)
114  result << QStringLiteral("field%1: %2").arg(it.value() + 1).arg(it.key());
115  return result;
116 }
117 
118 void TorcIOTLogger::Notify(const QVariantMap &Notification)
119 {
120  if (!GetValid())
121  return;
122 
123  bool updated = false;
124  // set fields...
125  {
126  QMutexLocker locker(&lock);
127 
128  QMap<QString,int>::const_iterator it = m_fields.constBegin();
129  for ( ; it != m_fields.constEnd(); ++it)
130  {
131  if (Notification.contains(it.key()))
132  {
133  m_fieldValues[it.value()] = Notification.value(it.key()).toString();
134  updated = true;
135  }
136  }
137  }
138 
139  if (updated)
140  emit TryNotify();
141 }
142 
144 {
145  if (!GetValid())
146  return;
147 
149  {
150  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Not sending %1 update. Network is not available").arg(m_description));
151  return;
152  }
153 
154  QDateTime now = QDateTime::currentDateTime();
155 
156  // ThingSpeak/IOTPlotter etc limit updates to a max of 1 every 15 seconds. If we try and update too fast, simply
157  // amalgamate fields and start the timer to trigger the next update. If the same field is updated more than
158  // once, its value is overwritten with the most recent value. The timestamp may also be out of date for 'stale' fields.
159 
160  if (m_lastUpdate.secsTo(now) < m_maxUpdateInterval)
161  {
162  if (m_timer.isActive())
163  return;
164 
165  emit StartTimer(m_lastUpdate.msecsTo(now));
166  return;
167  }
168 
169  m_lastUpdate = now;
170 
171  TorcNetworkRequest* request = CreateRequest();
172  if (request)
173  {
174  TorcNetwork::GetAsynchronous(request, this);
175  {
176  QMutexLocker locker(&lock);
177  m_requests.append(request);
178  }
179  }
180 }
181 
183 {
184  if (!Request)
185  return;
186 
187  QMutexLocker locker(&lock);
188  if (!m_requests.contains(Request))
189  {
190  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Response to unknown %1 request").arg(m_description));
191  return;
192  }
193 
194  int status = Request->GetStatus();
195  if (status >= HTTP_BadRequest)
196  {
197  // not sure whether it is my network or the thingspeak server - but I get intermittent Host Not Found errors
198  // which end up disabling ThingSpeak. So check for HostNotFound...
199  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%2 update error '%1'").arg(TorcHTTPRequest::StatusToString((HTTPStatus)status), m_description));
200 
201  if (Request->GetReplyError() != QNetworkReply::HostNotFoundError)
202  {
204  {
205  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("%1 %2 update errors. Disabling logger - Check your config.")
206  .arg(m_badRequestCount)
207  .arg(m_description));
208  SetValid(false);
209  }
210  }
211  }
212  else
213  {
214  // reset error count
215  m_badRequestCount = 0;
216  ProcessRequest(Request);
217  }
218 
219  m_requests.removeAll(Request);
220  Request->DownRef();
221 }
virtual bool Initialise(const QVariantMap &Details)
int m_maxUpdateInterval
Definition: torciotlogger.h:45
virtual void SetValid(bool Valid)
Definition: torcdevice.cpp:101
virtual ~TorcIOTLogger()
void RequestReady(TorcNetworkRequest *Request)
QStringList GetDescription(void) overridefinal
A wrapper around QNetworkRequest.
static bool GetAsynchronous(TorcNetworkRequest *Request, QObject *Parent)
Queue an asynchronous HTTP request.
Definition: torcnetwork.cpp:94
QMap< QString, int > m_fields
Definition: torciotlogger.h:49
static QString StatusToString(HTTPStatus Status)
QDateTime m_lastUpdate
Definition: torciotlogger.h:46
QMap< int, QString > m_reverseFields
Definition: torciotlogger.h:50
bool GetValid(void)
Definition: torcdevice.cpp:135
QNetworkReply::NetworkError GetReplyError(void) const
void DoNotify(void)
static bool IsAvailable(void)
Definition: torcnetwork.cpp:46
virtual bool DownRef(void)
TorcIOTLogger(const QVariantMap &Details)
virtual void ProcessRequest(TorcNetworkRequest *Request)=0
void Notify(const QVariantMap &Notification) override
virtual TorcNetworkRequest * CreateRequest(void)=0
QMutex lock
Definition: torcdevice.h:66
QList< TorcNetworkRequest * > m_requests
Definition: torciotlogger.h:47
double value
Definition: torcdevice.h:60
HTTPStatus
QString m_fieldValues[32]
Definition: torciotlogger.h:51
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
static void Cancel(TorcNetworkRequest *Request)
Definition: torcnetwork.cpp:73
QString m_apiKey
Definition: torciotlogger.h:42
int GetStatus(void) const
void TryNotify(void)
void StartTimer(int Milliseconds)
QString m_description
Definition: torciotlogger.h:39