Skip to content

Commit e1e5c6a

Browse files
committed
DeviceTick: New class to orcestrate device polling
1 parent 677fa84 commit e1e5c6a

File tree

4 files changed

+303
-18
lines changed

4 files changed

+303
-18
lines changed

de_web.pro

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ HEADERS = bindings.h \
103103
device_descriptions.h \
104104
device_js.h \
105105
device_js_wrappers.h \
106+
device_tick.h \
106107
event.h \
107108
gateway.h \
108109
gateway_scanner.h \
@@ -140,6 +141,7 @@ SOURCES = air_quality.cpp \
140141
device_js.cpp \
141142
device_js_wrappers.cpp \
142143
device_setup.cpp \
144+
device_tick.cpp \
143145
diagnostics.cpp \
144146
discovery.cpp \
145147
de_web_plugin.cpp \

de_web_plugin.cpp

+5-18
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <cmath>
3636
#include "colorspace.h"
3737
#include "device_descriptions.h"
38+
#include "device_tick.h"
3839
#include "de_web_plugin.h"
3940
#include "de_web_plugin_private.h"
4041
#include "de_web_widget.h"
@@ -908,6 +909,10 @@ DeRestPluginPrivate::DeRestPluginPrivate(QObject *parent) :
908909

909910
connect(pollManager, &PollManager::done, this, &DeRestPluginPrivate::pollNextDevice);
910911

