* Copyright (c) 2021 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/
#include <QElapsedTimer>
#include <QTimer>
#include <deconz/dbg_trace.h>
#include <deconz/timeref.h>
#include "event.h"
#include "resource.h"
#include "device_tick.h"
#define DEV_TICK_BOOT_TIME 8000
#define TICK_INTERVAL_JOIN 500
#define TICK_INTERVAL_IDLE 1000
#define TICK_INTERVAL_IDLE_OTAU 6000
#define TICK_INTERVAL_POLL_TIMOUT 10000
extern int DEV_ApsQueueSize();
extern bool DEV_OtauBusy();
struct JoinDevice
{
DeviceKey deviceKey;
quint8 macCapabilities;
};
static const char *RLocal = nullptr;
typedef void (*DT_StateHandler)(DeviceTickPrivate *d, const Event &event);
static void DT_StateInit(DeviceTickPrivate *d, const Event &event);
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event);
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event);
static void DT_StatePoll(DeviceTickPrivate *d, const Event &event);
class DeviceTickPrivate
{
public:
DT_StateHandler stateHandler = DT_StateInit;
std::vector<JoinDevice> joinDevices;
deCONZ::SteadyTimeRef joinDisabledTime;
DeviceTick *q = nullptr;
QTimer *timer = nullptr;
size_t devIter = 0;
const DeviceContainer *devices = nullptr;
DeviceKey curDeviceKey = 0;
bool curDeviceManaged = false;
};
*/
DeviceTick::DeviceTick(const DeviceContainer &devices, QObject *parent) :
QObject(parent),
d(new DeviceTickPrivate)
{
d->devices = &devices;
d->q = this;
d->timer = new QTimer(this);
d->timer->setSingleShot(true);
connect(d->timer, &QTimer::timeout, this, &DeviceTick::timoutFired);
d->timer->start(DEV_TICK_BOOT_TIME);
}
*/
DeviceTick::~DeviceTick()
{
Q_ASSERT(d);
delete d;
d = nullptr;
}
*/
void DeviceTick::handleEvent(const Event &event)
{
d->stateHandler(d, event);
}
*/
void DeviceTick::timoutFired()
{
d->stateHandler(d, Event(RLocal, REventStateTimeout, 0));
}
The events REventStateLeave and REventStateEnter will be triggered accordingly.
*/
static void DT_SetState(DeviceTickPrivate *d, DT_StateHandler state)
{
if (d->stateHandler != state)
{
d->stateHandler(d, Event(RLocal, REventStateLeave, 0));
d->stateHandler = state;
d->stateHandler(d, Event(RLocal, REventStateEnter, 0));
}
}
*/
static void DT_StartTimer(DeviceTickPrivate *d, int timeoutMs)
{
d->timer->start(timeoutMs);
}
*/
static void DT_StopTimer(DeviceTickPrivate *d)
{
d->timer->stop();
}
*/
static void DT_StateInit(DeviceTickPrivate *d, const Event &event)
{
if (event.resource() == RLocal && event.what() == REventStateTimeout)
{
DBG_Printf(DBG_DEV, "DEV Tick.Init: booted after %ld seconds\n", (long)DEV_TICK_BOOT_TIME);
DT_SetState(d, DT_StateIdle);
}
}
*/
static bool DT_PollNextIdleDevice(DeviceTickPrivate *d)
{
const auto devCount = d->devices->size();
if (devCount == 0)
{
return false;
}
d->devIter %= devCount;
const auto &device = d->devices->at(d->devIter);
d->devIter++;
Q_ASSERT(device);
if (device->reachable())
{
d->curDeviceKey = device->key();
d->curDeviceManaged = device->managed();
emit d->q->eventNotify(Event(device->prefix(), REventPoll, 0, device->key()));
return true;
}
return false;
}
It walks over all Devices in TICK_INTERVAL_IDLE spacing.
The state transitions to DT_StateJoin when REventPermitjoinEnabled is received.
*/
static void DT_StateIdle(DeviceTickPrivate *d, const Event &event)
{
if (event.what() == REventPermitjoinEnabled)
{
DT_SetState(d, DT_StateJoin);
}
else if (event.resource() == RLocal)
{
if (event.what() == REventStateTimeout)
{
int timeout = DEV_OtauBusy() ? TICK_INTERVAL_IDLE_OTAU : TICK_INTERVAL_IDLE;
if (DA_ApsUnconfirmedRequests() < 4)
{
if (DT_PollNextIdleDevice(d))
{
DT_SetState(d, DT_StatePoll);
return;
}
}
DT_StartTimer(d, timeout);
}
else if (event.what() == REventStateEnter)
{
DT_StartTimer(d, TICK_INTERVAL_IDLE);
}
else if (event.what() == REventStateLeave)
{
DT_StopTimer(d);
}
}
}
*/
static void DT_StatePoll(DeviceTickPrivate *d, const Event &event)
{
if (event.what() == REventPermitjoinEnabled)
{
DT_SetState(d, DT_StateJoin);
}
else if (event.resource() == RLocal)
{
if (event.what() == REventStateTimeout)
{
DT_SetState(d, DT_StateIdle);
}
else if (event.what() == REventStateEnter)
{
DBG_Printf(DBG_DEV, "DEV Tick: poll enter " FMT_MAC ", managed = %d\n", FMT_MAC_CAST(d->curDeviceKey), d->curDeviceManaged);
DT_StartTimer(d, TICK_INTERVAL_POLL_TIMOUT);
}
else if (event.what() == REventStateLeave)
{
DBG_Printf(DBG_DEV, "DEV Tick: poll leave " FMT_MAC "\n", FMT_MAC_CAST(d->curDeviceKey));
DT_StopTimer(d);
}
}
else if (event.resource() == RDevices && event.what() == REventPollDone)
{
DBG_Printf(DBG_DEV, "DEV Tick: poll done " FMT_MAC "\n", FMT_MAC_CAST(d->curDeviceKey));
DT_SetState(d, DT_StateIdle);
}
}
*/
static void DT_RegisterJoiningDevice(DeviceTickPrivate *d, DeviceKey deviceKey, quint8 macCapabilities)
{
Q_ASSERT(deviceKey != 0);
auto i = std::find_if(d->joinDevices.cbegin(), d->joinDevices.cend(), [&deviceKey](const JoinDevice &dev)
{
return deviceKey == dev.deviceKey;
});
if (i == d->joinDevices.cend())
{
JoinDevice dev;
dev.deviceKey = deviceKey;
dev.macCapabilities = macCapabilities;
d->joinDevices.push_back(dev);
DBG_Printf(DBG_DEV, "DEV Tick: fast poll " FMT_MAC ", mac capabilities: 0x%02X\n", FMT_MAC_CAST(deviceKey), macCapabilities);
}
}
*/
static void DT_PollNextJoiningDevice(DeviceTickPrivate *d)
{
if (d->joinDevices.empty())
{
return;
}
d->devIter %= d->joinDevices.size();
Q_ASSERT(d->devIter < d->joinDevices.size());
const JoinDevice &device = d->joinDevices.at(d->devIter);
emit d->q->eventNotify(Event(RDevices, REventAwake, 0, device.deviceKey));
d->devIter++;
}
When a REventDeviceAnnounce event is received, the device is added to a joining
queue and processed exclusivly and quickly.
The state transitions to DT_StateIdle when Permit Join is disabled.
*/
static void DT_StateJoin(DeviceTickPrivate *d, const Event &event)
{
if (event.what() == REventPermitjoinDisabled)
{
d->joinDisabledTime = deCONZ::steadyTimeRef();
}
else if (event.what() == REventDeviceAnnounce)
{
DBG_Printf(DBG_DEV, "DEV Tick.Join: %s\n", event.what());
DT_RegisterJoiningDevice(d, event.deviceKey(), static_cast<quint8>(event.num()));
}
else if (event.resource() == RLocal)
{
if (event.what() == REventStateTimeout)
{
if (deCONZ::isValid(d->joinDisabledTime) &&
deCONZ::TimeSeconds{20} < (deCONZ::steadyTimeRef() - d->joinDisabledTime))
{
DT_SetState(d, DT_StateIdle);
return;
}
DT_PollNextJoiningDevice(d);
DT_StartTimer(d, TICK_INTERVAL_JOIN);
}
else if (event.what() == REventStateEnter)
{
d->joinDisabledTime = {};
DT_StartTimer(d, TICK_INTERVAL_JOIN);
}
else if (event.what() == REventStateLeave)
{
DT_StopTimer(d);
d->joinDevices.clear();
}
}
}