Add more ui, tweak memory usage, add device configs (#578)

This commit is contained in:
Darrell 2022-08-07 07:31:42 -04:00 committed by GitHub
parent b8ea0d477d
commit f7d23a1ae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 6082 additions and 1238 deletions

214
.clang-format Normal file
View File

@ -0,0 +1,214 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 999
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Right
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME

View File

@ -1,9 +1,9 @@
#include "BleFingerprint.h" #include "BleFingerprint.h"
#include "BleFingerprintCollection.h" #include "BleFingerprintCollection.h"
#include "mbedtls/aes.h"
#include "rssi.h" #include "rssi.h"
#include "string_utils.h" #include "string_utils.h"
#include "util.h" #include "util.h"
#include "mbedtls/aes.h"
class ClientCallbacks : public BLEClientCallbacks class ClientCallbacks : public BLEClientCallbacks
{ {
@ -15,21 +15,33 @@ class ClientCallbacks : public BLEClientCallbacks
static ClientCallbacks clientCB; static ClientCallbacks clientCB;
bool BleFingerprint::shouldHide(const String& s) bool BleFingerprint::shouldHide(const String &s) {
{
if (BleFingerprintCollection::include.length() > 0 && !prefixExists(BleFingerprintCollection::include, s)) return true; if (BleFingerprintCollection::include.length() > 0 && !prefixExists(BleFingerprintCollection::include, s)) return true;
return (BleFingerprintCollection::exclude.length() > 0 && prefixExists(BleFingerprintCollection::exclude, s)); return (BleFingerprintCollection::exclude.length() > 0 && prefixExists(BleFingerprintCollection::exclude, s));
} }
bool BleFingerprint::setId(const String& newId, short newIdType, const String& newName) bool BleFingerprint::setId(const String &newId, short newIdType, const String &newName) {
{
if (newIdType < idType && idType > 0) return false; if ((idType < 0 || newIdType < 0) ? newIdType >= idType : newIdType <= idType) return false;
//Serial.printf("setId: %s %d %s\n", newId.c_str(), newIdType, newName.c_str());
hidden = shouldHide(newId);
ignore = newIdType < 0; ignore = newIdType < 0;
idType = newIdType;
if ((idType != newIdType) || !id.equals(newId)) { DeviceConfig dc;
if (BleFingerprintCollection::findDeviceConfig(newId, dc))
{
if (!dc.alias.isEmpty())
return setId(dc.alias, ID_TYPE_ALIAS, dc.name);
if (!dc.name.isEmpty())
name = dc.name;
} else
if (!newName.isEmpty() && name!=newName)
name = newName;
if (id != newId) {
hidden = shouldHide(newId);
countable = !ignore && !hidden && !BleFingerprintCollection::countIds.isEmpty() && prefixExists(BleFingerprintCollection::countIds, newId); countable = !ignore && !hidden && !BleFingerprintCollection::countIds.isEmpty() && prefixExists(BleFingerprintCollection::countIds, newId);
bool newQuery = !ignore && !BleFingerprintCollection::query.isEmpty() && prefixExists(BleFingerprintCollection::query, newId); bool newQuery = !ignore && !BleFingerprintCollection::query.isEmpty() && prefixExists(BleFingerprintCollection::query, newId);
if (newQuery != allowQuery) if (newQuery != allowQuery)
@ -41,19 +53,16 @@ bool BleFingerprint::setId(const String& newId, short newIdType, const String& n
{ {
qryDelayMillis = 30000; qryDelayMillis = 30000;
lastQryMillis = millis(); lastQryMillis = millis();
} else if (rssi < -70) } else if (rssi < -70) {
{
qryDelayMillis = 5000; qryDelayMillis = 5000;
lastQryMillis = millis(); lastQryMillis = millis();
} }
} }
} }
id = newId; id = newId;
idType = newIdType;
added = false; added = false;
} }
if (!newName.isEmpty() && !name.equals(newName)) name = newName;
return true; return true;
} }
@ -118,14 +127,12 @@ int bt_encrypt_be(const uint8_t *key, const uint8_t *plaintext, uint8_t *enc_dat
struct encryption_block struct encryption_block
{ {
uint8_t key[16]; uint8_t key[16];
uint8_t plain_text[16]; uint8_t plain_text[16];
uint8_t cipher_text[16]; uint8_t cipher_text[16];
}; };
int ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk) bool ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk) {
{
int rc;
struct encryption_block ecb; struct encryption_block ecb;
auto irk32 = (const uint32_t *)irk; auto irk32 = (const uint32_t *)irk;
@ -137,7 +144,6 @@ int ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk)
key32[2] = irk32[2]; key32[2] = irk32[2];
key32[3] = irk32[3]; key32[3] = irk32[3];
pt32[0] = 0; pt32[0] = 0;
pt32[1] = 0; pt32[1] = 0;
pt32[2] = 0; pt32[2] = 0;
@ -149,17 +155,11 @@ int ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk)
auto err = bt_encrypt_be(ecb.key, ecb.plain_text, ecb.cipher_text); auto err = bt_encrypt_be(ecb.key, ecb.plain_text, ecb.cipher_text);
if ((ecb.cipher_text[15] == rpa[0]) && (ecb.cipher_text[14] == rpa[1]) && if (ecb.cipher_text[15] != rpa[0] || ecb.cipher_text[14] != rpa[1] || ecb.cipher_text[13] != rpa[2]) return false;
(ecb.cipher_text[13] == rpa[2])) {
rc = 1; // Serial.printf("RPA resolved %d %02x%02x%02x %02x%02x%02x\n", err, rpa[0], rpa[1], rpa[2], ecb.cipher_text[15], ecb.cipher_text[14], ecb.cipher_text[13]);
} else {
rc = 0;
}
//Serial.printf("RPA resolved %d %d %02x%02x%02x %02x%02x%02x\n", rc, err, rpa[0], rpa[1], rpa[2], ecb.cipher_text[15], ecb.cipher_text[14], ecb.cipher_text[13]); return true;
return rc;
} }
void BleFingerprint::fingerprintAddress() void BleFingerprint::fingerprintAddress()
@ -171,26 +171,23 @@ void BleFingerprint::fingerprintAddress()
{ {
switch (addressType) switch (addressType)
{ {
case BLE_ADDR_PUBLIC: case BLE_ADDR_PUBLIC:
case BLE_ADDR_PUBLIC_ID: case BLE_ADDR_PUBLIC_ID:
setId(mac, ID_TYPE_PUBLIC_MAC); setId(mac, ID_TYPE_PUBLIC_MAC);
break;
case BLE_ADDR_RANDOM: {
auto naddress = address.getNative();
auto irks = BleFingerprintCollection::getIrks();
auto it = std::find_if(irks.begin(), irks.end(), [naddress](uint8_t *irk) { return ble_ll_resolv_rpa(naddress, irk); });
if (it != irks.end()) {
auto irk_hex = hexStr(*it, 16);
setId(String("irk:") + irk_hex.c_str(), ID_TYPE_KNOWN_IRK);
break; break;
case BLE_ADDR_RANDOM:
{
auto naddress = address.getNative();
auto irks = BleFingerprintCollection::irks;
auto it = std::find_if(irks.begin(), irks.end(), [naddress](std::pair<uint8_t *, String> &p) {
auto irk = std::get<0>(p);
return ble_ll_resolv_rpa(naddress, irk);
});
if (it != irks.end()) {
setId(std::get<1>(*it), ID_TYPE_KNOWN_IRK);
break;
}
} }
default: }
setId(mac, ID_TYPE_MAC); default:
break; setId(mac, ID_TYPE_RAND_MAC);
break;
} }
} }
} }
@ -295,7 +292,7 @@ void BleFingerprint::fingerprintServiceData(NimBLEAdvertisedDevice *advertisedDe
{ // found COVID-19 exposure tracker { // found COVID-19 exposure tracker
calRssi = BleFingerprintCollection::refRssi + EXPOSURE_TX; calRssi = BleFingerprintCollection::refRssi + EXPOSURE_TX;
setId("exp:" + String(strServiceData.length()), ID_TYPE_EXPOSURE); setId("exp:" + String(strServiceData.length()), ID_TYPE_EXPOSURE);
disc = hexStr(strServiceData).c_str(); //disc = hexStr(strServiceData).c_str();
} }
else if (uuid == smartTagUUID) else if (uuid == smartTagUUID)
{ // found Samsung smart tag { // found Samsung smart tag
@ -392,7 +389,7 @@ void BleFingerprint::fingerprintManufactureData(NimBLEAdvertisedDevice *advertis
BLEBeacon oBeacon = BLEBeacon(); BLEBeacon oBeacon = BLEBeacon();
oBeacon.setData(strManufacturerData); oBeacon.setData(strManufacturerData);
calRssi = oBeacon.getSignalPower(); calRssi = oBeacon.getSignalPower();
setId(Sprintf("iBeacon:%s-%u-%u", std::string(oBeacon.getProximityUUID()).c_str(), ENDIAN_CHANGE_U16(oBeacon.getMajor()), ENDIAN_CHANGE_U16(oBeacon.getMinor())), calRssi != 3 ? ID_TYPE_IBEACON : ID_TYPE_ECHO_LOST ); setId(Sprintf("iBeacon:%s-%u-%u", std::string(oBeacon.getProximityUUID()).c_str(), ENDIAN_CHANGE_U16(oBeacon.getMajor()), ENDIAN_CHANGE_U16(oBeacon.getMinor())), calRssi != 3 ? ID_TYPE_IBEACON : ID_TYPE_ECHO_LOST);
} }
else if (strManufacturerData.length() >= 4 && strManufacturerData[2] == 0x10) else if (strManufacturerData.length() >= 4 && strManufacturerData[2] == 0x10)
{ {
@ -440,11 +437,12 @@ void BleFingerprint::fingerprintManufactureData(NimBLEAdvertisedDevice *advertis
{ {
mdRssi = haveTxPower ? BleFingerprintCollection::refRssi + txPower : NO_RSSI; mdRssi = haveTxPower ? BleFingerprintCollection::refRssi + txPower : NO_RSSI;
setId(Sprintf("msft:cdp:%02x%02x", strManufacturerData[3], strManufacturerData[5]), ID_TYPE_MSFT); setId(Sprintf("msft:cdp:%02x%02x", strManufacturerData[3], strManufacturerData[5]), ID_TYPE_MSFT);
disc = Sprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", /*disc = Sprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
strManufacturerData[6], strManufacturerData[7], strManufacturerData[8], strManufacturerData[9], strManufacturerData[10], strManufacturerData[6], strManufacturerData[7], strManufacturerData[8], strManufacturerData[9], strManufacturerData[10],
strManufacturerData[11], strManufacturerData[12], strManufacturerData[13], strManufacturerData[14], strManufacturerData[15], strManufacturerData[11], strManufacturerData[12], strManufacturerData[13], strManufacturerData[14], strManufacturerData[15],
strManufacturerData[16], strManufacturerData[17], strManufacturerData[18], strManufacturerData[19], strManufacturerData[20], strManufacturerData[16], strManufacturerData[17], strManufacturerData[18], strManufacturerData[19], strManufacturerData[20],
strManufacturerData[21], strManufacturerData[22], strManufacturerData[23], strManufacturerData[24], strManufacturerData[25]); strManufacturerData[21], strManufacturerData[22], strManufacturerData[23], strManufacturerData[24], strManufacturerData[25]);
*/
} }
else if (manuf == "0075") // samsung else if (manuf == "0075") // samsung
{ {
@ -549,7 +547,7 @@ void BleFingerprint::fill(JsonObject *doc)
bool BleFingerprint::report(JsonObject *doc) bool BleFingerprint::report(JsonObject *doc)
{ {
if (ignore || idType == 0 || hidden) if (ignore || idType <= ID_TYPE_RAND_MAC || hidden)
return false; return false;
if (reported || !hasValue) if (reported || !hasValue)
@ -666,3 +664,8 @@ bool BleFingerprint::shouldCount()
return counting; return counting;
} }
void BleFingerprint::expire()
{
lastSeenMillis = 0;
}

View File

@ -16,18 +16,21 @@
#define ID_TYPE_TX_POW short(1) #define ID_TYPE_TX_POW short(1)
#define NO_ID_TYPE short(0)
#define ID_TYPE_ECHO_LOST short(-10) #define ID_TYPE_ECHO_LOST short(-10)
#define ID_TYPE_MISC_APPLE short(-5) #define ID_TYPE_MISC_APPLE short(-5)
#define ID_TYPE_MAC short(0) #define ID_TYPE_RAND_MAC short(1)
#define ID_TYPE_AD short(10) #define ID_TYPE_AD short(10)
#define ID_TYPE_SD short(15) #define ID_TYPE_SD short(15)
#define ID_TYPE_MD short(20) #define ID_TYPE_MD short(20)
#define ID_TYPE_MISC short(30) #define ID_TYPE_MISC short(30)
#define ID_TYPE_FINDMY short(32) #define ID_TYPE_FINDMY short(32)
#define ID_TYPE_NAME short(35) #define ID_TYPE_NAME short(35)
#define ID_TYPE_PUBLIC_MAC short(50) #define ID_TYPE_MSFT short(40)
#define ID_TYPE_MSFT short(100) #define ID_TYPE_UNIQUE short(50)
#define ID_TYPE_PUBLIC_MAC short(55)
#define ID_TYPE_SONOS short(105) #define ID_TYPE_SONOS short(105)
#define ID_TYPE_GARMIN short(107) #define ID_TYPE_GARMIN short(107)
#define ID_TYPE_MITHERM short(110) #define ID_TYPE_MITHERM short(110)
@ -38,7 +41,7 @@
#define ID_TYPE_ITRACK short(127) #define ID_TYPE_ITRACK short(127)
#define ID_TYPE_NUT short(128) #define ID_TYPE_NUT short(128)
#define ID_TYPE_TRACKR short(130) #define ID_TYPE_TRACKR short(130)
#define ID_TYPE_TILE short( 135) #define ID_TYPE_TILE short(135)
#define ID_TYPE_MEATER short(140) #define ID_TYPE_MEATER short(140)
#define ID_TYPE_TRACTIVE short(142) #define ID_TYPE_TRACTIVE short(142)
#define ID_TYPE_VANMOOF short(145) #define ID_TYPE_VANMOOF short(145)
@ -51,7 +54,7 @@
#define ID_TYPE_EBEACON short(220) #define ID_TYPE_EBEACON short(220)
#define ID_TYPE_ABEACON short(230) #define ID_TYPE_ABEACON short(230)
#define ID_TYPE_IBEACON short(240) #define ID_TYPE_IBEACON short(240)
#define ID_TYPE_ALIAS short(250)
class BleFingerprintCollection; class BleFingerprintCollection;
@ -79,6 +82,8 @@ public:
String getMac() const { return SMacf(address); } String getMac() const { return SMacf(address); }
short getIdType() const { return idType; }
String const getDiscriminator() { return disc; } String const getDiscriminator() { return disc; }
float getDistance() const { return output.value.position; } float getDistance() const { return output.value.position; }
@ -91,7 +96,7 @@ public:
NimBLEAddress const getAddress() { return address; } NimBLEAddress const getAddress() { return address; }
unsigned long getMsSinceLastSeen() const { return millis() - lastSeenMillis; }; unsigned long getMsSinceLastSeen() const { return lastSeenMillis ? millis() - lastSeenMillis : 4294967295; };
unsigned long getMsSinceFirstSeen() const { return millis() - firstSeenMillis; }; unsigned long getMsSinceFirstSeen() const { return millis() - firstSeenMillis; };
@ -115,6 +120,8 @@ public:
bool shouldCount(); bool shouldCount();
void fingerprintAddress(); void fingerprintAddress();
void expire();
private: private:
static bool shouldHide(const String &s); static bool shouldHide(const String &s);
@ -122,7 +129,7 @@ private:
bool hasValue = false, added = false, close = false, reported = false, ignore = false, allowQuery = false, didQuery = false, rmAsst = false, hidden = false, connectable = false, countable = false, counting = false; bool hasValue = false, added = false, close = false, reported = false, ignore = false, allowQuery = false, didQuery = false, rmAsst = false, hidden = false, connectable = false, countable = false, counting = false;
NimBLEAddress address; NimBLEAddress address;
String id, name, disc; String id, name, disc;
short int idType = 0; short int idType = NO_ID_TYPE;
int rssi = -100, calRssi = NO_RSSI, mdRssi = NO_RSSI, asRssi = NO_RSSI, newest = NO_RSSI, recent = NO_RSSI, oldest = NO_RSSI; int rssi = -100, calRssi = NO_RSSI, mdRssi = NO_RSSI, asRssi = NO_RSSI, newest = NO_RSSI, recent = NO_RSSI, oldest = NO_RSSI;
unsigned int qryAttempts = 0, qryDelayMillis = 0; unsigned int qryAttempts = 0, qryDelayMillis = 0;
float raw = 0, lastReported = 0, temp = 0, humidity = 0; float raw = 0, lastReported = 0, temp = 0, humidity = 0;
@ -139,11 +146,8 @@ private:
bool filter(); bool filter();
void fingerprint(NimBLEAdvertisedDevice *advertisedDevice); void fingerprint(NimBLEAdvertisedDevice *advertisedDevice);
void fingerprintServiceAdvertisements(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceAdvCount, bool haveTxPower, int8_t txPower); void fingerprintServiceAdvertisements(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceAdvCount, bool haveTxPower, int8_t txPower);
void fingerprintServiceData(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceDataCount, bool haveTxPower, int8_t txPower); void fingerprintServiceData(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceDataCount, bool haveTxPower, int8_t txPower);
void fingerprintManufactureData(NimBLEAdvertisedDevice *advertisedDevice, bool haveTxPower, int8_t txPower); void fingerprintManufactureData(NimBLEAdvertisedDevice *advertisedDevice, bool haveTxPower, int8_t txPower);
}; };

View File

@ -5,22 +5,33 @@
String BleFingerprintCollection::include{}, BleFingerprintCollection::exclude{}, BleFingerprintCollection::query{}, BleFingerprintCollection::knownMacs{}, BleFingerprintCollection::knownIrks{}, BleFingerprintCollection::countIds{}; String BleFingerprintCollection::include{}, BleFingerprintCollection::exclude{}, BleFingerprintCollection::query{}, BleFingerprintCollection::knownMacs{}, BleFingerprintCollection::knownIrks{}, BleFingerprintCollection::countIds{};
float BleFingerprintCollection::skipDistance = 0.0f, BleFingerprintCollection::maxDistance = 0.0f, BleFingerprintCollection::absorption = 3.5f, BleFingerprintCollection::countEnter = 2, BleFingerprintCollection::countExit = 4; float BleFingerprintCollection::skipDistance = 0.0f, BleFingerprintCollection::maxDistance = 0.0f, BleFingerprintCollection::absorption = 3.5f, BleFingerprintCollection::countEnter = 2, BleFingerprintCollection::countExit = 4;
int BleFingerprintCollection::refRssi = 0, BleFingerprintCollection::forgetMs = 0, BleFingerprintCollection::skipMs = 0, BleFingerprintCollection::countMs = 10000; int BleFingerprintCollection::refRssi = 0, BleFingerprintCollection::forgetMs = 0, BleFingerprintCollection::skipMs = 0, BleFingerprintCollection::countMs = 10000;
std::vector<std::pair<uint8_t*,String>> BleFingerprintCollection::irks; std::vector<DeviceConfig> BleFingerprintCollection::deviceConfigs;
std::vector<uint8_t *> BleFingerprintCollection::irks;
bool BleFingerprintCollection::config(String& id, String& json) { bool BleFingerprintCollection::config(String &id, String &json) {
DynamicJsonDocument doc(1024); DynamicJsonDocument doc(1024);
deserializeJson(doc, json); deserializeJson(doc, json);
DeviceConfig config = {};
config.id = id;
auto alias = doc["id"].as<String>();
if (alias != id) config.alias = alias;
config.calRssi = doc["rssi@1m"];
config.name = doc["name"].as<String>();
deviceConfigs.push_back(config);
auto p = id.indexOf("irk:"); auto p = id.indexOf("irk:");
if (p == 0) { if (p == 0) {
auto irk_hex = id.substring(4); auto irk_hex = id.substring(4);
uint8_t* irk = new uint8_t[16]; uint8_t *irk = new uint8_t[16];
if (!hextostr(irk_hex, irk, 16)) if (!hextostr(irk_hex, irk, 16))
return false; return false;
irks.push_back({irk, doc["id"]}); irks.push_back(irk);
for(auto it = std::begin(fingerprints); it != std::end(fingerprints); ++it) for (auto it = std::begin(fingerprints); it != std::end(fingerprints); ++it)
(*it)->fingerprintAddress(); (*it)->fingerprintAddress();
} }
return true; return true;
} }
@ -28,14 +39,14 @@ void BleFingerprintCollection::connectToWifi() {
std::istringstream iss(BleFingerprintCollection::knownIrks.c_str()); std::istringstream iss(BleFingerprintCollection::knownIrks.c_str());
std::string irk_hex; std::string irk_hex;
while (iss >> irk_hex) { while (iss >> irk_hex) {
uint8_t* irk = new uint8_t[16]; uint8_t *irk = new uint8_t[16];
if (!hextostr(irk_hex.c_str(), irk, 16)) if (!hextostr(irk_hex.c_str(), irk, 16))
continue; continue;
irks.push_back({irk, String("irk:") + irk_hex.c_str()}); irks.push_back(irk);
} }
} }
bool BleFingerprintCollection::command(String& command, String& pay) { bool BleFingerprintCollection::command(String &command, String &pay) {
if (command == "max_distance") if (command == "max_distance")
{ {
@ -116,21 +127,22 @@ BleFingerprint *BleFingerprintCollection::getFingerprintInternal(BLEAdvertisedDe
{ {
auto mac = advertisedDevice->getAddress(); auto mac = advertisedDevice->getAddress();
auto it = std::find_if(fingerprints.begin(), fingerprints.end(), [mac](BleFingerprint *f) auto it = std::find_if(fingerprints.rbegin(), fingerprints.rend(), [mac](BleFingerprint *f) { return f->getAddress() == mac; });
{ return f->getAddress() == mac; }); if (it != fingerprints.rend())
if (it != fingerprints.end())
return *it; return *it;
auto created = new BleFingerprint(this, advertisedDevice, ONE_EURO_FCMIN, ONE_EURO_BETA, ONE_EURO_DCUTOFF); auto created = new BleFingerprint(this, advertisedDevice, ONE_EURO_FCMIN, ONE_EURO_BETA, ONE_EURO_DCUTOFF);
auto it2 = std::find_if(fingerprints.begin(), fingerprints.end(), [created](BleFingerprint *f) auto it2 = std::find_if(fingerprints.begin(), fingerprints.end(), [created](BleFingerprint *f) { return f->getId() == created->getId(); });
{ return f->getId() == created->getId(); });
if (it2 != fingerprints.end()) if (it2 != fingerprints.end())
{ {
auto found = *it2; auto found = *it2;
//Serial.printf("Detected mac switch for fingerprint id %s\n", found->getId().c_str());
created->setInitial(found->getRssi(), found->getDistance()); created->setInitial(found->getRssi(), found->getDistance());
if (found->getIdType()>ID_TYPE_UNIQUE)
found->expire();
} }
fingerprints.push_front(created); fingerprints.push_back(created);
return created; return created;
} }
@ -144,18 +156,28 @@ BleFingerprint *BleFingerprintCollection::getFingerprint(BLEAdvertisedDevice *ad
return f; return f;
} }
const std::list<BleFingerprint *> BleFingerprintCollection::getCopy() const std::vector<BleFingerprint *> BleFingerprintCollection::getCopy() {
{
if (xSemaphoreTake(fingerprintSemaphore, 1000) != pdTRUE) if (xSemaphoreTake(fingerprintSemaphore, 1000) != pdTRUE)
log_e("Couldn't take semaphore!"); log_e("Couldn't take semaphore!");
cleanupOldFingerprints(); cleanupOldFingerprints();
std::list<BleFingerprint *> copy(fingerprints); std::vector<BleFingerprint *> copy(fingerprints);
if (xSemaphoreGive(fingerprintSemaphore) != pdTRUE) if (xSemaphoreGive(fingerprintSemaphore) != pdTRUE)
log_e("Couldn't give semaphore!"); log_e("Couldn't give semaphore!");
return std::move(copy); return std::move(copy);
} }
const std::list<BleFingerprint *>* const BleFingerprintCollection::getNative() const std::vector<BleFingerprint *> *const BleFingerprintCollection::getNative() { return &fingerprints; }
{
return &fingerprints; std::vector<uint8_t *> BleFingerprintCollection::getIrks() { return irks; }
bool BleFingerprintCollection::findDeviceConfig(const String &id, DeviceConfig &config) {
auto it = std::find_if(deviceConfigs.begin(), deviceConfigs.end(), [id](DeviceConfig dc) { return dc.id == id; });
if (it == deviceConfigs.end()) return false;
config = (*it);
return true;
}
std::vector<DeviceConfig> BleFingerprintCollection::getConfigs()
{
return deviceConfigs;
} }

View File

@ -12,6 +12,13 @@
#define ALLOW_BLE_CONTROLLER_RESTART_AFTER_SECS 1800 #define ALLOW_BLE_CONTROLLER_RESTART_AFTER_SECS 1800
#endif #endif
struct DeviceConfig {
String id;
String alias;
String name;
uint8_t calRssi = 127;
};
class BleFingerprintCollection : public BLEAdvertisedDeviceCallbacks class BleFingerprintCollection : public BLEAdvertisedDeviceCallbacks
{ {
public: public:
@ -22,13 +29,12 @@ public:
} }
BleFingerprint *getFingerprint(BLEAdvertisedDevice *advertisedDevice); BleFingerprint *getFingerprint(BLEAdvertisedDevice *advertisedDevice);
void cleanupOldFingerprints(); void cleanupOldFingerprints();
const std::list<BleFingerprint *>* const getNative(); const std::vector<BleFingerprint *> *const getNative();
const std::list<BleFingerprint *> getCopy(); const std::vector<BleFingerprint *> getCopy();
void setDisable(bool disable) { _disable = disable; } void setDisable(bool disable) { _disable = disable; }
void connectToWifi(); void connectToWifi();
bool command(String& command, String& pay); bool command(String& command, String& pay);
bool config(String& id, String& json); bool config(String &id, String &json);
static std::vector<std::pair<uint8_t*,String>> irks;
static String knownMacs, knownIrks, include, exclude, query; static String knownMacs, knownIrks, include, exclude, query;
static float skipDistance, maxDistance, absorption; static float skipDistance, maxDistance, absorption;
static int refRssi, forgetMs, skipMs; static int refRssi, forgetMs, skipMs;
@ -36,13 +42,20 @@ public:
static float countEnter, countExit; static float countEnter, countExit;
static int countMs; static int countMs;
static std::vector<uint8_t *> getIrks();
static bool findDeviceConfig(const String &id, DeviceConfig &config);
static std::vector<DeviceConfig> getConfigs();
private: private:
static std::vector<DeviceConfig> deviceConfigs;
static std::vector<uint8_t *> irks;
bool _disable = false; bool _disable = false;
unsigned long lastCleanup = 0; unsigned long lastCleanup = 0;
SemaphoreHandle_t fingerprintSemaphore; SemaphoreHandle_t fingerprintSemaphore;
std::list<BleFingerprint *> fingerprints; std::vector<BleFingerprint *> fingerprints;
BleFingerprint *getFingerprintInternal(BLEAdvertisedDevice *advertisedDevice); BleFingerprint *getFingerprintInternal(BLEAdvertisedDevice *advertisedDevice);
void onResult(BLEAdvertisedDevice *advertisedDevice) override void onResult(BLEAdvertisedDevice *advertisedDevice) override

View File

@ -46,6 +46,15 @@ String kebabify(const String &text)
return kebabify(s).c_str(); return kebabify(s).c_str();
} }
std::string hexStr(const uint8_t *data, int len) {
std::string s(len * 2, ' ');
for (int i = 0; i < len; ++i) {
s[2 * i] = hexmap[(data[i] & 0xF0) >> 4];
s[2 * i + 1] = hexmap[data[i] & 0x0F];
}
return s;
}
std::string hexStr(const char *data, unsigned int len) std::string hexStr(const char *data, unsigned int len)
{ {
std::string s(len * 2, ' '); std::string s(len * 2, ' ');

View File

@ -4,35 +4,35 @@
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8)) #define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8))
static BLEUUID eddystoneUUID((uint16_t)0xFEAA); const BLEUUID eddystoneUUID((uint16_t)0xFEAA);
static BLEUUID tileUUID((uint16_t)0xFEED); const BLEUUID tileUUID((uint16_t)0xFEED);
static BLEUUID exposureUUID((uint16_t)0xFD6F); const BLEUUID exposureUUID((uint16_t)0xFD6F);
static BLEUUID smartTagUUID((uint16_t)0xFD5A); const BLEUUID smartTagUUID((uint16_t)0xFD5A);
static BLEUUID sonosUUID((uint16_t)0xFE07); const BLEUUID sonosUUID((uint16_t)0xFE07);
static BLEUUID itagUUID((uint16_t)0xffe0); const BLEUUID itagUUID((uint16_t)0xffe0);
static BLEUUID miThermUUID(uint16_t(0x181A)); const BLEUUID miThermUUID(uint16_t(0x181A));
static BLEUUID trackrUUID((uint16_t)0x0F3E); const BLEUUID trackrUUID((uint16_t)0x0F3E);
static BLEUUID vanmoofUUID(0x6acc5540, 0xe631, 0x4069, 0x944db8ca7598ad50); const BLEUUID vanmoofUUID(0x6acc5540, 0xe631, 0x4069, 0x944db8ca7598ad50);
static BLEUUID tractiveUUID(0x20130001, 0x0719, 0x4b6e, 0xbe5d158ab92fa5a4); const BLEUUID tractiveUUID(0x20130001, 0x0719, 0x4b6e, 0xbe5d158ab92fa5a4);
static BLEUUID nutUUID((uint16_t)0x1803); const BLEUUID nutUUID((uint16_t)0x1803);
static BLEUUID fitbitUUID(0xadabfb00, 0x6e7d, 0x4601, 0xbda2bffaa68956ba); const BLEUUID fitbitUUID(0xadabfb00, 0x6e7d, 0x4601, 0xbda2bffaa68956ba);
static BLEUUID roomAssistantService(0x5403c8a7, 0x5c96, 0x47e9, 0x9ab859e373d875a7); const BLEUUID roomAssistantService(0x5403c8a7, 0x5c96, 0x47e9, 0x9ab859e373d875a7);
static BLEUUID rootAssistantCharacteristic(0x21c46f33, 0xe813, 0x4407, 0x86012ad281030052); const BLEUUID rootAssistantCharacteristic(0x21c46f33, 0xe813, 0x4407, 0x86012ad281030052);
static BLEUUID meaterService(0xa75cc7fc, 0xc956, 0x488f, 0xac2a2dbc08b63a04); const BLEUUID meaterService(0xa75cc7fc, 0xc956, 0x488f, 0xac2a2dbc08b63a04);
static BLEUUID meaterCharacteristic(0x7edda774, 0x045e, 0x4bbf, 0x909b45d1991a2876); const BLEUUID meaterCharacteristic(0x7edda774, 0x045e, 0x4bbf, 0x909b45d1991a2876);
static BLEUUID genericAccessService(uint16_t(0x1800)); const BLEUUID genericAccessService(uint16_t(0x1800));
static BLEUUID nameChar(uint16_t(0x2A00)); const BLEUUID nameChar(uint16_t(0x2A00));
static BLEUUID deviceInformationService(uint16_t(0x180A)); const BLEUUID deviceInformationService(uint16_t(0x180A));
static BLEUUID modelChar(uint16_t(0x2A24)); const BLEUUID modelChar(uint16_t(0x2A24));
static BLEUUID fwRevChar(uint16_t(0x2A26)); const BLEUUID fwRevChar(uint16_t(0x2A26));
static BLEUUID hwRevChar(uint16_t(0x2A27)); const BLEUUID hwRevChar(uint16_t(0x2A27));
static BLEUUID manufChar(uint16_t(0x2A29)); const BLEUUID manufChar(uint16_t(0x2A29));
static int median_of_3(int a, int b, int c) static int median_of_3(int a, int b, int c)
{ {

View File

@ -181,9 +181,7 @@ namespace Enrollment
return true; return true;
} }
void Setup() void Setup() {
{
//NimBLEDevice::setOwnAddrType(BLE_ADDR_RANDOM, true);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT); NimBLEDevice::setSecurityIOCap(BLE_HS_IO_NO_INPUT_OUTPUT);
NimBLEDevice::setSecurityAuth(true, true, true); NimBLEDevice::setSecurityAuth(true, true, true);
@ -272,7 +270,7 @@ namespace Enrollment
if (command == "enroll") if (command == "enroll")
{ {
id = pay.equals("PRESS") ? "" : pay; id = pay.equals("PRESS") ? "" : pay;
enrolling = !enrolling; enrolling = true;
return true; return true;
} }
return false; return false;

View File

@ -15,8 +15,21 @@
namespace HttpServer namespace HttpServer
{ {
bool deserializeState(JsonObject root ){return false;} void serializeConfigs(JsonObject& root)
bool deserializeConfig(JsonObject doc, bool fromFS = false){return false;} {
JsonArray devices = root.createNestedArray("configs");
auto f = BleFingerprintCollection::getConfigs();
for (auto it = f.begin(); it != f.end(); ++it)
{
JsonObject node = devices.createNestedObject();
node["id"]=it->id;
node["alias"] = it->alias;
node["name"]=it->name;
node["rss@1m"]=it->calRssi;
}
}
void serializeDevices(JsonObject& root) void serializeDevices(JsonObject& root)
{ {
JsonArray devices = root.createNestedArray("devices"); JsonArray devices = root.createNestedArray("devices");
@ -31,20 +44,30 @@ namespace HttpServer
} }
} }
bool servingJson = false;
void serveJson(AsyncWebServerRequest* request) { void serveJson(AsyncWebServerRequest* request) {
byte subJson = 0; if (servingJson) request->send(429, "Too Many Requests", "Too Many Requests");
servingJson = true;
const String& url = request->url(); const String& url = request->url();
short subJson = 0;
if (url.indexOf("devices") > 0) subJson = 1; if (url.indexOf("devices") > 0) subJson = 1;
if (url.indexOf("configs") > 0) subJson = 2;
AsyncJsonResponse* response = new AsyncJsonResponse(false, JSON_BUFFER_SIZE); AsyncJsonResponse* response = new AsyncJsonResponse(false, JSON_BUFFER_SIZE);
JsonObject root = response->getRoot(); JsonObject root = response->getRoot();
//root["room"] = room; root["room"] = room;
switch (subJson) switch (subJson)
{ {
case 1: case 1:
serializeDevices(root); break; serializeDevices(root);
break;
case 2:
serializeConfigs(root);
break;
} }
response->setLength(); response->setLength();
request->send(response); request->send(response);
servingJson = false;
} }
void serializeConfig(){}; void serializeConfig(){};
@ -60,14 +83,10 @@ namespace HttpServer
request->send(response); request->send(response);
}); });
server->on("/devices", HTTP_GET, [](AsyncWebServerRequest *request) server->on("/devices", HTTP_GET, serveIndexHtml);
{ serveIndexHtml(request); }); server->on("/bundle.css", HTTP_GET, serveBundleCss);
server->on("/bundle.css", HTTP_GET, [](AsyncWebServerRequest *request) server->on("/index.js", HTTP_GET, serveIndexJs);
{ serveBundleCss(request); }); server->on("/json", HTTP_GET, serveJson);
server->on("/index.js", HTTP_GET, [](AsyncWebServerRequest *request)
{ serveIndexJs(request); });
server->on("/json", HTTP_GET, [](AsyncWebServerRequest *request)
{ serveJson(request); });
AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request, JsonVariant &json) AsyncCallbackJsonWebHandler *handler = new AsyncCallbackJsonWebHandler("/json", [](AsyncWebServerRequest *request, JsonVariant &json)
{ {
@ -83,25 +102,9 @@ namespace HttpServer
return; return;
} }
const String& url = request->url(); const String& url = request->url();
isConfig = url.indexOf("cfg") > -1;
if (!isConfig)
verboseResponse = deserializeState(root);
else
verboseResponse = deserializeConfig(root);
} }
if (verboseResponse) request->send(200, "application/json", F("{\"success\":true}"));
{ });
if (!isConfig)
{
serveJson(request);
return; //if JSON contains "v"
}
else
{
serializeConfig(); //Save new settings to FS
}
}
request->send(200, "application/json", F("{\"success\":true}")); });
server->addHandler(handler); server->addHandler(handler);
} }
} }

