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 "BleFingerprintCollection.h"
#include "mbedtls/aes.h"
#include "rssi.h"
#include "string_utils.h"
#include "util.h"
#include "mbedtls/aes.h"
class ClientCallbacks : public BLEClientCallbacks
{
@ -15,21 +15,33 @@ class ClientCallbacks : public BLEClientCallbacks
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;
return (BleFingerprintCollection::exclude.length() > 0 && prefixExists(BleFingerprintCollection::exclude, s));
}
bool BleFingerprint::setId(const String& newId, short newIdType, const String& newName)
{
if (newIdType < idType && idType > 0) return false;
bool BleFingerprint::setId(const String &newId, short newIdType, const String &newName) {
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());
ignore = newIdType < 0;
idType = newIdType;
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);
ignore = newIdType < 0;
if ((idType != newIdType) || !id.equals(newId)) {
countable = !ignore && !hidden && !BleFingerprintCollection::countIds.isEmpty() && prefixExists(BleFingerprintCollection::countIds, newId);
bool newQuery = !ignore && !BleFingerprintCollection::query.isEmpty() && prefixExists(BleFingerprintCollection::query, newId);
if (newQuery != allowQuery)
@ -41,19 +53,16 @@ bool BleFingerprint::setId(const String& newId, short newIdType, const String& n
{
qryDelayMillis = 30000;
lastQryMillis = millis();
} else if (rssi < -70)
{
} else if (rssi < -70) {
qryDelayMillis = 5000;
lastQryMillis = millis();
}
}
}
id = newId;
idType = newIdType;
added = false;
}
if (!newName.isEmpty() && !name.equals(newName)) name = newName;
return true;
}
@ -123,9 +132,7 @@ struct encryption_block
uint8_t cipher_text[16];
};
int ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk)
{
int rc;
bool ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk) {
struct encryption_block ecb;
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[3] = irk32[3];
pt32[0] = 0;
pt32[1] = 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);
if ((ecb.cipher_text[15] == rpa[0]) && (ecb.cipher_text[14] == rpa[1]) &&
(ecb.cipher_text[13] == rpa[2])) {
if (ecb.cipher_text[15] != rpa[0] || ecb.cipher_text[14] != rpa[1] || ecb.cipher_text[13] != rpa[2]) return false;
rc = 1;
} else {
rc = 0;
}
// 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]);
//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 rc;
return true;
}
void BleFingerprint::fingerprintAddress()
@ -175,21 +175,18 @@ void BleFingerprint::fingerprintAddress()
case BLE_ADDR_PUBLIC_ID:
setId(mac, ID_TYPE_PUBLIC_MAC);
break;
case BLE_ADDR_RANDOM:
{
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);
});
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()) {
setId(std::get<1>(*it), ID_TYPE_KNOWN_IRK);
auto irk_hex = hexStr(*it, 16);
setId(String("irk:") + irk_hex.c_str(), ID_TYPE_KNOWN_IRK);
break;
}
}
default:
setId(mac, ID_TYPE_MAC);
setId(mac, ID_TYPE_RAND_MAC);
break;
}
}
@ -295,7 +292,7 @@ void BleFingerprint::fingerprintServiceData(NimBLEAdvertisedDevice *advertisedDe
{ // found COVID-19 exposure tracker
calRssi = BleFingerprintCollection::refRssi + EXPOSURE_TX;
setId("exp:" + String(strServiceData.length()), ID_TYPE_EXPOSURE);
disc = hexStr(strServiceData).c_str();
//disc = hexStr(strServiceData).c_str();
}
else if (uuid == smartTagUUID)
{ // found Samsung smart tag
@ -392,7 +389,7 @@ void BleFingerprint::fingerprintManufactureData(NimBLEAdvertisedDevice *advertis
BLEBeacon oBeacon = BLEBeacon();
oBeacon.setData(strManufacturerData);
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)
{
@ -440,11 +437,12 @@ void BleFingerprint::fingerprintManufactureData(NimBLEAdvertisedDevice *advertis
{
mdRssi = haveTxPower ? BleFingerprintCollection::refRssi + txPower : NO_RSSI;
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[11], strManufacturerData[12], strManufacturerData[13], strManufacturerData[14], strManufacturerData[15],
strManufacturerData[16], strManufacturerData[17], strManufacturerData[18], strManufacturerData[19], strManufacturerData[20],
strManufacturerData[21], strManufacturerData[22], strManufacturerData[23], strManufacturerData[24], strManufacturerData[25]);
*/
}
else if (manuf == "0075") // samsung
{
@ -549,7 +547,7 @@ void BleFingerprint::fill(JsonObject *doc)
bool BleFingerprint::report(JsonObject *doc)
{
if (ignore || idType == 0 || hidden)
if (ignore || idType <= ID_TYPE_RAND_MAC || hidden)
return false;
if (reported || !hasValue)
@ -666,3 +664,8 @@ bool BleFingerprint::shouldCount()
return counting;
}
void BleFingerprint::expire()
{
lastSeenMillis = 0;
}

View File

@ -16,18 +16,21 @@
#define ID_TYPE_TX_POW short(1)
#define NO_ID_TYPE short(0)
#define ID_TYPE_ECHO_LOST short(-10)
#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_SD short(15)
#define ID_TYPE_MD short(20)
#define ID_TYPE_MISC short(30)
#define ID_TYPE_FINDMY short(32)
#define ID_TYPE_NAME short(35)
#define ID_TYPE_PUBLIC_MAC short(50)
#define ID_TYPE_MSFT short(100)
#define ID_TYPE_MSFT short(40)
#define ID_TYPE_UNIQUE short(50)
#define ID_TYPE_PUBLIC_MAC short(55)
#define ID_TYPE_SONOS short(105)
#define ID_TYPE_GARMIN short(107)
#define ID_TYPE_MITHERM short(110)
@ -38,7 +41,7 @@
#define ID_TYPE_ITRACK short(127)
#define ID_TYPE_NUT short(128)
#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_TRACTIVE short(142)
#define ID_TYPE_VANMOOF short(145)
@ -51,7 +54,7 @@
#define ID_TYPE_EBEACON short(220)
#define ID_TYPE_ABEACON short(230)
#define ID_TYPE_IBEACON short(240)
#define ID_TYPE_ALIAS short(250)
class BleFingerprintCollection;
@ -79,6 +82,8 @@ public:
String getMac() const { return SMacf(address); }
short getIdType() const { return idType; }
String const getDiscriminator() { return disc; }
float getDistance() const { return output.value.position; }
@ -91,7 +96,7 @@ public:
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; };
@ -115,6 +120,8 @@ public:
bool shouldCount();
void fingerprintAddress();
void expire();
private:
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;
NimBLEAddress address;
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;
unsigned int qryAttempts = 0, qryDelayMillis = 0;
float raw = 0, lastReported = 0, temp = 0, humidity = 0;
@ -139,11 +146,8 @@ private:
bool filter();
void fingerprint(NimBLEAdvertisedDevice *advertisedDevice);
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 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{};
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;
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);
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:");
if (p == 0) {
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))
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();
}
return true;
}
@ -28,14 +39,14 @@ void BleFingerprintCollection::connectToWifi() {
std::istringstream iss(BleFingerprintCollection::knownIrks.c_str());
std::string 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))
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")
{
@ -116,21 +127,22 @@ BleFingerprint *BleFingerprintCollection::getFingerprintInternal(BLEAdvertisedDe
{
auto mac = advertisedDevice->getAddress();
auto it = std::find_if(fingerprints.begin(), fingerprints.end(), [mac](BleFingerprint *f)
{ return f->getAddress() == mac; });
if (it != fingerprints.end())
auto it = std::find_if(fingerprints.rbegin(), fingerprints.rend(), [mac](BleFingerprint *f) { return f->getAddress() == mac; });
if (it != fingerprints.rend())
return *it;
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)
{ return f->getId() == created->getId(); });
auto it2 = std::find_if(fingerprints.begin(), fingerprints.end(), [created](BleFingerprint *f) { return f->getId() == created->getId(); });
if (it2 != fingerprints.end())
{
auto found = *it2;
//Serial.printf("Detected mac switch for fingerprint id %s\n", found->getId().c_str());
created->setInitial(found->getRssi(), found->getDistance());
if (found->getIdType()>ID_TYPE_UNIQUE)
found->expire();
}
fingerprints.push_front(created);
fingerprints.push_back(created);
return created;
}
@ -144,18 +156,28 @@ BleFingerprint *BleFingerprintCollection::getFingerprint(BLEAdvertisedDevice *ad
return f;
}
const std::list<BleFingerprint *> BleFingerprintCollection::getCopy()
{
const std::vector<BleFingerprint *> BleFingerprintCollection::getCopy() {
if (xSemaphoreTake(fingerprintSemaphore, 1000) != pdTRUE)
log_e("Couldn't take semaphore!");
cleanupOldFingerprints();
std::list<BleFingerprint *> copy(fingerprints);
std::vector<BleFingerprint *> copy(fingerprints);
if (xSemaphoreGive(fingerprintSemaphore) != pdTRUE)
log_e("Couldn't give semaphore!");
return std::move(copy);
}
const std::list<BleFingerprint *>* const BleFingerprintCollection::getNative()
{
return &fingerprints;
const std::vector<BleFingerprint *> *const BleFingerprintCollection::getNative() { 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
#endif
struct DeviceConfig {
String id;
String alias;
String name;
uint8_t calRssi = 127;
};
class BleFingerprintCollection : public BLEAdvertisedDeviceCallbacks
{
public:
@ -22,13 +29,12 @@ public:
}
BleFingerprint *getFingerprint(BLEAdvertisedDevice *advertisedDevice);
void cleanupOldFingerprints();
const std::list<BleFingerprint *>* const getNative();
const std::list<BleFingerprint *> getCopy();
const std::vector<BleFingerprint *> *const getNative();
const std::vector<BleFingerprint *> getCopy();
void setDisable(bool disable) { _disable = disable; }
void connectToWifi();
bool command(String& command, String& pay);
bool config(String& id, String& json);
static std::vector<std::pair<uint8_t*,String>> irks;
bool config(String &id, String &json);
static String knownMacs, knownIrks, include, exclude, query;
static float skipDistance, maxDistance, absorption;
static int refRssi, forgetMs, skipMs;
@ -36,13 +42,20 @@ public:
static float countEnter, countExit;
static int countMs;
static std::vector<uint8_t *> getIrks();
static bool findDeviceConfig(const String &id, DeviceConfig &config);
static std::vector<DeviceConfig> getConfigs();
private:
static std::vector<DeviceConfig> deviceConfigs;
static std::vector<uint8_t *> irks;
bool _disable = false;
unsigned long lastCleanup = 0;
SemaphoreHandle_t fingerprintSemaphore;
std::list<BleFingerprint *> fingerprints;
std::vector<BleFingerprint *> fingerprints;
BleFingerprint *getFingerprintInternal(BLEAdvertisedDevice *advertisedDevice);
void onResult(BLEAdvertisedDevice *advertisedDevice) override

View File

@ -46,6 +46,15 @@ String kebabify(const String &text)
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 s(len * 2, ' ');

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,6 @@
/*
* Binary array for the Web UI.
* 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!
* Gzip is used for smaller size and improved speeds.
*/
// 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-node-resolve": "^13.3.0",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"autoprefixer": "^10.4.8",
"concurrently": "^7.3.0",
"cssnano": "^5.1.12",
"mime": "^3.0.0",
"pascal-case": "^3.1.2",
"postcss": "^8.4.14",
"postcss-load-config": "^4.0.1",
"rollup": "^2.77.2",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-generate-html-template": "^1.7.0",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-svelte": "^7.1.0",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.49.0",
"svelte-preprocess": "^4.10.7",
"tailwindcss": "^3.1.7",
"vite": "^3.0.0"
},
"dependencies": {
"svelte-spa-router": "^3.2.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 css from 'rollup-plugin-css-only';
import image from '@rollup/plugin-image';
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 { readFile, writeFile } from 'fs';
import { basename } from 'path';
@ -41,10 +42,7 @@ function cppGzipped(html, fileName, contentType) {
const array = hexdump(result);
const src = `/*
* Binary array for the Web UI.
* 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!
* Gzip is used for smaller size and improved speeds.
*/
// Autogenerated do not edit!!
@ -59,7 +57,6 @@ function cppGzipped(html, fileName, contentType) {
request->send(response);
}
`;
resolve(src);
}));
}
@ -176,15 +173,15 @@ export default {
svelte({}),
resolve(),
image(),
css({ output: 'bundle.css' }),
postcss({ extract: 'bundle.css' }),
htmlTemplate({
template: 'src/template.html',
target: 'index.html',
}),
terser(),
cpp({
additionalFiles: ['dist/index.html'],
fileName: function(a) { return "../src/ui_" + basename(a).replace(".", "_") + ".h"; }
})
]
};

