Skip to content

Commit 8ab0aee

Browse files
committed
Initial commit of alarm systems and refactored IAS ACE support (wip)
1 parent 2e84deb commit 8ab0aee

21 files changed

+2607
-398
lines changed

alarm_system.cpp

Lines changed: 736 additions & 0 deletions
Large diffs are not rendered by default.

alarm_system.h

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 ALARM_SYSTEM_H
12+
#define ALARM_SYSTEM_H
13+
14+
#include <QObject>
15+
#include <vector>
16+
#include "resource.h"
17+
18+
/*! \class AlarmSystem
19+
20+
This class implements a stateful alarm system. The system is controlled with events from physical devices and the REST API.
21+
22+
A general overview can be found at https://dresden-elektronik.github.io/deconz-rest-doc/endpoints/alarmsystems
23+
24+
The state machine mimics a typical alarm system, similar to implementations in home automation systems.
25+
26+
https://www.npmjs.com/package/homebridge-alarm-panel
27+
https://www.home-assistant.io/integrations/manual
28+
29+
There are four target states:
30+
disarmed
31+
armed_away
32+
armed_stay
33+
armed_night
34+
35+
A: alarm system id
36+
M: alarm mask
37+
0000 0001 Away
38+
0000 0010 Stay
39+
0000 0100 Night
40+
41+
RConfigAlarmSystemId: uint32 0000 0000 0000 0000 0000 0000 0000 0000
42+
0000 0000 0000 0000 0000 0000 AAAA AAAA
43+
44+
REventDeviceAlarm uint32 0000 0000 0000 0000 MMMM MMMM AAAA AAAA
45+
46+
*/
47+
48+
#define AS_ARM_MASK_ARMED_AWAY 0x0100
49+
#define AS_ARM_MASK_ARMED_STAY 0x0200
50+
#define AS_ARM_MASK_ARMED_NIGHT 0x0400
51+
52+
class Event;
53+
class EventEmitter;
54+
class AS_DeviceTable;
55+
class AlarmSystemPrivate;
56+
57+
enum AS_ArmMode
58+
{
59+
AS_ArmModeDisarmed = 0,
60+
AS_ArmModeArmedStay = 1,
61+
AS_ArmModeArmedNight = 2,
62+
AS_ArmModeArmedAway = 3,
63+
64+
AS_ArmModeMax
65+
};
66+
67+
using AlarmSystemId = quint32;
68+
69+
class AlarmSystem : public QObject,
70+
public Resource
71+
{
72+
Q_OBJECT
73+
74+
public:
75+
AlarmSystem(AlarmSystemId id, EventEmitter *eventEmitter, AS_DeviceTable *devTable, QObject *parent = nullptr);
76+
~AlarmSystem();
77+
void handleEvent(const Event &event);
78+
bool isValidCode(const QString &code, quint64 srcExtAddress);
79+
AlarmSystemId id() const;
80+
const QString &idString() const;
81+
quint8 iasAcePanelStatus() const;
82+
uint secondsRemaining() const;
83+
QLatin1String armStateString() const;
84+
AS_ArmMode targetArmMode() const;
85+
bool setTargetArmMode(AS_ArmMode targetArmMode);
86+
bool addDevice(const QString &uniqueId, quint32 flags);
87+
bool removeDevice(const QLatin1String &uniqueId);
88+
const AS_DeviceTable *deviceTable() const;
89+
bool setCode(int index, const QString &code);
90+
void start();
91+
92+
Q_SIGNALS:
93+
void eventNotify(const Event&);
94+
95+
private Q_SLOTS:
96+
void timerFired();
97+
98+
private:
99+
AlarmSystemPrivate *d = nullptr;
100+
};
101+
102+
/*! \class AlarmSystems
103+
104+
RAII wrapper to hold \c AlarmSystem objects.
105+
*/
106+
class AlarmSystems
107+
{
108+
public:
109+
AlarmSystems();
110+
~AlarmSystems();
111+
112+
std::vector<AlarmSystem*> alarmSystems;
113+
};
114+
115+
void DB_LoadAlarmSystems(AlarmSystems &alarmSystems, AS_DeviceTable *devTable, EventEmitter *eventEmitter);
116+
void AS_InitDefaultAlarmSystem(AlarmSystems &alarmSystems, AS_DeviceTable *devTable, EventEmitter *eventEmitter);
117+
118+
QLatin1String AS_ArmModeToString(AS_ArmMode armMode);
119+
AS_ArmMode AS_ArmModeFromString(const QString &armMode);
120+
AlarmSystem *AS_GetAlarmSystemForDevice(quint64 extAddress, AlarmSystems &alarmSystems);
121+
const AlarmSystem *AS_GetAlarmSystem(AlarmSystemId alarmSystemId, const AlarmSystems &alarmSystems);
122+
AlarmSystem *AS_GetAlarmSystem(AlarmSystemId alarmSystemId, AlarmSystems &alarmSystems);
123+
124+
#endif // ALARM_SYSTEM_H