View File

@ -1,37 +1,193 @@
/* /*
* Binary array for the Web UI. * Binary array for the Web UI.
* gzip is used for smaller size and improved speeds. * Gzip is used for smaller size and improved speeds.
*
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
* to find out how to easily modify the web UI source!
*/ */
// Autogenerated do not edit!! // Autogenerated do not edit!!
const uint16_t BUNDLE_CSS_L = 356; const uint16_t BUNDLE_CSS_L = 2904;
const uint8_t BUNDLE_CSS[] PROGMEM = { const uint8_t BUNDLE_CSS[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0x95, 0x51, 0xdb, 0x4a, 0x03, 0x31, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x13, 0xed, 0x1a, 0x5d, 0x6f, 0xdc, 0xb8,
0x10, 0x7d, 0xef, 0x57, 0x04, 0x44, 0x50, 0x68, 0xca, 0xb6, 0xb4, 0x56, 0xd2, 0x27, 0xf1, 0x45, 0xf1, 0xb9, 0xf7, 0x2b, 0xd4, 0x0b, 0x02, 0xd8, 0x01, 0xa5, 0x48, 0xda, 0xd5, 0xae, 0xad, 0x45,
0xc1, 0x37, 0xbf, 0x60, 0x36, 0x99, 0xdd, 0x1d, 0x9b, 0x4d, 0x96, 0x64, 0x7a, 0xa7, 0xff, 0x6e, 0x8b, 0x5e, 0x0f, 0x77, 0xe8, 0x01, 0x97, 0x7b, 0xb8, 0xb4, 0x4f, 0x49, 0x1e, 0xb8, 0x12, 0x77,
0xb2, 0x5a, 0xb5, 0x0a, 0x8a, 0x04, 0x12, 0x32, 0x73, 0xce, 0xcc, 0x39, 0x33, 0x2d, 0x90, 0x1b, 0xc5, 0x58, 0x12, 0x55, 0x4a, 0xb2, 0x77, 0xad, 0x6e, 0x7f, 0x7b, 0x87, 0xa4, 0x48, 0x51, 0x5a,
0xc5, 0x35, 0x5a, 0x46, 0xd9, 0xd4, 0x85, 0xaf, 0xe6, 0x87, 0x0e, 0x8c, 0x21, 0x57, 0xab, 0x71, 0xd9, 0xeb, 0xb4, 0x3d, 0xa0, 0x05, 0x62, 0xc4, 0xb1, 0x38, 0x33, 0x1c, 0x0e, 0xe7, 0x8b, 0x9c,
0xc0, 0xf6, 0xd8, 0x8c, 0xbf, 0x65, 0x19, 0xb7, 0x2c, 0xc1, 0x52, 0xed, 0x94, 0x46, 0xc7, 0x18, 0x91, 0x7e, 0xf7, 0xf6, 0xcd, 0xef, 0x9d, 0x06, 0xd3, 0xfc, 0x81, 0x96, 0x69, 0x52, 0xd7, 0xce,
0x8e, 0x2a, 0x78, 0xcf, 0xe2, 0x30, 0x10, 0x42, 0x7b, 0xeb, 0x83, 0x8c, 0xba, 0xc1, 0x16, 0x95, 0xfd, 0xc2, 0x0b, 0xbc, 0xb5, 0xf3, 0x0f, 0xe7, 0xdd, 0x4f, 0x7f, 0x75, 0x7e, 0xa6, 0x09, 0x29,
0x48, 0x98, 0x86, 0x85, 0x81, 0xb0, 0x5c, 0xa4, 0x5c, 0xe5, 0x1d, 0xcb, 0x0a, 0x5a, 0xb2, 0x3b, 0x6b, 0x02, 0xa3, 0xac, 0x69, 0xaa, 0x3a, 0x7e, 0xfb, 0xd6, 0x22, 0xf5, 0x12, 0x56, 0xbc, 0x79,
0x25, 0x1e, 0x33, 0x71, 0x28, 0xee, 0xd6, 0xe8, 0x28, 0xbd, 0x0f, 0x68, 0xd7, 0xc8, 0xa4, 0x21, 0xfb, 0x06, 0xc5, 0x78, 0xd7, 0x10, 0x8e, 0xe2, 0x2d, 0xd9, 0x31, 0x4e, 0xba, 0x2d, 0xe3, 0x29,
0x85, 0x02, 0x81, 0x1d, 0x8a, 0x08, 0x2e, 0xca, 0x88, 0x81, 0xaa, 0x0f, 0x6e, 0xa4, 0x7d, 0x2a, 0xe1, 0xb1, 0xef, 0xd4, 0x2c, 0xa7, 0xa9, 0xf3, 0x8a, 0x44, 0x64, 0x4d, 0xb6, 0x9b, 0x2d, 0x3b,
0x3a, 0xbe, 0xe9, 0xb6, 0x39, 0x64, 0xc9, 0x25, 0x49, 0x98, 0x5b, 0x28, 0x31, 0x99, 0xe6, 0xe0, 0xb8, 0x35, 0x7d, 0xa4, 0xe5, 0x3e, 0x56, 0x14, 0x2e, 0x40, 0x4e, 0x93, 0xb9, 0xae, 0xdb, 0x3c,
0x49, 0x80, 0x12, 0xa1, 0x2e, 0xe1, 0x6a, 0x32, 0x9b, 0x0d, 0xc5, 0xe7, 0x55, 0x8c, 0x6e, 0xe7, 0xb8, 0x09, 0x2b, 0x1b, 0x52, 0x36, 0xf1, 0xb7, 0xdf, 0x9e, 0xb2, 0xa6, 0xc8, 0x3b, 0xf7, 0x81,
0xd7, 0x99, 0x59, 0x82, 0x5e, 0xd6, 0xc1, 0xaf, 0x9c, 0x91, 0xef, 0xf0, 0x8b, 0xc9, 0x34, 0x9f, 0x6c, 0xef, 0x68, 0xe3, 0x36, 0xe4, 0xd0, 0x08, 0x16, 0xc4, 0xc5, 0xe9, 0xe7, 0xb6, 0x6e, 0xe2,
0xbe, 0xc2, 0x5b, 0xab, 0x9d, 0xe3, 0x06, 0x23, 0x45, 0x25, 0x9c, 0x77, 0x98, 0x59, 0xbd, 0xed, 0xc0, 0xf7, 0x5f, 0x6f, 0x76, 0x40, 0xed, 0xee, 0x70, 0x41, 0xf3, 0x63, 0xdc, 0x52, 0xb7, 0xc6,
0x80, 0xce, 0x24, 0x4d, 0x69, 0x36, 0xc2, 0x77, 0x4c, 0x6d, 0x52, 0xf4, 0x84, 0x35, 0x95, 0x64, 0x65, 0xed, 0xd6, 0x84, 0xd3, 0x1d, 0xaa, 0x8f, 0x75, 0x43, 0x0a, 0xb7, 0xa5, 0xc8, 0xc5, 0x55,
0x89, 0x77, 0x19, 0x25, 0x37, 0x58, 0x2e, 0x29, 0xf9, 0xec, 0xab, 0xb4, 0x69, 0x2e, 0x4d, 0x8f, 0x95, 0x13, 0x57, 0x01, 0xd0, 0x9f, 0x73, 0x5a, 0xde, 0xbd, 0xc3, 0xc9, 0x7b, 0x39, 0xfc, 0x11,
0x06, 0xc7, 0xc9, 0x16, 0x41, 0x44, 0xd3, 0xc3, 0x5a, 0xbf, 0x97, 0x3e, 0x6e, 0x7f, 0xe0, 0xea, 0xe6, 0xa3, 0xf7, 0x64, 0xcf, 0x88, 0xf3, 0xb7, 0x9f, 0xd0, 0xaf, 0x6c, 0xcb, 0x1a, 0x86, 0xfe,
0x00, 0xbb, 0xa8, 0xc1, 0xe2, 0xd7, 0x62, 0x7d, 0xeb, 0xec, 0x5e, 0x82, 0x79, 0x59, 0xc5, 0xe4, 0x42, 0xf2, 0x7b, 0xd2, 0xd0, 0x04, 0x3b, 0xbf, 0x90, 0x96, 0xa0, 0xef, 0x38, 0xc5, 0x39, 0xfa,
0x77, 0x5c, 0x14, 0x97, 0x8b, 0xc1, 0x71, 0xc0, 0x50, 0x5a, 0x3c, 0xed, 0xc7, 0x44, 0xa8, 0xe6, 0x05, 0x30, 0xce, 0x7b, 0xe0, 0x8f, 0xac, 0x45, 0xbe, 0x13, 0x9c, 0x9d, 0xef, 0x59, 0xce, 0xb8,
0x7c, 0xfe, 0x3b, 0x6c, 0xc8, 0x70, 0xa3, 0x32, 0xfe, 0x38, 0xa2, 0xf8, 0xec, 0xc3, 0xdf, 0x14, 0xf3, 0x43, 0xc1, 0x3e, 0x53, 0xc3, 0x6b, 0x3a, 0x7c, 0x7f, 0x2c, 0xb6, 0xac, 0xe7, 0x62, 0xd1,
0xbd, 0x0a, 0x31, 0x4d, 0xa5, 0xf3, 0xd4, 0x6f, 0x37, 0xd1, 0xee, 0x2d, 0xe9, 0xe5, 0x7f, 0x79, 0x6f, 0x40, 0x1a, 0xe2, 0x66, 0x84, 0xee, 0x33, 0xd8, 0x91, 0x17, 0x6d, 0xdc, 0x82, 0x3d, 0xba,
0x1c, 0xce, 0xf3, 0x82, 0x1b, 0x11, 0xd1, 0xa2, 0xfe, 0x45, 0xe2, 0x2b, 0x4d, 0xc8, 0x83, 0x61, 0x0d, 0xde, 0xca, 0xad, 0xc6, 0xcb, 0x8d, 0xcb, 0xec, 0xd1, 0xf0, 0x78, 0xda, 0xb2, 0xf4, 0xd8,
0x8d, 0x02, 0x00, 0x00 0xd9, 0xb3, 0x69, 0x99, 0x81, 0x4c, 0xcd, 0xa6, 0xc0, 0x7c, 0x4f, 0xcb, 0xd8, 0x3f, 0x65, 0xbc,
0xb7, 0x86, 0xdb, 0xb0, 0xca, 0x7d, 0xa0, 0x69, 0x93, 0xc5, 0x41, 0x75, 0xd8, 0x24, 0x62, 0x79,
0x43, 0xde, 0xcf, 0xf6, 0x4f, 0x78, 0xbb, 0xe5, 0xf1, 0x03, 0x00, 0xc9, 0xd5, 0x87, 0x86, 0x36,
0x39, 0xf9, 0x74, 0x3d, 0x56, 0x7e, 0x4a, 0x12, 0xc6, 0x71, 0x43, 0x59, 0x19, 0xb7, 0x25, 0xb0,
0x15, 0x8b, 0x3b, 0x29, 0x6b, 0x1a, 0x92, 0x6e, 0x2e, 0x11, 0x9c, 0xb2, 0x00, 0x65, 0x21, 0xca,
0x16, 0x28, 0x5b, 0xa2, 0x2c, 0x42, 0xd9, 0xaa, 0x93, 0xa6, 0x93, 0x9b, 0xd1, 0xa2, 0x48, 0xc8,
0xc3, 0x68, 0x37, 0x27, 0xdc, 0x8d, 0xc5, 0x9d, 0x2e, 0xa4, 0xe9, 0xb6, 0xa8, 0x6e, 0x38, 0x2b,
0xf7, 0x9d, 0xcd, 0x04, 0x34, 0x0e, 0x62, 0x9c, 0x12, 0x96, 0x12, 0x74, 0xb7, 0x4d, 0x51, 0xc5,
0x09, 0x58, 0xaf, 0xa8, 0xba, 0x89, 0xdb, 0x14, 0xac, 0x64, 0x75, 0x85, 0x13, 0x82, 0xde, 0xff,
0xf8, 0x0e, 0x9e, 0xdd, 0x5f, 0xc9, 0xbe, 0xcd, 0x31, 0x47, 0xef, 0x48, 0x99, 0x33, 0x04, 0x20,
0x9c, 0x30, 0xf4, 0x3d, 0x2b, 0xc1, 0xa9, 0x71, 0x8d, 0x7e, 0xa6, 0x5b, 0xa2, 0x96, 0x77, 0x04,
0x35, 0x20, 0x5a, 0x4e, 0x09, 0x07, 0x6f, 0x79, 0x40, 0x86, 0xd5, 0x66, 0xd8, 0x5f, 0x40, 0x8a,
0x53, 0x5d, 0xe0, 0x3c, 0xb7, 0xf6, 0x7c, 0xe3, 0xbf, 0x3e, 0xd5, 0x2d, 0x48, 0xdd, 0x56, 0x16,
0x74, 0x1d, 0xbd, 0x1e, 0x79, 0x84, 0xbf, 0xa9, 0x58, 0x4d, 0xe5, 0x46, 0x39, 0xc9, 0x61, 0xc9,
0x7b, 0xb2, 0xb9, 0x27, 0x5c, 0xf8, 0x66, 0xee, 0xe2, 0x9c, 0xee, 0xcb, 0x78, 0x8b, 0x6b, 0x22,
0xa6, 0x08, 0x6e, 0x60, 0xef, 0xa6, 0x61, 0x45, 0xec, 0x7a, 0x61, 0x24, 0xd6, 0x04, 0xde, 0x60,
0x7a, 0x18, 0x8a, 0x11, 0xf8, 0x4e, 0xae, 0xe3, 0x13, 0xa2, 0x2c, 0xcf, 0x71, 0x55, 0x93, 0x58,
0x3f, 0x6c, 0x06, 0xc4, 0x54, 0xdb, 0x10, 0xef, 0x22, 0x20, 0xfd, 0xd3, 0xb6, 0x05, 0xee, 0x25,
0xa2, 0x65, 0xd5, 0x36, 0x88, 0x55, 0xcd, 0x9e, 0xb3, 0xb6, 0x42, 0xb0, 0x3c, 0x49, 0x1a, 0x24,
0x48, 0x31, 0x27, 0x53, 0x7b, 0xd9, 0x9a, 0x1e, 0xc1, 0x94, 0x66, 0x4c, 0x0c, 0x8f, 0xcd, 0xbe,
0x79, 0xce, 0xb1, 0x37, 0x15, 0x4e, 0x53, 0x91, 0x49, 0x8c, 0x44, 0x4a, 0x84, 0x4e, 0x4a, 0xdb,
0x70, 0x08, 0x4f, 0x48, 0x27, 0x45, 0x5c, 0x32, 0xd0, 0xca, 0x87, 0xe6, 0x58, 0x91, 0x3f, 0x28,
0xba, 0x4f, 0x48, 0x8d, 0x38, 0xa9, 0x49, 0xa3, 0x07, 0xa0, 0xb6, 0x82, 0xc2, 0x48, 0x91, 0x18,
0x8f, 0x87, 0x9c, 0x41, 0x30, 0xb0, 0x4a, 0x48, 0xac, 0x30, 0x9b, 0x2d, 0x4e, 0xee, 0xc4, 0x8e,
0xcb, 0xb4, 0x57, 0x92, 0x5c, 0xa9, 0x82, 0x3d, 0x97, 0x8d, 0x8d, 0xa4, 0x05, 0xde, 0x13, 0xb5,
0x78, 0x2c, 0xa3, 0x79, 0xc7, 0x92, 0xb6, 0xe6, 0x20, 0x70, 0xc7, 0xda, 0x46, 0x6c, 0x2c, 0xc6,
0x6d, 0xc3, 0x7a, 0x24, 0x78, 0x1f, 0x2d, 0xef, 0xc1, 0x94, 0x69, 0x27, 0x73, 0x64, 0x86, 0x53,
0xf6, 0xa0, 0x66, 0x57, 0x9c, 0xed, 0x41, 0xd4, 0xba, 0x7b, 0xca, 0xe2, 0x71, 0xac, 0xa5, 0xa5,
0x65, 0x09, 0xb6, 0xab, 0x2b, 0x5a, 0xba, 0xbd, 0x4a, 0x06, 0x1c, 0x2c, 0x3a, 0xc6, 0x75, 0xbd,
0x5e, 0xa5, 0x14, 0xbd, 0x12, 0x60, 0xaf, 0x49, 0xf6, 0x69, 0x6e, 0xf7, 0x42, 0xa9, 0x3b, 0x4a,
0xf2, 0x74, 0xd3, 0x4b, 0xef, 0xb2, 0xdd, 0x0e, 0xf4, 0x17, 0xbb, 0x61, 0x75, 0xb0, 0x44, 0x50,
0x2c, 0xac, 0xd0, 0x9c, 0x63, 0xa6, 0xb4, 0x62, 0xe6, 0xec, 0x28, 0xe4, 0xe5, 0xb6, 0xca, 0x19,
0x4e, 0xdd, 0x8b, 0xfa, 0x17, 0x6e, 0x62, 0xc2, 0xbd, 0x6e, 0x0b, 0x70, 0x87, 0x63, 0x97, 0xd2,
0xba, 0xca, 0xf1, 0x31, 0xce, 0x69, 0x0d, 0x5a, 0x80, 0x84, 0x7e, 0xda, 0xe6, 0x2c, 0xb9, 0xfb,
0x7b, 0xcb, 0x1a, 0x82, 0xd2, 0x14, 0xa5, 0x39, 0xda, 0xd1, 0x7d, 0x0b, 0x71, 0x7f, 0x96, 0x80,
0x50, 0xc6, 0x51, 0x25, 0x52, 0x42, 0x67, 0x32, 0xa6, 0xdc, 0x27, 0xec, 0xed, 0x1c, 0x82, 0x72,
0xb2, 0x27, 0x65, 0xda, 0x0d, 0xae, 0x57, 0x90, 0xb2, 0x45, 0x90, 0xd1, 0xdb, 0xbc, 0x93, 0x8b,
0xd7, 0xcd, 0x31, 0x57, 0x1b, 0x9c, 0x73, 0x54, 0x13, 0x1d, 0x60, 0x50, 0xe1, 0xf8, 0xda, 0xa4,
0x27, 0x19, 0x4c, 0xb1, 0xf2, 0x05, 0xd8, 0x49, 0x42, 0x32, 0x99, 0xb3, 0x4c, 0x38, 0x9d, 0xa3,
0xfa, 0x00, 0x7b, 0x75, 0x9b, 0xe0, 0x05, 0xde, 0x6d, 0x18, 0x24, 0x1b, 0xda, 0x1c, 0xe3, 0x40,
0xb3, 0x9a, 0xe7, 0xf2, 0x12, 0x06, 0x1f, 0x38, 0xcb, 0x87, 0x58, 0xe9, 0x0d, 0x92, 0xb4, 0xbc,
0x06, 0xea, 0x8a, 0x51, 0x38, 0x97, 0xf9, 0x29, 0x06, 0x8d, 0x8b, 0x44, 0x92, 0x6a, 0x44, 0x4a,
0x76, 0xb8, 0xcd, 0x21, 0x51, 0xb7, 0x29, 0x65, 0x28, 0xc1, 0xe0, 0xcc, 0x35, 0x22, 0xc5, 0x96,
0xa4, 0x88, 0xee, 0x38, 0x2e, 0x08, 0xa2, 0xc5, 0x1e, 0xb1, 0xed, 0x67, 0x91, 0x24, 0xea, 0xfb,
0x3d, 0xba, 0xa7, 0x29, 0x61, 0xc6, 0x70, 0xd2, 0x5c, 0xd3, 0x9c, 0x56, 0xd0, 0x34, 0xcd, 0xc9,
0x49, 0x4c, 0x54, 0xd4, 0x96, 0xc7, 0x82, 0x76, 0x0f, 0xfa, 0x30, 0x83, 0xec, 0x71, 0x3a, 0xbb,
0x81, 0xc8, 0x5b, 0x44, 0x9f, 0xcd, 0x44, 0x26, 0x06, 0x03, 0xb8, 0x07, 0x30, 0xc6, 0x1c, 0xfc,
0xa8, 0xe1, 0x32, 0x96, 0x21, 0xc1, 0x92, 0x81, 0x74, 0x00, 0x19, 0x2a, 0xce, 0x1a, 0x18, 0xeb,
0x51, 0x7d, 0x47, 0x1e, 0x06, 0x72, 0x39, 0x32, 0x94, 0x35, 0x6c, 0x46, 0xf0, 0x0a, 0xec, 0xe1,
0x51, 0x0f, 0x2b, 0x5c, 0x02, 0xce, 0x19, 0x06, 0x47, 0x33, 0xa0, 0x25, 0x04, 0xd1, 0x23, 0x83,
0x24, 0xee, 0xe8, 0xa9, 0x60, 0x95, 0xdc, 0xad, 0x4b, 0x5c, 0x81, 0x8f, 0x71, 0x9a, 0x34, 0x25,
0xe4, 0x84, 0x18, 0x92, 0xc3, 0x81, 0x42, 0xe2, 0x3a, 0x2a, 0x2a, 0xd8, 0x16, 0x2d, 0x71, 0x6e,
0x26, 0xc1, 0x31, 0x95, 0x91, 0xd4, 0x7d, 0x24, 0x9c, 0x69, 0x58, 0xd9, 0x16, 0x10, 0x3a, 0x89,
0xab, 0xe2, 0x61, 0x0a, 0xed, 0x35, 0x72, 0x46, 0xcc, 0x71, 0x22, 0x8f, 0x9f, 0x1e, 0x2e, 0xd2,
0x18, 0x64, 0x1b, 0x11, 0xff, 0x36, 0x44, 0xa5, 0x84, 0xde, 0x2c, 0x3e, 0xdc, 0x31, 0xce, 0x50,
0xbd, 0xcf, 0xed, 0x76, 0x3b, 0x0b, 0xa7, 0x80, 0x7c, 0xbf, 0xc5, 0x57, 0xd1, 0x2d, 0x0a, 0x16,
0x3e, 0x0a, 0x97, 0x2b, 0xe4, 0x45, 0xd7, 0xe7, 0xf3, 0xfb, 0xc4, 0xe8, 0x3b, 0xbe, 0xf3, 0xca,
0x87, 0x1f, 0x8b, 0x62, 0x1e, 0xf5, 0x1c, 0x54, 0x2d, 0x4c, 0xd2, 0x29, 0x76, 0x9b, 0xb7, 0x5c,
0xef, 0x6b, 0xcb, 0x85, 0xcb, 0x49, 0x5d, 0xf7, 0x10, 0x71, 0x33, 0xe5, 0xb8, 0x36, 0x3b, 0xdf,
0x73, 0x7c, 0x94, 0x86, 0xd5, 0x80, 0xac, 0x25, 0xda, 0x45, 0x7a, 0x08, 0xa4, 0x76, 0xf0, 0x6c,
0x63, 0x15, 0xdc, 0xb4, 0xdc, 0xc2, 0xd6, 0xa4, 0xa2, 0x58, 0x0f, 0x52, 0x0e, 0x97, 0xb4, 0x5e,
0x68, 0x2d, 0x02, 0x1c, 0x2a, 0x12, 0x3c, 0x92, 0xcb, 0x00, 0xcf, 0x04, 0x34, 0xa8, 0xa9, 0xa4,
0x06, 0x71, 0x26, 0xb2, 0xc1, 0x9c, 0xcb, 0x6e, 0x50, 0xe3, 0x4d, 0x18, 0xb0, 0xce, 0x1b, 0x53,
0xf8, 0x74, 0x97, 0x03, 0x42, 0x6d, 0xd7, 0xca, 0xff, 0x1a, 0xf5, 0x35, 0x66, 0xbf, 0xc6, 0xec,
0xd7, 0x98, 0xfd, 0xdf, 0x8e, 0xd9, 0xaf, 0xb1, 0xfa, 0x35, 0x56, 0xbf, 0xc6, 0xea, 0xff, 0x43,
0xac, 0x7a, 0x42, 0x3c, 0x0c, 0x95, 0x1a, 0xef, 0xac, 0x7b, 0xf2, 0x9f, 0x0a, 0x92, 0x52, 0xec,
0x5c, 0x15, 0x50, 0x07, 0x2a, 0xf0, 0x6a, 0x09, 0x9e, 0x74, 0xdd, 0x59, 0xe4, 0xc3, 0xd5, 0x5a,
0xe2, 0x4e, 0x33, 0x93, 0xd6, 0xab, 0x9b, 0x27, 0x27, 0x49, 0xdc, 0xdc, 0xa4, 0xc0, 0x0f, 0x97,
0x4f, 0xce, 0x52, 0xc8, 0xd9, 0x69, 0xe1, 0xcd, 0xd3, 0x12, 0x2a, 0xe4, 0xec, 0xb4, 0x68, 0xb1,
0x7a, 0x7a, 0x9a, 0x44, 0x9e, 0x4e, 0x9e, 0xee, 0xa7, 0x74, 0x67, 0x1d, 0x96, 0x93, 0x57, 0xb8,
0xa2, 0xd2, 0xd0, 0x55, 0xa0, 0xac, 0x93, 0x3d, 0xb7, 0x70, 0x97, 0x1a, 0xe2, 0x06, 0x1c, 0x2a,
0x4d, 0xaf, 0x38, 0xd8, 0x74, 0x6e, 0x4e, 0x76, 0xa6, 0x44, 0x91, 0x00, 0x3e, 0x94, 0xd9, 0x82,
0x76, 0x35, 0x22, 0x0c, 0xbc, 0x08, 0x98, 0x8c, 0x49, 0x15, 0x0c, 0x88, 0x8f, 0x03, 0x71, 0xdf,
0xd2, 0x19, 0x93, 0x8b, 0xae, 0x8e, 0x21, 0x3e, 0x18, 0xc1, 0x7a, 0xce, 0xe7, 0x7c, 0x15, 0x61,
0xe3, 0x06, 0x7e, 0x67, 0x71, 0x08, 0x35, 0x87, 0x7c, 0x86, 0xc3, 0xc9, 0xdb, 0xe5, 0xe4, 0x60,
0xea, 0x34, 0x31, 0x38, 0x79, 0x99, 0x48, 0x97, 0x84, 0x98, 0x16, 0x02, 0x78, 0xd6, 0x7d, 0x26,
0xc0, 0xc0, 0xb8, 0x07, 0x69, 0xa6, 0x19, 0x6c, 0x61, 0xe8, 0x6b, 0x4a, 0xd0, 0x83, 0xbb, 0x0e,
0xb5, 0x4b, 0xde, 0xf4, 0x90, 0x95, 0x06, 0x18, 0x9a, 0xe0, 0xe3, 0x5b, 0x4d, 0x15, 0x81, 0xdf,
0x7a, 0xd2, 0xb0, 0xee, 0xae, 0xcd, 0xf3, 0xce, 0xf6, 0x28, 0x89, 0x12, 0x66, 0x75, 0xd7, 0x87,
0xdc, 0x32, 0xf0, 0x8d, 0x6f, 0x84, 0x87, 0x70, 0x64, 0x0f, 0x9d, 0x79, 0x82, 0x0a, 0xd7, 0x93,
0x4d, 0x30, 0x65, 0x35, 0xf5, 0x08, 0x7b, 0x63, 0xad, 0x36, 0x92, 0x24, 0x85, 0xdc, 0xa5, 0xe6,
0xa4, 0x94, 0x13, 0x95, 0x9c, 0x01, 0xd4, 0x16, 0x65, 0x8f, 0x7f, 0xe0, 0xb8, 0xea, 0xcc, 0x53,
0x2c, 0xfe, 0x3b, 0x79, 0xa2, 0xf5, 0x50, 0xbb, 0x09, 0x11, 0x35, 0x72, 0x27, 0x8b, 0x58, 0xd9,
0x8d, 0xa8, 0x63, 0x05, 0x3a, 0x79, 0xa2, 0x65, 0x4d, 0x77, 0x47, 0x38, 0x62, 0x30, 0x6f, 0x3a,
0x3d, 0xd2, 0xcd, 0x6e, 0xc9, 0x4e, 0xa2, 0x4e, 0x5e, 0x4a, 0x45, 0xcd, 0xeb, 0x1e, 0xff, 0x18,
0x97, 0xac, 0xb9, 0xfa, 0x90, 0x41, 0x2d, 0x4c, 0xca, 0x4f, 0xd7, 0xff, 0x1c, 0x0f, 0xd5, 0x31,
0xac, 0x69, 0x5d, 0x4e, 0x20, 0x87, 0xd4, 0xe2, 0xe8, 0x34, 0xed, 0x75, 0xe1, 0x36, 0xbd, 0x4e,
0x20, 0x23, 0x25, 0x57, 0x41, 0x75, 0x78, 0x73, 0x8f, 0xf9, 0xd5, 0xec, 0xc4, 0xeb, 0xeb, 0xcd,
0x59, 0xaf, 0xd8, 0xcc, 0xba, 0x0a, 0x1c, 0xd7, 0x79, 0x66, 0xea, 0xb5, 0x11, 0x5a, 0xe4, 0x3f,
0x37, 0xf4, 0xfd, 0x2f, 0x90, 0xdd, 0xb4, 0x1f, 0xc6, 0x1d, 0x48, 0x38, 0xb4, 0xae, 0xc2, 0xf0,
0xd6, 0x09, 0x17, 0x01, 0xfc, 0x46, 0x6f, 0xa7, 0xcb, 0xf7, 0xd3, 0xc4, 0xda, 0x0f, 0x19, 0xa8,
0x5a, 0xf6, 0x5c, 0xdd, 0x92, 0x49, 0xeb, 0x48, 0x88, 0x3c, 0x72, 0x45, 0x47, 0x46, 0x59, 0x48,
0x36, 0xe7, 0xe0, 0xc8, 0xce, 0xf7, 0xba, 0x07, 0xca, 0x71, 0x4a, 0xdb, 0x3a, 0xee, 0x1d, 0x4f,
0xe3, 0xa5, 0xa3, 0x8d, 0x29, 0x6e, 0xe1, 0x07, 0x72, 0x86, 0x21, 0x29, 0xd2, 0x29, 0x8b, 0xc5,
0x7a, 0xc4, 0x64, 0x8a, 0x0e, 0x15, 0x56, 0x41, 0x35, 0xd2, 0x34, 0xe4, 0x01, 0xb1, 0x77, 0xa5,
0xc8, 0xfd, 0xdd, 0x6a, 0x6f, 0x2b, 0x65, 0xda, 0x75, 0x94, 0x8a, 0x89, 0x22, 0xa7, 0xff, 0x1d,
0x14, 0x33, 0x4c, 0x13, 0x4a, 0x81, 0x91, 0x34, 0x06, 0x04, 0xca, 0x8b, 0xb9, 0x2e, 0x17, 0x4e,
0xb8, 0x5c, 0xc2, 0xef, 0xea, 0x19, 0xae, 0x70, 0xac, 0xbb, 0xd1, 0x17, 0x30, 0x5d, 0xdc, 0x3a,
0xab, 0x1b, 0xf8, 0x77, 0x49, 0xd0, 0xf0, 0x0b, 0x78, 0xce, 0xf9, 0xc5, 0x19, 0x57, 0x38, 0xd4,
0xc9, 0x97, 0x48, 0x1a, 0xdd, 0x3a, 0x70, 0x43, 0x7a, 0x6e, 0xf7, 0x95, 0x1b, 0x9a, 0x06, 0x60,
0xef, 0x35, 0x95, 0x1b, 0x0c, 0xa0, 0x50, 0xc3, 0x96, 0x06, 0xa6, 0x32, 0x69, 0x75, 0x74, 0x6f,
0x34, 0x48, 0x27, 0xf4, 0x50, 0x64, 0x69, 0x0d, 0x93, 0xd9, 0x58, 0x91, 0x8a, 0x73, 0x42, 0x83,
0xed, 0x83, 0x42, 0xc3, 0xc6, 0x27, 0x05, 0x90, 0x47, 0x53, 0xf2, 0x70, 0x96, 0x5e, 0x0b, 0x07,
0x9a, 0x5e, 0x4e, 0x65, 0x59, 0x4d, 0x65, 0x59, 0x69, 0xe6, 0xfe, 0x98, 0xb9, 0x3f, 0x61, 0xeb,
0x4b, 0x86, 0xfe, 0x94, 0x9f, 0x3f, 0x62, 0xa6, 0x88, 0x16, 0x53, 0x22, 0x6f, 0x1d, 0x4d, 0x97,
0xf5, 0xd6, 0x66, 0x57, 0x8b, 0xf1, 0xc2, 0x13, 0x62, 0xb5, 0xba, 0x21, 0x3f, 0x0e, 0x56, 0x30,
0xdc, 0xc3, 0x73, 0xee, 0xa1, 0xe1, 0x1e, 0x4c, 0xb8, 0xcf, 0xa9, 0xcc, 0xd6, 0xd8, 0x19, 0xf7,
0x73, 0xe6, 0x86, 0xf7, 0x72, 0x62, 0x8f, 0x19, 0x63, 0x48, 0x52, 0xf9, 0xb6, 0x42, 0x90, 0xa8,
0xf7, 0x16, 0xaa, 0xf3, 0x29, 0xc6, 0x3d, 0xaa, 0x3f, 0x4c, 0x2c, 0xa4, 0x3e, 0x4b, 0x24, 0x48,
0xb2, 0xb2, 0xb1, 0x12, 0xd0, 0x23, 0xc3, 0x83, 0xfd, 0xae, 0xa9, 0xf7, 0x20, 0xfb, 0x9d, 0x4a,
0x68, 0x89, 0xb0, 0x1f, 0x91, 0x06, 0xe1, 0x19, 0x71, 0xa0, 0x15, 0x2d, 0xe9, 0x0f, 0xb5, 0x45,
0xdf, 0xdb, 0x65, 0x44, 0x3d, 0x90, 0x2e, 0x26, 0x62, 0xdc, 0x9c, 0x53, 0x87, 0x5a, 0xcd, 0xea,
0xfd, 0x6e, 0x61, 0xf3, 0x9e, 0x21, 0x37, 0x7e, 0x2c, 0xc9, 0xc4, 0xfb, 0xbd, 0xd1, 0x0b, 0xbf,
0xb5, 0xef, 0xf7, 0xa8, 0x92, 0xf1, 0x02, 0xe7, 0x23, 0xe4, 0xd2, 0x20, 0xc5, 0x95, 0xb1, 0x2d,
0x46, 0xc8, 0xc8, 0x20, 0x73, 0xa9, 0x58, 0x1b, 0xb7, 0x10, 0xb8, 0xb6, 0xaa, 0x08, 0x4f, 0x70,
0x4d, 0xa6, 0xaf, 0x99, 0x0c, 0xe2, 0xe4, 0xe5, 0x04, 0x2b, 0x33, 0xc3, 0x4d, 0xf2, 0x00, 0xc7,
0xc0, 0x58, 0xf2, 0x55, 0x18, 0x0d, 0x24, 0x51, 0x37, 0xbb, 0x2d, 0xc9, 0x5b, 0xe6, 0xc3, 0x95,
0xce, 0x5c, 0x12, 0x34, 0xe4, 0xae, 0x21, 0x61, 0xad, 0x23, 0xe7, 0x26, 0x72, 0x6e, 0x6f, 0x87,
0x74, 0x65, 0x93, 0x8a, 0x84, 0x35, 0x70, 0xbb, 0xb9, 0xc8, 0x0d, 0xf2, 0xe9, 0x32, 0x70, 0xec,
0x03, 0x65, 0x96, 0x9b, 0x75, 0x4e, 0x3d, 0xc9, 0x6a, 0xf6, 0x78, 0x7a, 0x5a, 0xb6, 0xdb, 0x8b,
0xb2, 0x05, 0x6b, 0x48, 0xcb, 0xce, 0xe2, 0x45, 0x3b, 0x8d, 0x2e, 0x73, 0xf3, 0xd7, 0x4e, 0x10,
0x2c, 0x1d, 0xa8, 0x20, 0x9e, 0x66, 0x68, 0xbd, 0x2f, 0x31, 0x7c, 0x67, 0xde, 0xc5, 0xa8, 0xa6,
0x82, 0x45, 0xfb, 0xe2, 0x05, 0x67, 0x26, 0x3d, 0xbd, 0xee, 0x6f, 0xba, 0xe4, 0x8e, 0xe6, 0x22,
0xd1, 0xa8, 0x3f, 0xf1, 0x70, 0xf6, 0x41, 0x51, 0x7c, 0x3d, 0x5c, 0xfb, 0x86, 0x72, 0xd8, 0x02,
0xea, 0x42, 0xd8, 0x02, 0x99, 0x12, 0xd8, 0x82, 0x0d, 0xc5, 0xaf, 0x05, 0x54, 0x65, 0xaf, 0x05,
0xd0, 0x85, 0xad, 0x0d, 0x12, 0x25, 0xad, 0x35, 0xb6, 0x8a, 0x78, 0x61, 0x74, 0x11, 0x83, 0xb2,
0x76, 0x53, 0xc7, 0x78, 0xdd, 0x59, 0x90, 0xb4, 0xed, 0xbf, 0x10, 0xf0, 0x82, 0xa8, 0xde, 0x58,
0x88, 0x0a, 0x78, 0xc0, 0xc2, 0xc7, 0x58, 0xce, 0x41, 0xd3, 0xbb, 0x00, 0xb2, 0xaf, 0xa1, 0x08,
0x94, 0x92, 0xcb, 0x2f, 0x0b, 0xee, 0x08, 0x7a, 0xe2, 0x63, 0x08, 0x45, 0xf9, 0x6f, 0xaf, 0x30,
0xcb, 0xcd, 0x5e, 0xf7, 0xb7, 0xe3, 0xfc, 0xf2, 0x1d, 0x35, 0xb4, 0x10, 0x49, 0x6b, 0xd7, 0x96,
0x7d, 0x41, 0xd4, 0x6e, 0x69, 0xe2, 0x6e, 0xc9, 0x23, 0x25, 0xfc, 0xca, 0x5b, 0x22, 0x1f, 0x79,
0x21, 0x0a, 0x44, 0x1d, 0xd0, 0x6b, 0x5d, 0x5e, 0xe7, 0x66, 0xad, 0x11, 0xd6, 0x50, 0x1b, 0x32,
0x30, 0xfd, 0xc7, 0xd8, 0xba, 0xa3, 0xc6, 0x12, 0xf4, 0x5f, 0xbc, 0xa9, 0x0e, 0x4b, 0xc8, 0x7b,
0xe0, 0xfa, 0x8b, 0x97, 0xb8, 0x75, 0xd6, 0x37, 0x4e, 0x18, 0x5c, 0x5a, 0x60, 0x94, 0x62, 0xed,
0x25, 0xfe, 0x83, 0x44, 0x2b, 0x3f, 0x18, 0xf8, 0x18, 0x3f, 0x82, 0x62, 0x62, 0xf9, 0xdc, 0x3d,
0xca, 0x8f, 0x30, 0x0e, 0x50, 0xf6, 0x1a, 0xac, 0xae, 0xf3, 0xc4, 0xee, 0xe0, 0x80, 0xea, 0x09,
0xed, 0x26, 0xec, 0x33, 0x95, 0x55, 0xb0, 0x84, 0xf4, 0x70, 0x0b, 0x59, 0x35, 0x5a, 0x58, 0xfb,
0x1b, 0x4d, 0xb3, 0x04, 0xd1, 0x2f, 0xfe, 0xc5, 0xcb, 0xed, 0x7e, 0x1d, 0xfd, 0x25, 0x43, 0x58,
0x1d, 0xfa, 0x2f, 0xbd, 0xec, 0xaf, 0x21, 0x26, 0x5f, 0x0a, 0x88, 0x0f, 0x05, 0x74, 0x9b, 0xa6,
0xe2, 0x64, 0x07, 0x55, 0xa3, 0x92, 0xc5, 0xad, 0x93, 0x8c, 0x14, 0x24, 0x4e, 0x31, 0xbf, 0xbb,
0xee, 0x3c, 0xf1, 0x67, 0xf0, 0x8a, 0x9b, 0x97, 0x5f, 0xdd, 0xcf, 0x55, 0x3a, 0xb6, 0xd5, 0x98,
0xf1, 0xea, 0xe5, 0x8c, 0xcf, 0x8f, 0xd8, 0x39, 0xc6, 0x83, 0x0f, 0x2c, 0x2e, 0x1e, 0x3e, 0xa1,
0x0f, 0xb5, 0x4b, 0x00, 0xee, 0x1b, 0x3c, 0x73, 0x9a, 0x4d, 0xd9, 0x06, 0x97, 0xd9, 0xce, 0x45,
0xc4, 0x25, 0xb6, 0xcb, 0xcb, 0x47, 0x65, 0xb4, 0x72, 0x82, 0xd5, 0xc2, 0x09, 0xd6, 0xd1, 0x25,
0xb6, 0x93, 0x98, 0x5e, 0x7d, 0x69, 0xc0, 0xbd, 0x4c, 0xd5, 0x76, 0xd4, 0xc9, 0xab, 0xc8, 0x8b,
0x42, 0xee, 0x25, 0x17, 0x92, 0x67, 0x1a, 0xa4, 0x75, 0xf1, 0x31, 0x96, 0x7d, 0xa9, 0x65, 0xdf,
0x97, 0x0a, 0xa3, 0xd7, 0x27, 0x09, 0x95, 0x7d, 0x1b, 0xd3, 0x64, 0x1a, 0x1a, 0x46, 0x00, 0x52,
0x04, 0xba, 0xd1, 0x83, 0xe5, 0x76, 0xcf, 0xfa, 0x3e, 0xaa, 0x6b, 0xa1, 0x90, 0x6a, 0x82, 0xa8,
0x65, 0xce, 0xea, 0xa9, 0xf0, 0xbc, 0xde, 0xd0, 0x6d, 0x36, 0x39, 0xe7, 0x60, 0xcf, 0x91, 0x55,
0x47, 0x38, 0x57, 0x34, 0xda, 0x73, 0xe4, 0xee, 0x97, 0xa3, 0xfb, 0x79, 0x7f, 0x15, 0x9f, 0x5c,
0xcf, 0x27, 0x53, 0xe6, 0x6e, 0xe8, 0x0a, 0x6d, 0x6e, 0xb6, 0x68, 0x44, 0x3d, 0x77, 0xcd, 0x7d,
0xa6, 0xaf, 0x5c, 0xa4, 0x4a, 0x07, 0x67, 0x25, 0xd7, 0x62, 0xaa, 0x81, 0xc5, 0x13, 0x9c, 0x4c,
0xb3, 0x39, 0xdf, 0x2b, 0xd5, 0x84, 0x63, 0xd5, 0x2c, 0xce, 0x15, 0xa3, 0x58, 0x7d, 0x23, 0x3b,
0x82, 0x5e, 0x7d, 0x4f, 0xe0, 0x22, 0xe4, 0xa6, 0x35, 0xde, 0xad, 0x9b, 0xf1, 0xc8, 0xee, 0xa7,
0x7b, 0xb4, 0xfe, 0x3e, 0xa7, 0xc9, 0xdd, 0xa5, 0x39, 0x08, 0x08, 0xdf, 0x33, 0x7e, 0x99, 0xf7,
0xe4, 0x43, 0x9b, 0x86, 0x8f, 0xf1, 0x4e, 0x93, 0x39, 0xea, 0x3b, 0xb7, 0xa7, 0x65, 0xfa, 0xa6,
0xc0, 0xb4, 0xd4, 0xe8, 0x6c, 0xef, 0xb3, 0xdd, 0x7a, 0xdc, 0x89, 0xc8, 0x82, 0x09, 0xf6, 0xbc,
0xba, 0xfc, 0x66, 0xa0, 0x09, 0xf0, 0x22, 0x49, 0xf8, 0xb1, 0x33, 0xef, 0x96, 0x7c, 0x7f, 0xe9,
0x5b, 0x5f, 0xef, 0x2d, 0x41, 0x91, 0x76, 0x9d, 0x04, 0x32, 0x6c, 0x9e, 0x2c, 0x8e, 0xfe, 0x05,
0xb4, 0x46, 0x69, 0xaa, 0x6b, 0x2c, 0x00, 0x00
}; };
void serveBundleCss(AsyncWebServerRequest* request) { void serveBundleCss(AsyncWebServerRequest* request) {

View File

@ -1,9 +1,6 @@
/* /*
* Binary array for the Web UI. * Binary array for the Web UI.
* gzip is used for smaller size and improved speeds. * Gzip is used for smaller size and improved speeds.
*
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
* to find out how to easily modify the web UI source!
*/ */
// Autogenerated do not edit!! // Autogenerated do not edit!!

File diff suppressed because it is too large Load Diff

4199
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,17 +14,26 @@
"@rollup/plugin-image": "^2.1.1", "@rollup/plugin-image": "^2.1.1",
"@rollup/plugin-node-resolve": "^13.3.0", "@rollup/plugin-node-resolve": "^13.3.0",
"@sveltejs/vite-plugin-svelte": "^1.0.1", "@sveltejs/vite-plugin-svelte": "^1.0.1",
"autoprefixer": "^10.4.8",
"concurrently": "^7.3.0", "concurrently": "^7.3.0",
"cssnano": "^5.1.12",
"mime": "^3.0.0", "mime": "^3.0.0",
"pascal-case": "^3.1.2", "pascal-case": "^3.1.2",
"postcss": "^8.4.14",
"postcss-load-config": "^4.0.1",
"rollup": "^2.77.2", "rollup": "^2.77.2",
"rollup-plugin-css-only": "^3.1.0", "rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-generate-html-template": "^1.7.0", "rollup-plugin-generate-html-template": "^1.7.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-svelte": "^7.1.0", "rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.49.0", "svelte": "^3.49.0",
"svelte-preprocess": "^4.10.7",
"tailwindcss": "^3.1.7",
"vite": "^3.0.0" "vite": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"svelte-spa-router": "^3.2.0",
"svelte-table": "^0.5.0" "svelte-table": "^0.5.0"
} }
} }