912+
auto *deviceTick = new DeviceTick(m_devices, this);
913+
connect(this, &DeRestPluginPrivate::eventNotify, deviceTick, &DeviceTick::handleEvent);
914+
connect(deviceTick, &DeviceTick::eventNotify, this, &DeRestPluginPrivate::enqueueEvent);
915+
911916
const deCONZ::Node *node;
912917
if (apsCtrl && apsCtrl->getNode(0, &node) == 0)
913918
{
@@ -19940,24 +19945,6 @@ void DeRestPluginPrivate::pollNextDevice()
1994019945

1994119946
if (DEV_TestManaged())
1994219947
{
19943-
static int devIter = 1;
19944-
19945-
const deCONZ::Node *node = nullptr;
19946-
deCONZ::ApsController *ctrl = deCONZ::ApsController::instance();
19947-
19948-
if (ctrl->getNode(devIter, &node) == 0)
19949-
{
19950-
devIter++;
19951-
19952-
auto *device = DEV_GetOrCreateDevice(this, m_devices, node->address().ext());
19953-
Q_ASSERT(device);
19954-
enqueueEvent(Event(device->prefix(), REventPoll, 0, device->key()));
19955-
}
19956-
else
19957-
{
19958-
devIter = 1;
19959-
}
19960-
1996119948
return;
1996219949
}
1996319950

device_tick.cpp

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
* Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
3+
* All rights reserved.
4+
*
5+
* The software in this package is published under the terms of the BSD
6+
* style license a copy of which has been included with this distribution in
7+
* the LICENSE.txt file.
8+
*
9+
*/
10+
11+
#include <QElapsedTimer>
12+
#include <QTimer>
13+
#include <deconz/dbg_trace.h>
14+
#include "event.h"
15+
#include "resource.h"
16+
#include "device_tick.h"
17+
18+
#define DEV_TICK_BOOT_TIME 8000
19+
#define TICK_INTERVAL_JOIN 500
20+
#define TICK_INTERVAL_IDLE 1000
21+
22+
struct JoinDevice
23+
{
24+
DeviceKey deviceKey;
25+
quint8 macCapabilities;
26+
};
27+
28+
static const char *RLocal = nullptr;
29+
30+
typedef void (*DT_StateHandler)(DeviceTickPrivate *d, const Event &event);
31+
32+
static void DT_StateInit(DeviceTickPrivate *d, const Event &event);
33+
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event);
34+
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event);
35+
36+
class DeviceTickPrivate
37+
{
38+
public:
39+
DT_StateHandler stateHandler = DT_StateInit;
40+
std::vector<JoinDevice> joinDevices;
41+
DeviceTick *q = nullptr;
42+
QTimer *timer = nullptr;
43+
size_t devIter = 0;
44+
const DeviceContainer *devices = nullptr;
45+
};
46+
47+
/*! Constructor.
48+
*/
49+
DeviceTick::DeviceTick(const DeviceContainer &devices, QObject *parent) :
50+
QObject(parent),
51+
d(new DeviceTickPrivate)
52+
{
53+
d->devices = &devices;
54+
d->q = this;
55+
d->timer = new QTimer(this);
56+
d->timer->setSingleShot(true);
57+
connect(d->timer, &QTimer::timeout, this, &DeviceTick::timoutFired);
58+
d->timer->start(DEV_TICK_BOOT_TIME);
59+
}
60+
61+
/*! Destructor.
62+
*/
63+
DeviceTick::~DeviceTick()
64+
{
65+
Q_ASSERT(d);
66+
delete d;
67+
d = nullptr;
68+
}
69+
70+
/*! Public event entry.
71+
*/
72+
void DeviceTick::handleEvent(const Event &event)
73+
{
74+
d->stateHandler(d, event);
75+
}
76+
77+
/*! State timer callback.
78+
*/
79+
void DeviceTick::timoutFired()
80+
{
81+
d->stateHandler(d, Event(RLocal, REventStateTimeout, 0));
82+
}
83+
84+
/*! Sets a new DeviceTick state.
85+
The events REventStateLeave and REventStateEnter will be triggered accordingly.
86+
*/
87+
static void DT_SetState(DeviceTickPrivate *d, DT_StateHandler state)
88+
{
89+
if (d->stateHandler != state)
90+
{
91+
d->stateHandler(d, Event(RLocal, REventStateLeave, 0));
92+
d->stateHandler = state;
93+
d->stateHandler(d, Event(RLocal, REventStateEnter, 0));
94+
}
95+
}
96+
97+
/*! Starts the state timer. Free function to be mocked by test code.
98+
*/
99+
static void DT_StartTimer(DeviceTickPrivate *d, int timeoutMs)
100+
{
101+
d->timer->start(timeoutMs);
102+
}
103+
104+
/*! Stops the state timer. Free function to be mocked by test code.
105+
*/
106+
static void DT_StopTimer(DeviceTickPrivate *d)
107+
{
108+
d->timer->stop();
109+
}
110+
111+
/*! Initial state to wait DEV_TICK_BOOT_TIME seconds before starting normal operation.
112+
*/
113+
static void DT_StateInit(DeviceTickPrivate *d, const Event &event)
114+
{
115+
if (event.resource() == RLocal && event.what() == REventStateTimeout)
116+
{
117+
DBG_Printf(DBG_INFO, "DEV Tick.Init: booted after %lld seconds\n", DEV_TICK_BOOT_TIME);
118+
DT_SetState(d, DT_StateIdle);
119+
}
120+
}
121+
122+
/*! Emits REventPoll to the next device in DT_StateIdle.
123+
*/
124+
static void DT_PollNextIdleDevice(DeviceTickPrivate *d)
125+
{
126+
if (d->devices->empty())
127+
{
128+
return;
129+
}
130+
131+
d->devIter %= d->devices->size();
132+
Q_ASSERT(d->devIter < d->devices->size());
133+
134+
const Device *device = d->devices->at(d->devIter);
135+
emit d->q->eventNotify(Event(device->prefix(), REventPoll, 0, device->key()));
136+
d->devIter++;
137+
}
138+
139+
/*! This state is active while Permit Join is disabled for normal idle operation.
140+
141+
It walks over all Devices in TICK_INTERVAL_IDLE spacing.
142+
The state transitions to DT_StateJoin when REventPermitjoinEnabled is received.
143+
*/
144+
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event)
145+
{
146+
if (event.what() == REventPermitjoinEnabled)
147+
{
148+
DT_SetState(d, DT_StateJoin);
149+
}
150+
else if (event.resource() == RLocal)
151+
{
152+
if (event.what() == REventStateTimeout)
153+
{
154+
DT_PollNextIdleDevice(d);
155+
DT_StartTimer(d, TICK_INTERVAL_IDLE);
156+
}
157+
else if (event.what() == REventStateEnter)
158+
{
159+
DT_StartTimer(d, TICK_INTERVAL_IDLE);
160+
}
161+
else if (event.what() == REventStateLeave)
162+
{
163+
DT_StopTimer(d);
164+
}
165+
}
166+
}
167+
168+
/*! Adds a joining device entry to the queue if not already present.
169+
*/
170+
static void DT_RegisterJoiningDevice(DeviceTickPrivate *d, DeviceKey deviceKey, quint8 macCapabilities)
171+
{
172+
Q_ASSERT(deviceKey != 0); // if this triggers we have problems elsewhere
173+
174+
auto i = std::find_if(d->joinDevices.cbegin(), d->joinDevices.cend(), [&deviceKey](const JoinDevice &dev)
175+
{
176+
return deviceKey == dev.deviceKey;
177+
});
178+
179+
if (i == d->joinDevices.cend())
180+
{
181+
JoinDevice dev;
182+
dev.deviceKey = deviceKey;
183+
dev.macCapabilities = macCapabilities;
184+
d->joinDevices.push_back(dev);
185+
DBG_Printf(DBG_INFO, "DEV Tick: fast poll 0x%016llX, mac capabilities: 0x%02X\n", deviceKey, macCapabilities);
186+
}
187+
}
188+
189+
/*! Emits REventPoll to the next device in DT_StateJoin.
190+
*/
191+
static void DT_PollNextJoiningDevice(DeviceTickPrivate *d)
192+
{
193+
if (d->joinDevices.empty())
194+
{
195+
return;
196+
}
197+
198+
d->devIter %= d->joinDevices.size();
199+
Q_ASSERT(d->devIter < d->joinDevices.size());
200+
201+
const JoinDevice &device = d->joinDevices.at(d->devIter);
202+
emit d->q->eventNotify(Event(RDevices, REventPoll, 0, device.deviceKey));
203+
d->devIter++;
204+
}
205+
206+
/*! This state is active while Permit Join is enabled.
207+
208+
When a REventDeviceAnnounce event is received, the device is added to a joining
209+
queue and processed exclusivly and quickly.
210+
211+
The state transitions to DT_StateIdle when Permit Join is disabled.
212+
*/
213+
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event)
214+
{
215+
if (event.what() == REventPermitjoinDisabled)
216+
{
217+
DT_SetState(d, DT_StateIdle);
218+
}
219+
else if (event.what() == REventDeviceAnnounce)
220+
{
221+
DBG_Printf(DBG_INFO, "DEV Tick.Join: %s\n", event.what());
222+
DT_RegisterJoiningDevice(d, event.deviceKey(), static_cast<quint8>(event.num()));
223+
}
224+
else if (event.resource() == RLocal)
225+
{
226+
if (event.what() == REventStateTimeout)
227+
{
228+
DT_PollNextJoiningDevice(d);
229+
DT_StartTimer(d, TICK_INTERVAL_JOIN);
230+
}
231+
else if (event.what() == REventStateEnter)
232+
{
233+
DT_StartTimer(d, TICK_INTERVAL_JOIN);
234+
}
235+
else if (event.what() == REventStateLeave)
236+
{
237+
DT_StopTimer(d);
238+
d->joinDevices.clear();
239+
}
240+
}
241+
}