alarm_system_device_table.cpp

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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 "deconz/dbg_trace.h"
12+
#include "deconz/timeref.h"
13+
#include "alarm_system_device_table.h"
14+
#include "database.h"
15+
#include "utils/utils.h"
16+
17+
//! getIterator() is a helper to simplyfy code
18+
static std::vector<AS_DeviceEntry>::iterator getIterator(std::vector<AS_DeviceEntry> &table, quint64 extAddress)
19+
{
20+
return std::find_if(table.begin(), table.end(), [&](const AS_DeviceEntry &entry) { return entry.extAddress == extAddress; });
21+
}
22+
23+
static std::vector<AS_DeviceEntry>::const_iterator getIterator(const std::vector<AS_DeviceEntry> &table, quint64 extAddress)
24+
{
25+
return std::find_if(table.cbegin(), table.cend(), [&](const AS_DeviceEntry &entry) { return entry.extAddress == extAddress; });
26+
}
27+
28+
AS_DeviceTable::AS_DeviceTable()
29+
{
30+
static_assert(sizeof(AS_DeviceEntry) == 64, "expected size of AS_DeviceEntry == 64 bytes");
31+
static_assert (AS_MAX_UNIQUEID_LENGTH == DB_MAX_UNIQUEID_SIZE, "DB/AS max uniqueid size mismatch");
32+
}
33+
34+
const AS_DeviceEntry &AS_DeviceTable::get(const QString &uniqueId) const
35+
{
36+
const quint64 extAddress = extAddressFromUniqueId(uniqueId);
37+
const auto i = getIterator(m_table, extAddress);
38+
39+
if (i != m_table.cend())
40+
{
41+
return *i;
42+
}
43+
44+
return m_invalidEntry;
45+
}
46+
47+
const AS_DeviceEntry &AS_DeviceTable::get(quint64 extAddress) const
48+
{
49+
const auto i = getIterator(m_table, extAddress);
50+
51+
if (i != m_table.cend())
52+
{
53+
return *i;
54+
}
55+
56+
return m_invalidEntry;
57+
}
58+
59+
const AS_DeviceEntry &AS_DeviceTable::at(size_t index) const
60+
{
61+
if (index < m_table.size())
62+
{
63+
return m_table[index];
64+
}
65+
66+
return m_invalidEntry;
67+
}
68+
69+
static void entryInitArmMask(AS_DeviceEntry &entry)
70+
{
71+
memset(entry.armMask, 0, sizeof(entry.armMask));
72+
char *p = entry.armMask;
73+
74+
if (entry.flags & AS_ENTRY_FLAG_ARMED_AWAY) { *p++ = 'A'; }
75+
if (entry.flags & AS_ENTRY_FLAG_ARMED_STAY) { *p++ = 'S'; }
76+
if (entry.flags & AS_ENTRY_FLAG_ARMED_NIGHT) { *p++ = 'N'; }
77+
}
78+
79+
static bool storeDeviceEntry(const AS_DeviceEntry &entry)
80+
{
81+
DB_AlarmSystemDevice dbDevice;
82+
83+
copyString(dbDevice.uniqueid, sizeof(dbDevice.uniqueid), entry.uniqueId);
84+
DBG_Assert(!isEmptyString(dbDevice.uniqueid));
85+
if (isEmptyString(dbDevice.uniqueid))
86+
{
87+
return false;
88+
}
89+
90+
dbDevice.alarmSystemId = entry.alarmSystemId;
91+
dbDevice.flags = entry.flags;
92+
dbDevice.timestamp = deCONZ::systemTimeRef().ref;
93+
94+
return DB_StoreAlarmSystemDevice(dbDevice);
95+
}
96+
97+
bool AS_DeviceTable::put(const QString &uniqueId, quint32 flags, quint8 alarmSystemId)
98+
{
99+
const quint64 extAddress = extAddressFromUniqueId(uniqueId);
100+
101+
if (extAddress == 0)
102+
{
103+
return false;
104+
}
105+
106+
// check existing
107+
auto i = getIterator(m_table, extAddress);
108+
109+
if (i != m_table.end())
110+
{
111+
if (i->flags != flags || i->alarmSystemId != alarmSystemId)
112+
{
113+
i->flags = flags;
114+
i->alarmSystemId = alarmSystemId;
115+
entryInitArmMask(*i);
116+
117+
storeDeviceEntry(*i);
118+
}
119+
return true;
120+
}
121+
122+
// not existing, create new
123+
m_table.push_back(AS_DeviceEntry());
124+
AS_DeviceEntry &entry = m_table.back();
125+
126+
if (size_t(uniqueId.size()) >= sizeof(entry.uniqueId))
127+
{
128+
m_table.pop_back();
129+
return false;
130+
}
131+
132+
entry.uniqueIdSize = quint8(uniqueId.size());
133+
memcpy(entry.uniqueId, qPrintable(uniqueId), entry.uniqueIdSize);
134+
entry.uniqueId[entry.uniqueIdSize] = '\0';
135+
entry.extAddress = extAddress;
136+
entry.alarmSystemId = alarmSystemId;
137+
entry.flags = flags;
138+
entryInitArmMask(entry);
139+
140+
storeDeviceEntry(entry);
141+
return true;
142+
}
143+
144+
bool AS_DeviceTable::erase(const QLatin1String &uniqueId)
145+
{
146+
quint64 extAddress = extAddressFromUniqueId(uniqueId);
147+
auto i = getIterator(m_table, extAddress);
148+
149+
if (i != m_table.end() && DB_DeleteAlarmSystemDevice(i->uniqueId))
150+
{
151+
*i = m_table.back();
152+
m_table.pop_back();
153+
return true;
154+
}
155+
156+
return false;
157+
}
158+
159+
void AS_DeviceTable::reset(std::vector<AS_DeviceEntry> &&table)
160+
{
161+
m_table = table;
162+
}
163+
164+
void DB_LoadAlarmSystemDevices(AS_DeviceTable *devTable)
165+
{
166+
const auto dbDevices = DB_LoadAlarmSystemDevices();
167+
168+
if (dbDevices.empty())
169+
{
170+
return;
171+
}
172+
173+
std::vector<AS_DeviceEntry> table;
174+
table.reserve(dbDevices.size());
175+
176+
for (const auto &dbDev : dbDevices)
177+
{
178+
if (strlen(dbDev.uniqueid) > AS_MAX_UNIQUEID_LENGTH)
179+
{
180+
continue;
181+
}
182+
183+
table.push_back(AS_DeviceEntry());
184+
AS_DeviceEntry &entry = table.back();
185+
186+
entry.extAddress = extAddressFromUniqueId(QLatin1String(dbDev.uniqueid));
187+
entry.alarmSystemId = dbDev.alarmSystemId;
188+
entry.uniqueIdSize = quint8(strlen(dbDev.uniqueid));
189+
memcpy(entry.uniqueId, dbDev.uniqueid, entry.uniqueIdSize);
190+
entry.uniqueId[entry.uniqueIdSize] = '\0';
191+
entry.flags = dbDev.flags;
192+
entryInitArmMask(entry);
193+
}
194+
195+
devTable->reset(std::move(table));
196+
}