9
ui/postcss.config.cjs Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
cssnano: {
preset: 'default',
}
}
}

View File

@ -1,7 +1,8 @@
import svelte from 'rollup-plugin-svelte'; import svelte from 'rollup-plugin-svelte';
import css from 'rollup-plugin-css-only';
import image from '@rollup/plugin-image'; import image from '@rollup/plugin-image';
import resolve from '@rollup/plugin-node-resolve'; import resolve from '@rollup/plugin-node-resolve';
import { terser } from "rollup-plugin-terser";
import postcss from 'rollup-plugin-postcss'
import htmlTemplate from 'rollup-plugin-generate-html-template'; import htmlTemplate from 'rollup-plugin-generate-html-template';
import { readFile, writeFile } from 'fs'; import { readFile, writeFile } from 'fs';
import { basename } from 'path'; import { basename } from 'path';
@ -41,10 +42,7 @@ function cppGzipped(html, fileName, contentType) {
const array = hexdump(result); const array = hexdump(result);
const src = `/* const src = `/*
* Binary array for the Web UI. * Binary array for the Web UI.
* gzip is used for smaller size and improved speeds. * Gzip is used for smaller size and improved speeds.
*
* Please see https://kno.wled.ge/advanced/custom-features/#changing-web-ui
* to find out how to easily modify the web UI source!
*/ */
// Autogenerated do not edit!! // Autogenerated do not edit!!
@ -59,7 +57,6 @@ function cppGzipped(html, fileName, contentType) {
request->send(response); request->send(response);
} }
`; `;
resolve(src); resolve(src);
})); }));
} }
@ -176,15 +173,15 @@ export default {
svelte({}), svelte({}),
resolve(), resolve(),
image(), image(),
css({ output: 'bundle.css' }), postcss({ extract: 'bundle.css' }),
htmlTemplate({ htmlTemplate({
template: 'src/template.html', template: 'src/template.html',
target: 'index.html', target: 'index.html',
}), }),
terser(),
cpp({ cpp({
additionalFiles: ['dist/index.html'], additionalFiles: ['dist/index.html'],
fileName: function(a) { return "../src/ui_" + basename(a).replace(".", "_") + ".h"; } fileName: function(a) { return "../src/ui_" + basename(a).replace(".", "_") + ".h"; }
}) })
] ]
}; };