device_tick.h

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
3+
* All rights reserved.
4+
*
5+
* The software in this package is published under the terms of the BSD
6+
* style license a copy of which has been included with this distribution in
7+
* the LICENSE.txt file.
8+
*
9+
*/
10+
11+
#ifndef DEVICE_TICK_H
12+
#define DEVICE_TICK_H
13+
14+
#include <QObject>
15+
#include "device.h"
16+
17+
class Event;
18+
class Device;
19+
20+
class DeviceTickPrivate;
21+
22+
23+
/*! \class DeviceTick
24+
25+
Coordinates poking the Device state machines.
26+
27+
It differentiates between normal idle operation and device pairing while
28+
Permit Join is enabled. While during pairing a faster pace is applied.
29+
30+
TODO
31+
32+
Take task queue and APS-DATA.request queue into account.
33+
*/
34+
class DeviceTick : public QObject
35+
{
36+
Q_OBJECT
37+
38+
public:
39+
explicit DeviceTick(const DeviceContainer &devices, QObject *parent = nullptr);
40+
~DeviceTick();
41+
42+
Q_SIGNALS:
43+
void eventNotify(const Event&); //! Emitted \p Event needs to be enqueued in a higher layer.
44+
45+
public Q_SLOTS:
46+
void handleEvent(const Event &event);
47+
48+
private Q_SLOTS:
49+
void timoutFired();
50+
51+
private:
52+
DeviceTickPrivate *d = nullptr;
53+
};
54+
55+
#endif // DEVICE_TICK_H

0 commit comments

Comments
 (0)