alarm_system_device_table.h

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 ALARM_SYSTEM_DEVICE_TABLE_H
12+
#define ALARM_SYSTEM_DEVICE_TABLE_H
13+
14+
#include <QString>
15+
#include <vector>
16+
17+
// 28:6d:97:00:01:06:41:79-01-0500 31 characters
18+
#define AS_MAX_UNIQUEID_LENGTH 31
19+
20+
#define AS_ENTRY_FLAG_ARMED_AWAY 0x00000100
21+
#define AS_ENTRY_FLAG_ARMED_STAY 0x00000200
22+
#define AS_ENTRY_FLAG_ARMED_NIGHT 0x00000400
23+
#define AS_ENTRY_FLAG_IAS_ACE 0x00000008
24+
25+
/*! \struct AS_DeviceEntry
26+
27+
Holds a uniqueid and configuration for a device in an alarm system.
28+
The size is 64 bytes to fit in one cache line.
29+
*/
30+
struct AS_DeviceEntry
31+
{
32+
char uniqueId[AS_MAX_UNIQUEID_LENGTH + 1]{0};
33+
quint64 extAddress = 0;
34+
quint32 flags = 0;
35+
quint8 uniqueIdSize = 0;
36+
quint8 alarmSystemId = 0;
37+
char armMask[4]{0};
38+
char padding[14]{0};
39+
};
40+
41+
class AS_DeviceTable
42+
{
43+
public:
44+
AS_DeviceTable();
45+
46+
const AS_DeviceEntry &get(const QString &uniqueId) const;
47+
const AS_DeviceEntry &get(quint64 extAddress) const;
48+
const AS_DeviceEntry &at(size_t index) const;
49+
bool put(const QString &uniqueId, quint32 flags, quint8 alarmSystemId);
50+
bool erase(const QLatin1String &uniqueId);
51+
size_t size() const { return m_table.size(); }
52+
void reset(std::vector<AS_DeviceEntry> && table);
53+
54+
private:
55+
const AS_DeviceEntry m_invalidEntry{};
56+
std::vector<AS_DeviceEntry> m_table;
57+
};
58+
59+
inline bool isValid(const AS_DeviceEntry &entry)
60+
{
61+
return entry.uniqueId[0] != '\0' &&
62+
entry.uniqueIdSize > 0 &&
63+
entry.alarmSystemId > 0 &&
64+
entry.extAddress != 0;
65+
}
66+
67+
void DB_LoadAlarmSystemDevices(AS_DeviceTable *devTable);
68+
69+
#endif // ALARM_SYSTEM_DEVICE_TABLE_H

0 commit comments

Comments
 (0)