View File

@ -1,125 +1,16 @@
<script> <script>
import { devices } from './stores'; import Router from "svelte-spa-router";
import SvelteTable from "svelte-table"; import Sidebar from "./components/Sidebar.svelte";
import routes from "./routes";
var filterSelections = { };
var sortBy = "distance"
var sortOrder = 1;
const columns = [
{
key: "distance",
title: "Distance",
value: v => v.distance,
renderValue: v => `${v.distance.toLocaleString(undefined, { minimumFractionDigits: 2 })}m`,
sortable: true,
},
{
key: "id",
title: "ID",
value: v => v.id,
sortable: true,
filterOptions: rows => {
const prefixes = new Set()
rows.forEach(row => {
var prefix = row.id.substring(0, row.id.indexOf(":")+1);
if (prefix.length > 0) {
prefixes.add(prefix);
}
});
return Array.from(prefixes).sort().map(a=>({"name": a, "value": a}));
},
filterValue: v => v.id.substring(0, v.id.indexOf(":")+1),
headerClass: "text-left",
},
{
key: "name",
title: "Name",
value: v => v.name ?? "",
sortable: true,
filterOptions: rows => {
let letrs = {};
rows.forEach(row => {
let letr = row.name?.charAt(0);
if (letr && letrs[letr] === undefined)
letrs[letr] = {
name: `${letr.toUpperCase()}`,
value: letr.toLowerCase(),
};
});
// fix order
letrs = Object.entries(letrs)
.sort()
.reduce((o, [k, v]) => ((o[k] = v), o), {});
return Object.values(letrs);
},
filterValue: v => v.name?.charAt(0).toLowerCase(),
},
{
key: "mac",
title: "MAC",
value: v => v.mac,
sortable: true,
filterOptions: rows => {
// use first letter of last_name to generate filter
let letrs = {};
rows.forEach(row => {
let letr = row.mac.charAt(0);
if (letrs[letr] === undefined)
letrs[letr] = {
name: `${letr.toUpperCase()}`,
value: letr.toLowerCase(),
};
});
// fix order
letrs = Object.entries(letrs)
.sort()
.reduce((o, [k, v]) => ((o[k] = v), o), {});
return Object.values(letrs);
},
filterValue: v => v.mac.charAt(0).toLowerCase(),
},
{
key: "rssi",
title: "Rssi",
value: v => v.rssi,
renderValue: v => v.rssi + "dBm",
sortable: true,
},
{
key: "rssi@1m",
title: "Rssi@1m",
value: v => v["rssi@1m"],
renderValue: v => v["rssi@1m"] + "dBm",
sortable: true,
},
{
key: "interval",
title: "Interval",
value: v => v.interval,
renderValue: v => v.interval + "ms",
sortable: true,
},
];
</script> </script>
<main> <div>
<div class="flex">
{#if $devices?.devices != null } <nav>
<SvelteTable columns="{columns}" rows="{$devices.devices}" <Sidebar />
bind:filterSelections="{filterSelections}" </nav>
bind:sortBy = "{sortBy}" <main class="max-w-7xl mx-auto py-8 sm:py-10 md:py-12 sm:px-10 lg:px-12">
bind:sortOrder = "{sortOrder}" <Router {routes} />
></SvelteTable> </main>
{:else} </div>
<h1>Error while loading devices</h1> </div>
{/if}
</main>
<style>
main {
padding: 1rem;
}
h1 {
text-align: center;
}
</style>

View File

@ -1,15 +1,7 @@
:root { /* Only apply purgecss on utilities, per the Tailwind documentation. */
color-scheme: light dark; /* purgecss start ignore */
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; @tailwind base;
font-size: 16px; @tailwind components;
line-height: 24px; /* purgecss end ignore */
color: rgba(255, 255, 255, 0.87); @tailwind utilities;
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}

View File

@ -0,0 +1,31 @@
<script>
export let name;
export let width = "1rem";
export let height = "1rem";
export let focusable = false;
let icons = [
{
w: 2048,
h: 1792,
name: "contact",
svg: `<path d="M1024 1131q0-64-9-117.5t-29.5-103-60.5-78-97-28.5q-6 4-30 18t-37.5 21.5-35.5 17.5-43 14.5-42 4.5-42-4.5-43-14.5-35.5-17.5-37.5-21.5-30-18q-57 0-97 28.5t-60.5 78-29.5 103-9 117.5 37 106.5 91 42.5h512q54 0 91-42.5t37-106.5zm-157-520q0-94-66.5-160.5t-160.5-66.5-160.5 66.5-66.5 160.5 66.5 160.5 160.5 66.5 160.5-66.5 66.5-160.5zm925 509v-64q0-14-9-23t-23-9h-576q-14 0-23 9t-9 23v64q0 14 9 23t23 9h576q14 0 23-9t9-23zm0-260v-56q0-15-10.5-25.5t-25.5-10.5h-568q-15 0-25.5 10.5t-10.5 25.5v56q0 15 10.5 25.5t25.5 10.5h568q15 0 25.5-10.5t10.5-25.5zm0-252v-64q0-14-9-23t-23-9h-576q-14 0-23 9t-9 23v64q0 14 9 23t23 9h576q14 0 23-9t9-23zm256-320v1216q0 66-47 113t-113 47h-352v-96q0-14-9-23t-23-9h-64q-14 0-23 9t-9 23v96h-768v-96q0-14-9-23t-23-9h-64q-14 0-23 9t-9 23v96h-352q-66 0-113-47t-47-113v-1216q0-66 47-113t113-47h1728q66 0 113 47t47 113z"/>`,
},
{
w: 32,
h: 32,
name: "trash",
svg: `<path d="M12 12h2v12h-2z" /><path d="M18 12h2v12h-2z" /><path d="M4 6v2h2v20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8h2V6zm4 22V8h16v20z" /><path d="M12 2h8v2h-8z" />`,
},
{
name: "device",
w: 2048,
h: 1792,
svg: `<path d="M685 483q16 0 27.5-11.5t11.5-27.5-11.5-27.5-27.5-11.5-27 11.5-11 27.5 11 27.5 27 11.5zm422 0q16 0 27-11.5t11-27.5-11-27.5-27-11.5-27.5 11.5-11.5 27.5 11.5 27.5 27.5 11.5zm-812 184q42 0 72 30t30 72v430q0 43-29.5 73t-72.5 30-73-30-30-73v-430q0-42 30-72t73-30zm1060 19v666q0 46-32 78t-77 32h-75v227q0 43-30 73t-73 30-73-30-30-73v-227h-138v227q0 43-30 73t-73 30q-42 0-72-30t-30-73l-1-227h-74q-46 0-78-32t-32-78v-666h918zm-232-405q107 55 171 153.5t64 215.5h-925q0-117 64-215.5t172-153.5l-71-131q-7-13 5-20 13-6 20 6l72 132q95-42 201-42t201 42l72-132q7-12 20-6 12 7 5 20zm477 488v430q0 43-30 73t-73 30q-42 0-72-30t-30-73v-430q0-43 30-72.5t72-29.5q43 0 73 29.5t30 72.5z"/>`,
},
];
let displayIcon = icons.find((e) => e.name === name);
</script>
{#if displayIcon != null}
<svg fill="currentColor" class={$$props.class || "m-auto"} {focusable} {width} {height} viewBox="0 0 {displayIcon.w} {displayIcon.h}">{@html displayIcon.svg}</svg>
{/if}

View File

@ -0,0 +1,26 @@
<script>
import SidebarItem from "./SidebarItem.svelte";
</script>
<div class="relative bg-white dark:bg-gray-800">
<div class="flex flex-col sm:flex-row sm:justify-around">
<div class="w-72 h-screen">
<div class="flex items-center justify-start mx-6 mt-10">
<svg class="h-10" preserveAspectRatio="xMidYMid" version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 204 204">
<circle cx="102" cy="102" r="102" fill="#fff" />
<path d="M3.42 102.13c-.04 57.18 47.35 98.12 99.2 98.09 57.46-.32 98.2-49.29 97.75-98.23-.03-28.4-10.65-47.14-17.55-56.35-7.38-9.71-6.07-12.38-17.74-.72l-9.3 9.3c24.2 32.31 20.42 61.25 7.6 83.7-25.5 35.3-49.6 32.8-49.6 32.8-8.63 10.92-14.66 10.4-24.32 2.04-30.59-1.88-58-34.54-59.12-69.25-.13-11.12 2.82-20.08 8.15-32.36-7.42-13.89-.35-25.14 15.7-23.15 0 0 19.51-17.51 44.8-18.32 28.29-1.26 50.29 18.14 50.29 18.14l9.3-9.4c10.8-10.9 10.68-8.91-1.72-18.62-.1.7-18.27-16.62-61.54-16.34-43.27.28-91.87 41.5-91.9 98.67z" />
<path d="M96.26 38.92c-12.62.72-25.74 8.82-35.04 14.46 2.48 12.33-5 21.34-14.58 21.1-9.55 18-6.64 31.97-6.64 31.97-.3 31.5 39.77 64.78 52.38 53.17 6.9-6.3 13.06-5.17 19.96 1.23 10.53 4.94 26.33-8.08 33.54-15.23 22-21.9 24.8-55.7 6.7-80.6l-3.2-4.4-6.3 6.3-6.3 6.2c14.65 19.66 7.32 43.8 7.32 43.8 2.88 14.8-3.88 24.35-18.38 21.78-11.95 8.42-34.24 9.22-48.54-.28-6.6-4.3-10.4-8.6-14.4-16.4-15.18-33.19 6.6-64.4 38.8-64.6 13.82.47 18.53 2.97 28.8 9.2l12.5-12.5-4.4-3.2c-13.4-9.7-27.61-12.84-42.22-12z" />
<path d="M91.08 67.62c-7.8 2.8-14.3 7.9-18.5 14.4-11.6 17.9-5.6 40.8 13.2 51 6 3.2 6.8 3.4 16.3 3.4 8-.1 10.9-.5 14.7-2.2 4.5-2.1 4.6-2.3 3.9-5.5-1.6-7.5 3.4-13.8 11.1-13.8 3.4 0 3.8-.3 4.7-3.8 2.5-8.8.6-21.2-4.4-28.9l-1.7-2.6-9.8 9.8c-7.9 7.9-9.7 10.2-9.8 12.7-.2 6.8-8.4 10.3-14 6.1-6.21-5.49-1.4-15.11 5.1-15.3 3.1 0 4.5-1.1 12.6-9.6 6.9-7.2 8.9-10 8.1-10.9-.6-.7-3.4-2.3-6.3-3.5-7.1-3.2-18.4-3.7-25.2-1.3z" />
<circle r="6" cy="126.75" cx="132.5" />
<circle cx="101.5" cy="167" r="6" />
<circle cx="48.75" cy="60.75" r="6" />
</svg>
<span class="text-gray-600 dark:text-gray-300 ml-4 text-2xl font-bold"> ESPresense </span>
</div>
<nav class="mt-10 px-6 ">
<SidebarItem icon="device" title="Devices" href="/" />
<SidebarItem icon="contact" title="Fingerprints" href="/fingerprints" />
</nav>
</div>
</div>
</div>

View File

@ -0,0 +1,29 @@
<script>
import Icon from "./Icon.svelte";
import active from "svelte-spa-router/active";
import { link } from "svelte-spa-router";
export let title = "Title";
export let icon = "";
export let href = "/";
export let count = 0;
</script>
<a use:link use:active={{ className: "text-gray-800 dark:text-gray-100 rounded-lg bg-gray-100 dark:bg-gray-600", inactiveClassName: "text-gray-600 dark:text-gray-400 rounded-lg" }} class="hover:text-gray-800 hover:bg-gray-100 flex items-center p-2 my-6 transition-colors dark:hover:text-white dark:hover:bg-gray-600 duration-200 text-gray-600 dark:text-gray-400 rounded-lg" {href}>
<Icon name={icon} />
<span class="mx-4 text-lg font-normal">
{title}
</span>
{#if count > 0}
<span class="flex-grow text-right">
<button type="button" class="w-6 h-6 text-xs rounded-full text-white bg-red-500">
<span class="p-1">
{count}
</span>
</button>
</span>
{:else}
<span class="flex-grow text-right" />
{/if}
</a>

View File

@ -0,0 +1,22 @@
<section class="text-gray-600 body-font">
<div class="container px-5 py-24 mx-auto">
<div class="flex flex-wrap -m-4 text-center">
<div class="p-4 sm:w-1/4 w-1/2">
<h2 class="title-font font-medium sm:text-4xl text-3xl text-gray-900">2.7K</h2>
<p class="leading-relaxed">Users</p>
</div>
<div class="p-4 sm:w-1/4 w-1/2">
<h2 class="title-font font-medium sm:text-4xl text-3xl text-gray-900">1.8K</h2>
<p class="leading-relaxed">Subscribes</p>
</div>
<div class="p-4 sm:w-1/4 w-1/2">
<h2 class="title-font font-medium sm:text-4xl text-3xl text-gray-900">35</h2>
<p class="leading-relaxed">Downloads</p>
</div>
<div class="p-4 sm:w-1/4 w-1/2">
<h2 class="title-font font-medium sm:text-4xl text-3xl text-gray-900">4</h2>
<p class="leading-relaxed">Products</p>
</div>
</div>
</div>
</section>

10
ui/src/routes.js Normal file
View File

@ -0,0 +1,10 @@
import Home from './routes/Home.svelte';
import Devices from './routes/Devices.svelte';
import NotFound from './routes/NotFound.svelte';
export default {
'/': Home,
'/fingerprints': Devices,
// The catch-all route must always be last
'*': NotFound
};

View File

@ -0,0 +1,137 @@
<script>
import { devices, events } from '../stores';
import SvelteTable from "svelte-table";
var filterSelections = { };
var sortBy = "distance"
var sortOrder = 1;
const columns = [
{
key: "distance",
title: "Dist",
value: v => v.distance,
renderValue: v => `${v.distance.toLocaleString(undefined, { minimumFractionDigits: 2 })}m`,
sortable: true,
class:"px-0 py-0 whitespace-nowrap"
},
{
key: "id",
title: "ID",
value: v => v.id,
sortable: true,
filterOptions: rows => {
const prefixes = new Set()
rows.forEach(row => {
var prefix = row.id.substring(0, row.id.indexOf(":")+1);
if (prefix.length > 0) {
prefixes.add(prefix);
}
});
return Array.from(prefixes).sort().map(a=>({"name": a, "value": a}));
},
filterValue: v => v.id.substring(0, v.id.indexOf(":")+1),
headerClass: "text-left px-6 py-3",
},
{
key: "name",
title: "Name",
value: v => v.name ?? "",
sortable: true,
filterOptions: rows => {
let letrs = {};
rows.forEach(row => {
let letr = row.name?.charAt(0);
if (letr && letrs[letr] === undefined)
letrs[letr] = {
name: `${letr.toUpperCase()}`,
value: letr.toLowerCase(),
};
});
// fix order
letrs = Object.entries(letrs)
.sort()
.reduce((o, [k, v]) => ((o[k] = v), o), {});
return Object.values(letrs);
},
filterValue: v => v.name?.charAt(0).toLowerCase(),
headerClass: "text-left px-6 py-3",
},
{
key: "mac",
title: "MAC",
value: v => v.mac,
sortable: true,
filterOptions: rows => {
// use first letter of last_name to generate filter
let letrs = {};
rows.forEach(row => {
let letr = row.mac.charAt(0);
if (letrs[letr] === undefined)
letrs[letr] = {
name: `${letr.toUpperCase()}`,
value: letr.toLowerCase(),
};
});
// fix order
letrs = Object.entries(letrs)
.sort()
.reduce((o, [k, v]) => ((o[k] = v), o), {});
return Object.values(letrs);
},
filterValue: v => v.mac.charAt(0).toLowerCase(),
headerClass: "text-left px-6 py-3",
},
{
key: "rssi",
title: "Rssi",
value: v => v.rssi,
renderValue: v => v.rssi + "dBm",
sortable: true,
headerClass: "text-left px-6 py-3",
},
{
key: "rssi@1m",
title: "Rssi@1m",
value: v => v["rssi@1m"],
renderValue: v => v["rssi@1m"] + "dBm",
sortable: true,
headerClass: "text-left px-6 py-3",
},
{
key: "interval",
title: "Interval",
value: v => v.interval,
renderValue: v => v.interval + "ms",
sortable: true,
headerClass: "text-left px-6 py-3",
},
];
</script>
<main>
{#if $devices?.devices != null }
<SvelteTable columns="{columns}" rows="{$devices.devices}"
bind:filterSelections="{filterSelections}"
bind:sortBy = "{sortBy}"
bind:sortOrder = "{sortOrder}"
classNameTable="min-w-full divide-y divide-gray-200 table-auto"
classNameThead="whitespace-nowrap text-left text-xs font-medium text-gray-500 uppercase"
classNameTbody="bg-white divide-y divide-gray-200"
classNameCell="px-3 py-1 whitespace-no-wrap text-sm leading-5 font-light text-gray-900"
classNameInput="px-1 py-1 border rounded-md text-sm leading-5 font-medium text-gray-900 placeholder-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm sm:leading-5"
classNameSelect="px-1 py-1 border rounded-md text-sm leading-5 font-medium text-gray-900 placeholder-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 focus:z-10 sm:text-sm sm:leading-5"
></SvelteTable>
{:else}
<h1>Error while loading devices</h1>
{/if}
</main>
<style>
main {
padding: 1rem;
}
h1 {
text-align: center;
}
</style>

10
ui/src/routes/Home.svelte Normal file
View File

@ -0,0 +1,10 @@
<script>
import Stats from "../components/Stats.svelte";
</script>
<Stats />
<label for="name">Name</label>
<input id="name" type="text" class=" bg-gray-200 relative" placeholder="Name" />
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Enroll
</button>

View File

@ -0,0 +1,11 @@
<h1>Not Found</h1>
<p>This route doesn't exist.</p>
<style>
h1 {
color: #ff0040;
text-transform: uppercase;
font-size: 4em;
font-weight: 100;
}
</style>

View File

@ -1,15 +1,21 @@
import { readable } from 'svelte/store'; import { readable } from 'svelte/store';
import { writable } from 'svelte/store';
export const devices = readable([], function start(set) { export const devices = readable([], function start(set) {
var errors = 0; var errors = 0;
var outstanding = false;
const interval = setInterval(() => { const interval = setInterval(() => {
if (outstanding) return;
outstanding = true;
fetch(`/json/devices`) fetch(`/json/devices`)
.then(d => d.json()) .then(d => d.json())
.then(r => { .then(r => {
outstanding = false;
errors = 0; errors = 0;
set(r); set(r);
}) })
.catch((ex) => { .catch((ex) => {
outstanding = false;
if (errors > 5) set(null); if (errors > 5) set(null);
console.log(ex); console.log(ex);
}); });
@ -19,3 +25,22 @@ export const devices = readable([], function start(set) {
clearInterval(interval); clearInterval(interval);
}; };
}); });
var initialValue = {};
export const events = readable(initialValue, function start(set) {
const socket = new WebSocket(`ws://${location.hostname}/ws`);
socket.addEventListener('open', function (event) {
console.log("It's open");
});
socket.addEventListener('message', function (event) {
initialValue = JSON.parse(event.data);
set(initialValue);
});
return function stop() {
socket.close();
};
});

12
ui/svelte.config.js Normal file
View File

@ -0,0 +1,12 @@
/** @type {import('@sveltejs/kit').Config} */
import preprocess from "svelte-preprocess";
const config = {
preprocess: [
preprocess({
postcss: true,
}),
],
}
export default config;

8
ui/tailwind.config.cjs Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: []
};