Add more ui, tweak memory usage, add device configs (#578)
This commit is contained in:
parent
b8ea0d477d
commit
f7d23a1ae9
|
@ -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
|
|
@ -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());
|
||||
|
||||
hidden = shouldHide(newId);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -118,14 +127,12 @@ int bt_encrypt_be(const uint8_t *key, const uint8_t *plaintext, uint8_t *enc_dat
|
|||
|
||||
struct encryption_block
|
||||
{
|
||||
uint8_t key[16];
|
||||
uint8_t plain_text[16];
|
||||
uint8_t cipher_text[16];
|
||||
uint8_t key[16];
|
||||
uint8_t plain_text[16];
|
||||
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()
|
||||
|
@ -171,26 +171,23 @@ void BleFingerprint::fingerprintAddress()
|
|||
{
|
||||
switch (addressType)
|
||||
{
|
||||
case BLE_ADDR_PUBLIC:
|
||||
case BLE_ADDR_PUBLIC_ID:
|
||||
setId(mac, ID_TYPE_PUBLIC_MAC);
|
||||
case BLE_ADDR_PUBLIC:
|
||||
case BLE_ADDR_PUBLIC_ID:
|
||||
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;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ' ');
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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!!
|
||||
|
|
1843
src/ui_index_js.h
1843
src/ui_index_js.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
cssnano: {
|
||||
preset: 'default',
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"; }
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||
};
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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();
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/** @type {import('@sveltejs/kit').Config} */
|
||||
import preprocess from "svelte-preprocess";
|
||||
|
||||
const config = {
|
||||
preprocess: [
|
||||
preprocess({
|
||||
postcss: true,
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
export default config;
|
|
@ -0,0 +1,8 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
};
|
Loading…
Reference in New Issue