View File

@ -1,125 +1,16 @@
<script>
import { devices } from './stores';
import SvelteTable from "svelte-table";
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,
},
];
import Router from "svelte-spa-router";
import Sidebar from "./components/Sidebar.svelte";
import routes from "./routes";
</script>
<main>
{#if $devices?.devices != null }
<SvelteTable columns="{columns}" rows="{$devices.devices}"
bind:filterSelections="{filterSelections}"
bind:sortBy = "{sortBy}"
bind:sortOrder = "{sortOrder}"
></SvelteTable>
{:else}
<h1>Error while loading devices</h1>
{/if}
</main>
<style>
main {
padding: 1rem;
}
h1 {
text-align: center;
}
</style>
<div>
<div class="flex">
<nav>
<Sidebar />
</nav>
<main class="max-w-7xl mx-auto py-8 sm:py-10 md:py-12 sm:px-10 lg:px-12">
<Router {routes} />
</main>
</div>
</div>

View File

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

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 { writable } from 'svelte/store';
export const devices = readable([], function start(set) {
var errors = 0;
var outstanding = false;
const interval = setInterval(() => {
if (outstanding) return;
outstanding = true;
fetch(`/json/devices`)
.then(d => d.json())
.then(r => {
outstanding = false;
errors = 0;
set(r);
})
.catch((ex) => {
outstanding = false;
if (errors > 5) set(null);
console.log(ex);
});
@ -19,3 +25,22 @@ export const devices = readable([], function start(set) {
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: []
};