AI added possibility to add two additional locks.
Some checks failed
NukiHub / Build esp32-gl-s10 (release) (push) Waiting to run
NukiHub / Build esp32-c3 (release) (push) Failing after 21m55s
NukiHub / Build esp32-c5 (release) (push) Failing after 14m56s
NukiHub / Build esp32-c61 (release) (push) Failing after 9m31s
NukiHub / Build esp32 (release) (push) Failing after 3h0m37s
NukiHub / Build esp32-c6 (release) (push) Failing after 14m45s
NukiHub / Build esp32-nopsram (release) (push) Has been cancelled
NukiHub / Build esp32-p4 (release) (push) Has been cancelled
NukiHub / Build esp32-p4c5 (release) (push) Has been cancelled
NukiHub / Build esp32-s3 (release) (push) Has been cancelled
NukiHub / Build esp32-s3-nopsram (release) (push) Has been cancelled
NukiHub / Build esp32-s3-oct (release) (push) Has been cancelled
NukiHub / Build esp32-solo1 (release) (push) Has been cancelled
NukiHub / Build esp32-h2 (release) (push) Has been cancelled

* only ESP32-C3
* only Smart Lock Go
This commit is contained in:
2026-03-20 17:39:07 +01:00
parent 213e0e8692
commit ea23bf142a
17 changed files with 1945 additions and 1226 deletions

View File

@@ -185,6 +185,28 @@ Before enabling pairing mode using the button on the Nuki Lock Ultra / Nuki Lock
When pairing is successful, the web interface should show "Paired: Yes".<br> When pairing is successful, the web interface should show "Paired: Yes".<br>
## Pairing additional locks (Slot 2 and Slot 3)
Nuki Hub supports up to 3 locks on a single ESP/device IP.
To pair additional locks:
- Go to "Nuki Configuration" and enable "Nuki Lock slot 2 enabled" and/or "Nuki Lock slot 3 enabled", then Save.
- Go to "MQTT Configuration" and set "Lock Slot 2 MQTT Name" / "Lock Slot 3 MQTT Name", then Save.
- Put the corresponding lock into Bluetooth pairing mode using the lock button.
- Pairing for slot 2/3 is automatic while the slot is enabled and not yet paired.
How this is shown:
- On the info page, check "NUKI LOCK SLOT 2" and "NUKI LOCK SLOT 3" sections.
- MQTT topics are published per slot name:
- `<mqtt_path>/<slot_1_mqtt_name>/...`
- `<mqtt_path>/<slot_2_mqtt_name>/...`
- `<mqtt_path>/<slot_3_mqtt_name>/...`
Unpairing slot 2/3 in web interface:
- Open "Credentials".
- Use "Unpair Nuki Lock Slot 2" or "Unpair Nuki Lock Slot 3".
- Enter the shown 4 digit confirmation code.
## Hybrid mode ## Hybrid mode
Hybrid mode allows you to use the official Nuki MQTT implementation on a Nuki Lock 3.0 Pro, Nuki Lock 4.0, Nuki Lock 4.0 Pro, Nuki Lock 5.0 Pro, Nuki Lock Go or Nuki Lock Ultra in conjunction with Nuki Hub.<br> Hybrid mode allows you to use the official Nuki MQTT implementation on a Nuki Lock 3.0 Pro, Nuki Lock 4.0, Nuki Lock 4.0 Pro, Nuki Lock 5.0 Pro, Nuki Lock Go or Nuki Lock Ultra in conjunction with Nuki Hub.<br>
@@ -265,6 +287,9 @@ In a browser navigate to the IP address assigned to the ESP32.
- MQTT User: If using authentication on the MQTT broker set to a username with read/write rights on the MQTT broker, set to # to clear - MQTT User: If using authentication on the MQTT broker set to a username with read/write rights on the MQTT broker, set to # to clear
- MQTT Password : If using authentication on the MQTT broker set to the password belonging to a username with read/write rights on the MQTT broker, set to # to clear - MQTT Password : If using authentication on the MQTT broker set to the password belonging to a username with read/write rights on the MQTT broker, set to # to clear
- MQTT Nuki Hub Path: Set to the preferred MQTT root topic for Nuki Hub, defaults to "nukihub". Make sure this topic is unique when using multiple ESP32 Nuki Hub devices - MQTT Nuki Hub Path: Set to the preferred MQTT root topic for Nuki Hub, defaults to "nukihub". Make sure this topic is unique when using multiple ESP32 Nuki Hub devices
- Lock Slot 1 MQTT Name: Set the MQTT topic name for lock slot 1 (default: `lock`)
- Lock Slot 2 MQTT Name: Set the MQTT topic name for lock slot 2 (default: `lock2`)
- Lock Slot 3 MQTT Name: Set the MQTT topic name for lock slot 3 (default: `lock3`)
- Enable Home Assistant auto discovery: Enable Home Assistant MQTT auto discovery. Will automatically create entities in Home Assistant for Nuki Hub and connected Nuki Lock and/or Opener when enabled. - Enable Home Assistant auto discovery: Enable Home Assistant MQTT auto discovery. Will automatically create entities in Home Assistant for Nuki Hub and connected Nuki Lock and/or Opener when enabled.
#### Advanced MQTT Configuration #### Advanced MQTT Configuration
@@ -289,6 +314,8 @@ In a browser navigate to the IP address assigned to the ESP32.
#### Basic Nuki Configuration #### Basic Nuki Configuration
- Nuki Smartlock enabled: Enable if you want Nuki Hub to connect to a Nuki Lock (All versions) - Nuki Smartlock enabled: Enable if you want Nuki Hub to connect to a Nuki Lock (All versions)
- Nuki Lock slot 2 enabled: Enable if you want Nuki Hub to connect to a second Nuki Lock
- Nuki Lock slot 3 enabled: Enable if you want Nuki Hub to connect to a third Nuki Lock
- Nuki Smartlock Ultra/Go/5th gen enabled: Enable if you want Nuki Hub to connect to a Nuki Lock Ultra/Go/5th gen Pro - Nuki Smartlock Ultra/Go/5th gen enabled: Enable if you want Nuki Hub to connect to a Nuki Lock Ultra/Go/5th gen Pro
- Nuki Opener enabled: Enable if you want Nuki Hub to connect to a Nuki Opener - Nuki Opener enabled: Enable if you want Nuki Hub to connect to a Nuki Opener
@@ -366,9 +393,9 @@ Note: All of the following requires the Nuki security code / PIN to be set, see
- PIN Code: Fill with the Nuki Security Code of the Nuki Lock and/or Nuki Opener. Required for functions that require the security code to be sent to the lock/opener such as setting lock permissions/adding keypad codes, viewing the activity log or changing the Nuki device configuration. Set to "#" to remove the security code from the Nuki Hub configuration. - PIN Code: Fill with the Nuki Security Code of the Nuki Lock and/or Nuki Opener. Required for functions that require the security code to be sent to the lock/opener such as setting lock permissions/adding keypad codes, viewing the activity log or changing the Nuki device configuration. Set to "#" to remove the security code from the Nuki Hub configuration.
- PIN Code Ultra/5th gen: Fill with the 6-digit Nuki Security Code of the Nuki Lock Ultra/Go/5th gen Pro. Required for pairing (and many other functions) - PIN Code Ultra/5th gen: Fill with the 6-digit Nuki Security Code of the Nuki Lock Ultra/Go/5th gen Pro. Required for pairing (and many other functions)
#### Unpair Nuki Lock / Unpair Nuki Opener #### Unpair Nuki Lock / Unpair Nuki Lock Slot 2 / Unpair Nuki Lock Slot 3 / Unpair Nuki Opener
- Type [4 DIGIT CODE] to confirm unpair: Set to the shown randomly generated code to unpair the Nuki Lock or Opener from the Nuki Hub. - Type [4 DIGIT CODE] to confirm unpair: Set to the shown randomly generated code to unpair the selected Nuki device from the Nuki Hub.
#### Factory reset Nuki Hub #### Factory reset Nuki Hub

View File

@@ -5,8 +5,8 @@
#define NUKI_HUB_VERSION "9.15" #define NUKI_HUB_VERSION "9.15"
#define NUKI_HUB_VERSION_INT (uint32_t)915 #define NUKI_HUB_VERSION_INT (uint32_t)915
#define NUKI_HUB_BUILD "unknownbuildnr" #define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2026-01-09" #define NUKI_HUB_DATE "2026-03-20"
#define NUKI_HUB_DATE "2026-01-09" #define NUKI_HUB_DATE "2026-03-20"
#define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"

View File

@@ -19,7 +19,7 @@ HomeAssistantDiscovery::HomeAssistantDiscovery(NetworkDevice* device, Preference
_hostname = _preferences->getString(preference_hostname, ""); _hostname = _preferences->getString(preference_hostname, "");
} }
void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride)
{ {
uint64_t savedDevId = _preferences->getULong64(preference_nukihub_id, 0); uint64_t savedDevId = _preferences->getULong64(preference_nukihub_id, 0);
uint64_t curDevId; uint64_t curDevId;
@@ -96,12 +96,11 @@ void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName
} }
else if(type == 1) else if(type == 1)
{ {
if(_preferences->getUInt(preference_nuki_id_lock, 0) != nukiId) if(topicOverride == nullptr && _preferences->getUInt(preference_nuki_id_lock, 0) != nukiId)
{ {
return; return;
} }
String lockTopic = _baseTopic; String lockTopic = (topicOverride != nullptr) ? String(topicOverride) : _baseTopic + "/lock";
lockTopic.concat("/lock");
publishHASSConfig((char*)"SmartLock", lockTopic.c_str(), nukiName, uidString, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch"); publishHASSConfig((char*)"SmartLock", lockTopic.c_str(), nukiName, uidString, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, publishAuthData, (char*)"lock", (char*)"unlock", (char*)"unlatch");
Log->println("HomeAssistant setup for lock completed."); Log->println("HomeAssistant setup for lock completed.");
} }

View File

@@ -7,7 +7,7 @@ class HomeAssistantDiscovery
{ {
public: public:
explicit HomeAssistantDiscovery(NetworkDevice* device, Preferences* preferences, char* buffer, size_t bufferSize); explicit HomeAssistantDiscovery(NetworkDevice* device, Preferences* preferences, char* buffer, size_t bufferSize);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad); void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride = nullptr);
void disableHASS(); void disableHASS();
void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString); void removeHassTopic(const String& mqttDeviceType, const String& mqttDeviceName, const String& uidString);
void publishHassTopic(const String& mqttDeviceType, void publishHassTopic(const String& mqttDeviceType,
@@ -67,4 +67,4 @@ private:
char* _buffer; char* _buffer;
const size_t _bufferSize; const size_t _bufferSize;
}; };

View File

@@ -5,6 +5,34 @@
#include "PreferencesKeys.h" #include "PreferencesKeys.h"
#include <DuoAuthLib.h> #include <DuoAuthLib.h>
#include <TOTP-RC6236-generator.hpp> #include <TOTP-RC6236-generator.hpp>
#include <ctype.h>
static String normalizeMqttLockName(String name)
{
name.trim();
name.toLowerCase();
return name;
}
static bool isValidMqttLockName(const String& name)
{
if(name.length() < 3 || name.length() > 32)
{
return false;
}
for(size_t i = 0; i < name.length(); i++)
{
const char c = name.charAt(i);
if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-')
{
continue;
}
return false;
}
return true;
}
ImportExport::ImportExport(Preferences *preferences) ImportExport::ImportExport(Preferences *preferences)
: _preferences(preferences) : _preferences(preferences)
@@ -657,6 +685,22 @@ void ImportExport::exportNukiHubJson(JsonDocument &json, bool redacted, bool pai
json[key] = text; json[key] = text;
memset(text, 0, sizeof(text)); memset(text, 0, sizeof(text));
} }
JsonArray locks = json["locks"].to<JsonArray>();
JsonObject lock1 = locks.add<JsonObject>();
lock1["slot"] = 1;
lock1["enabled"] = _preferences->getBool(preference_lock_enabled, true) ? "1" : "0";
lock1["mqtt_name"] = _preferences->getString(preference_mqtt_lock_name_1, "lock");
JsonObject lock2 = locks.add<JsonObject>();
lock2["slot"] = 2;
lock2["enabled"] = _preferences->getBool(preference_lock_enabled_2, false) ? "1" : "0";
lock2["mqtt_name"] = _preferences->getString(preference_mqtt_lock_name_2, "lock2");
JsonObject lock3 = locks.add<JsonObject>();
lock3["slot"] = 3;
lock3["enabled"] = _preferences->getBool(preference_lock_enabled_3, false) ? "1" : "0";
lock3["mqtt_name"] = _preferences->getString(preference_mqtt_lock_name_3, "lock3");
} }
JsonDocument ImportExport::importJson(JsonDocument &doc) JsonDocument ImportExport::importJson(JsonDocument &doc)
@@ -677,9 +721,97 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
const std::vector<char*> intPrefs = debugPreferences.getPreferencesIntKeys(); const std::vector<char*> intPrefs = debugPreferences.getPreferencesIntKeys();
const std::vector<char*> uintPrefs = debugPreferences.getPreferencesUIntKeys(); const std::vector<char*> uintPrefs = debugPreferences.getPreferencesUIntKeys();
const std::vector<char*> uint64Prefs = debugPreferences.getPreferencesUInt64Keys(); const std::vector<char*> uint64Prefs = debugPreferences.getPreferencesUInt64Keys();
bool hasLocksArray = false;
String importedLockNames[3] = {
normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_1, "lock")),
normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_2, "lock2")),
normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_3, "lock3"))
};
bool importedLockEnabled[3] = {
_preferences->getBool(preference_lock_enabled, true),
_preferences->getBool(preference_lock_enabled_2, false),
_preferences->getBool(preference_lock_enabled_3, false)
};
if(!doc["locks"].isNull() && doc["locks"].is<JsonArray>())
{
hasLocksArray = true;
JsonArray locks = doc["locks"].as<JsonArray>();
if(locks.size() == 0 || locks.size() > 3)
{
json["_import_error"] = "Invalid locks array size (must be 1..3).";
return json;
}
for(JsonVariant v : locks)
{
if(!v.is<JsonObject>())
{
json["_import_error"] = "Invalid locks entry (must be object).";
return json;
}
JsonObject o = v.as<JsonObject>();
int slot = 0;
if(!o["slot"].isNull())
{
slot = o["slot"].as<int>();
}
if(slot < 1 || slot > 3)
{
json["_import_error"] = "Invalid lock slot in import (must be 1..3).";
return json;
}
if(o["mqtt_name"].isNull())
{
json["_import_error"] = "Missing locks[].mqtt_name.";
return json;
}
String lockName = normalizeMqttLockName(o["mqtt_name"].as<String>());
if(!isValidMqttLockName(lockName))
{
json["_import_error"] = "Invalid locks[].mqtt_name (allowed: a-z,0-9,_,-; length 3..32).";
return json;
}
importedLockNames[slot - 1] = lockName;
if(!o["enabled"].isNull())
{
if(o["enabled"].is<bool>())
{
importedLockEnabled[slot - 1] = o["enabled"].as<bool>();
}
else
{
const String enabledVal = o["enabled"].as<String>();
importedLockEnabled[slot - 1] = (enabledVal == "1" || enabledVal == "true");
}
}
}
if(importedLockNames[0] == importedLockNames[1] ||
importedLockNames[0] == importedLockNames[2] ||
importedLockNames[1] == importedLockNames[2])
{
json["_import_error"] = "Duplicate locks[].mqtt_name is not allowed.";
return json;
}
}
for(const auto& key : keysPrefs) for(const auto& key : keysPrefs)
{ {
if(hasLocksArray &&
(strcmp(key, preference_mqtt_lock_name_1) == 0 ||
strcmp(key, preference_mqtt_lock_name_2) == 0 ||
strcmp(key, preference_mqtt_lock_name_3) == 0 ||
strcmp(key, preference_lock_enabled) == 0 ||
strcmp(key, preference_lock_enabled_2) == 0 ||
strcmp(key, preference_lock_enabled_3) == 0))
{
continue;
}
if(doc[key].isNull()) if(doc[key].isNull())
{ {
continue; continue;
@@ -760,6 +892,21 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
} }
if(hasLocksArray)
{
_preferences->putString(preference_mqtt_lock_name_1, importedLockNames[0]);
_preferences->putString(preference_mqtt_lock_name_2, importedLockNames[1]);
_preferences->putString(preference_mqtt_lock_name_3, importedLockNames[2]);
_preferences->putBool(preference_lock_enabled, importedLockEnabled[0]);
_preferences->putBool(preference_lock_enabled_2, importedLockEnabled[1]);
_preferences->putBool(preference_lock_enabled_3, importedLockEnabled[2]);
if(importedLockEnabled[1] || importedLockEnabled[2])
{
_preferences->putBool(preference_opener_enabled, false);
}
json["locks"] = "changed";
}
for(const auto& key : bytePrefs) for(const auto& key : bytePrefs)
{ {
if(!doc[key].isNull() && doc[key].is<JsonVariant>()) if(!doc[key].isNull() && doc[key].is<JsonVariant>())
@@ -1144,4 +1291,4 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
return json; return json;
} }

View File

@@ -821,17 +821,38 @@ bool NukiNetwork::reconnect(bool force)
if(_preferences->getBool(preference_reset_mqtt_topics, false)) if(_preferences->getBool(preference_reset_mqtt_topics, false))
{ {
char mqttLockPath[181] = {0}; char mqttLockPath1[181] = {0};
char mqttLockPath2[181] = {0};
char mqttLockPath3[181] = {0};
char mqttOpenerPath[181] = {0}; char mqttOpenerPath[181] = {0};
char mqttOldOpenerPath[181] = {0}; char mqttOldOpenerPath[181] = {0};
char mqttOldOpenerPath2[181] = {0}; char mqttOldOpenerPath2[181] = {0};
String mqttPath = _preferences->getString(preference_mqtt_lock_path, ""); String mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/lock"); mqttPath.concat("/");
mqttPath.concat(_preferences->getString(preference_mqtt_lock_name_1, "lock"));
size_t len = mqttPath.length(); size_t len = mqttPath.length();
for(int i=0; i < len; i++) for(int i=0; i < len; i++)
{ {
mqttLockPath[i] = mqttPath.charAt(i); mqttLockPath1[i] = mqttPath.charAt(i);
}
mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/");
mqttPath.concat(_preferences->getString(preference_mqtt_lock_name_2, "lock2"));
len = mqttPath.length();
for(int i=0; i < len; i++)
{
mqttLockPath2[i] = mqttPath.charAt(i);
}
mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/");
mqttPath.concat(_preferences->getString(preference_mqtt_lock_name_3, "lock3"));
len = mqttPath.length();
for(int i=0; i < len; i++)
{
mqttLockPath3[i] = mqttPath.charAt(i);
} }
mqttPath = _preferences->getString(preference_mqtt_lock_path, ""); mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
@@ -867,7 +888,9 @@ bool NukiNetwork::reconnect(bool force)
for(const auto& topic : mqttTopicsKeys) for(const auto& topic : mqttTopicsKeys)
{ {
removeTopic(_maintenancePathPrefix, topic); removeTopic(_maintenancePathPrefix, topic);
removeTopic(mqttLockPath, topic); removeTopic(mqttLockPath1, topic);
removeTopic(mqttLockPath2, topic);
removeTopic(mqttLockPath3, topic);
removeTopic(mqttOpenerPath, topic); removeTopic(mqttOpenerPath, topic);
if (len > 5) if (len > 5)
{ {
@@ -1536,9 +1559,9 @@ void NukiNetwork::removeTopic(const String& mqttPath, const String& mqttTopic)
#endif #endif
} }
void NukiNetwork::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) void NukiNetwork::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride)
{ {
_hadiscovery->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad); _hadiscovery->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, topicOverride);
} }
void NukiNetwork::disableHASS() void NukiNetwork::disableHASS()

View File

@@ -71,7 +71,7 @@ public:
void advertisingModeToString(const Nuki::AdvertisingMode advmode, char* str); void advertisingModeToString(const Nuki::AdvertisingMode advmode, char* str);
void timeZoneIdToString(const Nuki::TimeZoneId timeZoneId, char* str); void timeZoneIdToString(const Nuki::TimeZoneId timeZoneId, char* str);
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad); void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad, const char* topicOverride = nullptr);
void disableHASS(); void disableHASS();
void publishHassTopic(const String& mqttDeviceType, void publishHassTopic(const String& mqttDeviceType,
const String& mqttDeviceName, const String& mqttDeviceName,
@@ -183,4 +183,4 @@ private:
int8_t _lastRssi = 127; int8_t _lastRssi = 127;
#endif #endif
}; };

View File

@@ -14,12 +14,38 @@ extern bool forceEnableWebServer;
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start"); extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end"); extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
NukiNetworkLock::NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize) static String normalizedLockMqttName(Preferences* preferences, const char* prefKey)
{
String in = preferences->getString(prefKey, "lock");
in.trim();
in.toLowerCase();
String out;
out.reserve(in.length());
for(size_t i = 0; i < in.length(); i++)
{
const char c = in.charAt(i);
if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-')
{
out.concat(c);
}
}
if(out.length() < 3 || out.length() > 32)
{
return String("lock");
}
return out;
}
NukiNetworkLock::NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot)
: _network(network), : _network(network),
_nukiOfficial(nukiOfficial), _nukiOfficial(nukiOfficial),
_preferences(preferences), _preferences(preferences),
_buffer(buffer), _buffer(buffer),
_bufferSize(bufferSize) _bufferSize(bufferSize),
_lockSlot(lockSlot)
{ {
_nukiPublisher = new NukiPublisher(network, _mqttPath); _nukiPublisher = new NukiPublisher(network, _mqttPath);
_nukiOfficial->setPublisher(_nukiPublisher); _nukiOfficial->setPublisher(_nukiPublisher);
@@ -42,7 +68,8 @@ void NukiNetworkLock::initialize()
} }
String mqttPath = _preferences->getString(preference_mqtt_lock_path, ""); String mqttPath = _preferences->getString(preference_mqtt_lock_path, "");
mqttPath.concat("/lock"); mqttPath.concat("/");
mqttPath.concat(normalizedLockMqttName(_preferences, mqttNamePreferenceKey()));
size_t len = mqttPath.length(); size_t len = mqttPath.length();
for(int i=0; i < len; i++) for(int i=0; i < len; i++)
@@ -151,7 +178,7 @@ void NukiNetworkLock::initialize()
if(_nukiOfficial->getOffEnabled()) if(_nukiOfficial->getOffEnabled())
{ {
_nukiOfficial->setUid(_preferences->getUInt(preference_nuki_id_lock, 0)); _nukiOfficial->setUid(_preferences->getUInt(nukiIdPreferenceKey(), 0));
for(const auto& offTopic : _nukiOfficial->getOffTopics()) for(const auto& offTopic : _nukiOfficial->getOffTopics())
{ {
@@ -252,7 +279,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
Log->print("Lock action received: "); Log->print("Lock action received: ");
Log->println(data); Log->println(data);
LockActionResult lockActionResult = LockActionResult::Failed; LockActionResult lockActionResult = LockActionResult::Failed;
if(_lockActionReceivedCallback != NULL) if(_lockActionReceivedCallback)
{ {
lockActionResult = _lockActionReceivedCallback(data); lockActionResult = _lockActionReceivedCallback(data);
} }
@@ -348,7 +375,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return; return;
} }
if(_configUpdateReceivedCallback != NULL) if(_configUpdateReceivedCallback)
{ {
_configUpdateReceivedCallback(data); _configUpdateReceivedCallback(data);
} }
@@ -363,7 +390,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return; return;
} }
if(_keypadJsonCommandReceivedReceivedCallback != NULL) if(_keypadJsonCommandReceivedReceivedCallback)
{ {
_keypadJsonCommandReceivedReceivedCallback(data); _keypadJsonCommandReceivedReceivedCallback(data);
} }
@@ -378,7 +405,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return; return;
} }
if(_timeControlCommandReceivedReceivedCallback != NULL) if(_timeControlCommandReceivedReceivedCallback)
{ {
_timeControlCommandReceivedReceivedCallback(data); _timeControlCommandReceivedReceivedCallback(data);
} }
@@ -393,7 +420,7 @@ void NukiNetworkLock::onMqttDataReceived(const char* topic, byte* payload, const
return; return;
} }
if(_authCommandReceivedReceivedCallback != NULL) if(_authCommandReceivedReceivedCallback)
{ {
_authCommandReceivedReceivedCallback(data); _authCommandReceivedReceivedCallback(data);
} }
@@ -1011,9 +1038,8 @@ void NukiNetworkLock::publishKeypad(const std::list<NukiLock::KeypadEntry>& entr
bool topicPerEntry = _preferences->getBool(preference_keypad_topic_per_entry, false); bool topicPerEntry = _preferences->getBool(preference_keypad_topic_per_entry, false);
uint index = 0; uint index = 0;
char uidString[20]; char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); itoa(_preferences->getUInt(nukiIdPreferenceKey(), 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path); String baseTopic = _mqttPath;
baseTopic.concat("/lock");
JsonDocument json; JsonDocument json;
for(const auto& entry : entries) for(const auto& entry : entries)
@@ -1216,9 +1242,8 @@ void NukiNetworkLock::publishTimeControl(const std::list<NukiLock::TimeControlEn
uint index = 0; uint index = 0;
char str[50]; char str[50];
char uidString[20]; char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); itoa(_preferences->getUInt(nukiIdPreferenceKey(), 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path); String baseTopic = _mqttPath;
baseTopic.concat("/lock");
JsonDocument json; JsonDocument json;
for(const auto& entry : timeControlEntries) for(const auto& entry : timeControlEntries)
@@ -1349,9 +1374,8 @@ void NukiNetworkLock::publishAuth(const std::list<NukiLock::AuthorizationEntry>&
uint index = 0; uint index = 0;
char str[50]; char str[50];
char uidString[20]; char uidString[20];
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); itoa(_preferences->getUInt(nukiIdPreferenceKey(), 0), uidString, 16);
String baseTopic = _preferences->getString(preference_mqtt_lock_path); String baseTopic = _mqttPath;
baseTopic.concat("/lock");
JsonDocument json; JsonDocument json;
for(const auto& entry : authEntries) for(const auto& entry : authEntries)
@@ -1529,22 +1553,22 @@ void NukiNetworkLock::publishStatusUpdated(const bool statusUpdated)
_nukiPublisher->publishBool(mqtt_topic_lock_status_updated, statusUpdated, true); _nukiPublisher->publishBool(mqtt_topic_lock_status_updated, statusUpdated, true);
} }
void NukiNetworkLock::setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char *)) void NukiNetworkLock::setLockActionReceivedCallback(std::function<LockActionResult(const char* value)> lockActionReceivedCallback)
{ {
_lockActionReceivedCallback = lockActionReceivedCallback; _lockActionReceivedCallback = lockActionReceivedCallback;
} }
void NukiNetworkLock::setOfficialUpdateReceivedCallback(void (*officialUpdateReceivedCallback)(const char *, const char *)) void NukiNetworkLock::setOfficialUpdateReceivedCallback(std::function<void(const char* path, const char* value)> officialUpdateReceivedCallback)
{ {
_officialUpdateReceivedCallback = officialUpdateReceivedCallback; _officialUpdateReceivedCallback = officialUpdateReceivedCallback;
} }
void NukiNetworkLock::setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char *)) void NukiNetworkLock::setConfigUpdateReceivedCallback(std::function<void(const char* value)> configUpdateReceivedCallback)
{ {
_configUpdateReceivedCallback = configUpdateReceivedCallback; _configUpdateReceivedCallback = configUpdateReceivedCallback;
} }
void NukiNetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled)) void NukiNetworkLock::setKeypadCommandReceivedCallback(std::function<void(const char* command, const uint& id, const String& name, const String& code, const int& enabled)> keypadCommandReceivedReceivedCallback)
{ {
if(_disableNonJSON) if(_disableNonJSON)
{ {
@@ -1553,21 +1577,47 @@ void NukiNetworkLock::setKeypadCommandReceivedCallback(void (*keypadCommandRecei
_keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback; _keypadCommandReceivedReceivedCallback = keypadCommandReceivedReceivedCallback;
} }
void NukiNetworkLock::setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char *)) void NukiNetworkLock::setKeypadJsonCommandReceivedCallback(std::function<void(const char* value)> keypadJsonCommandReceivedReceivedCallback)
{ {
_keypadJsonCommandReceivedReceivedCallback = keypadJsonCommandReceivedReceivedCallback; _keypadJsonCommandReceivedReceivedCallback = keypadJsonCommandReceivedReceivedCallback;
} }
void NukiNetworkLock::setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char *)) void NukiNetworkLock::setTimeControlCommandReceivedCallback(std::function<void(const char* value)> timeControlCommandReceivedReceivedCallback)
{ {
_timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback; _timeControlCommandReceivedReceivedCallback = timeControlCommandReceivedReceivedCallback;
} }
void NukiNetworkLock::setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char *)) void NukiNetworkLock::setAuthCommandReceivedCallback(std::function<void(const char* value)> authCommandReceivedReceivedCallback)
{ {
_authCommandReceivedReceivedCallback = authCommandReceivedReceivedCallback; _authCommandReceivedReceivedCallback = authCommandReceivedReceivedCallback;
} }
const char* NukiNetworkLock::nukiIdPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_nuki_id_lock_2;
case 3:
return preference_nuki_id_lock_3;
default:
return preference_nuki_id_lock;
}
}
const char* NukiNetworkLock::mqttNamePreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_mqtt_lock_name_2;
case 3:
return preference_mqtt_lock_name_3;
default:
return preference_mqtt_lock_name_1;
}
}
void NukiNetworkLock::buildMqttPath(const char* path, char* outPath) void NukiNetworkLock::buildMqttPath(const char* path, char* outPath)
{ {
int offset = 0; int offset = 0;
@@ -1627,7 +1677,7 @@ const uint8_t NukiNetworkLock::queryCommands()
void NukiNetworkLock::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) void NukiNetworkLock::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad)
{ {
_network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad); _network->setupHASS(type, nukiId, nukiName, firmwareVersion, hardwareVersion, hasDoorSensor, hasKeypad, _mqttPath);
} }
@@ -1647,4 +1697,4 @@ const char* NukiNetworkLock::getAuthName()
return _authEntries[getAuthId()].c_str(); return _authEntries[getAuthId()].c_str();
} }
return _authName; return _authName;
} }

View File

@@ -15,11 +15,12 @@
#include "NukiOfficial.h" #include "NukiOfficial.h"
#include "NukiPublisher.h" #include "NukiPublisher.h"
#include "EspMillis.h" #include "EspMillis.h"
#include <functional>
class NukiNetworkLock : public MqttReceiver class NukiNetworkLock : public MqttReceiver
{ {
public: public:
explicit NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize); explicit NukiNetworkLock(NukiNetwork* network, NukiOfficial* nukiOfficial, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot = 1);
virtual ~NukiNetworkLock(); virtual ~NukiNetworkLock();
void initialize(); void initialize();
@@ -48,13 +49,13 @@ public:
void publishAuthCommandResult(const char* result); void publishAuthCommandResult(const char* result);
void publishOffAction(const int value); void publishOffAction(const int value);
void setLockActionReceivedCallback(LockActionResult (*lockActionReceivedCallback)(const char* value)); void setLockActionReceivedCallback(std::function<LockActionResult(const char* value)> lockActionReceivedCallback);
void setOfficialUpdateReceivedCallback(void (*officialUpdateReceivedCallback)(const char* path, const char* value)); void setOfficialUpdateReceivedCallback(std::function<void(const char* path, const char* value)> officialUpdateReceivedCallback);
void setConfigUpdateReceivedCallback(void (*configUpdateReceivedCallback)(const char* value)); void setConfigUpdateReceivedCallback(std::function<void(const char* value)> configUpdateReceivedCallback);
void setKeypadCommandReceivedCallback(void (*keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled)); void setKeypadCommandReceivedCallback(std::function<void(const char* command, const uint& id, const String& name, const String& code, const int& enabled)> keypadCommandReceivedReceivedCallback);
void setKeypadJsonCommandReceivedCallback(void (*keypadJsonCommandReceivedReceivedCallback)(const char* value)); void setKeypadJsonCommandReceivedCallback(std::function<void(const char* value)> keypadJsonCommandReceivedReceivedCallback);
void setTimeControlCommandReceivedCallback(void (*timeControlCommandReceivedReceivedCallback)(const char* value)); void setTimeControlCommandReceivedCallback(std::function<void(const char* value)> timeControlCommandReceivedReceivedCallback);
void setAuthCommandReceivedCallback(void (*authCommandReceivedReceivedCallback)(const char* value)); void setAuthCommandReceivedCallback(std::function<void(const char* value)> authCommandReceivedReceivedCallback);
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override; void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length) override;
void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad); void setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad);
@@ -65,11 +66,11 @@ public:
private: private:
const bool comparePrefixedPath(const char* fullPath, const char* subPath); const bool comparePrefixedPath(const char* fullPath, const char* subPath);
const char* nukiIdPreferenceKey() const;
const char* mqttNamePreferenceKey() const;
void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry); void publishKeypadEntry(const String topic, NukiLock::KeypadEntry entry);
void (*_officialUpdateReceivedCallback)(const char* path, const char* value) = nullptr;
String concat(String a, String b); String concat(String a, String b);
void buildMqttPath(const char* path, char* outPath); void buildMqttPath(const char* path, char* outPath);
@@ -102,14 +103,16 @@ private:
char _nukiName[33]; char _nukiName[33];
char _authName[33]; char _authName[33];
uint8_t _lockSlot = 1;
char* _buffer; char* _buffer;
size_t _bufferSize; size_t _bufferSize;
LockActionResult (*_lockActionReceivedCallback)(const char* value) = nullptr; std::function<LockActionResult(const char* value)> _lockActionReceivedCallback = nullptr;
void (*_configUpdateReceivedCallback)(const char* value) = nullptr; std::function<void(const char* path, const char* value)> _officialUpdateReceivedCallback = nullptr;
void (*_keypadCommandReceivedReceivedCallback)(const char* command, const uint& id, const String& name, const String& code, const int& enabled) = nullptr; std::function<void(const char* value)> _configUpdateReceivedCallback = nullptr;
void (*_keypadJsonCommandReceivedReceivedCallback)(const char* value) = nullptr; std::function<void(const char* command, const uint& id, const String& name, const String& code, const int& enabled)> _keypadCommandReceivedReceivedCallback = nullptr;
void (*_timeControlCommandReceivedReceivedCallback)(const char* value) = nullptr; std::function<void(const char* value)> _keypadJsonCommandReceivedReceivedCallback = nullptr;
void (*_authCommandReceivedReceivedCallback)(const char* value) = nullptr; std::function<void(const char* value)> _timeControlCommandReceivedReceivedCallback = nullptr;
}; std::function<void(const char* value)> _authCommandReceivedReceivedCallback = nullptr;
};

View File

@@ -12,9 +12,7 @@
#include "util/NukiHelper.h" #include "util/NukiHelper.h"
#include "util/NukiRetryHandler.h" #include "util/NukiRetryHandler.h"
NukiWrapper* nukiInst = nullptr; NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot)
NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize)
: _deviceName(deviceName), : _deviceName(deviceName),
_deviceId(deviceId), _deviceId(deviceId),
_bleScanner(scanner), _bleScanner(scanner),
@@ -23,6 +21,7 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId,
_nukiOfficial(nukiOfficial), _nukiOfficial(nukiOfficial),
_gpio(gpio), _gpio(gpio),
_preferences(preferences), _preferences(preferences),
_lockSlot(lockSlot),
_buffer(buffer), _buffer(buffer),
_bufferSize(bufferSize) _bufferSize(bufferSize)
{ {
@@ -30,23 +29,48 @@ NukiWrapper::NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId,
Log->print("Device id lock: "); Log->print("Device id lock: ");
Log->println(_deviceId->get()); Log->println(_deviceId->get());
nukiInst = this;
memset(&_lastKeyTurnerState, sizeof(NukiLock::KeyTurnerState), 0); memset(&_lastKeyTurnerState, sizeof(NukiLock::KeyTurnerState), 0);
memset(&_lastBatteryReport, sizeof(NukiLock::BatteryReport), 0); memset(&_lastBatteryReport, sizeof(NukiLock::BatteryReport), 0);
memset(&_batteryReport, sizeof(NukiLock::BatteryReport), 0); memset(&_batteryReport, sizeof(NukiLock::BatteryReport), 0);
memset(&_keyTurnerState, sizeof(NukiLock::KeyTurnerState), 0); memset(&_keyTurnerState, sizeof(NukiLock::KeyTurnerState), 0);
_keyTurnerState.lockState = NukiLock::LockState::Undefined; _keyTurnerState.lockState = NukiLock::LockState::Undefined;
network->setLockActionReceivedCallback(nukiInst->onLockActionReceivedCallback); network->setLockActionReceivedCallback([this](const char* value)
network->setOfficialUpdateReceivedCallback(nukiInst->onOfficialUpdateReceivedCallback); {
network->setConfigUpdateReceivedCallback(nukiInst->onConfigUpdateReceivedCallback); return onLockActionReceived(value);
network->setKeypadCommandReceivedCallback(nukiInst->onKeypadCommandReceivedCallback); });
network->setKeypadJsonCommandReceivedCallback(nukiInst->onKeypadJsonCommandReceivedCallback); network->setOfficialUpdateReceivedCallback([this](const char* topic, const char* value)
network->setTimeControlCommandReceivedCallback(nukiInst->onTimeControlCommandReceivedCallback); {
network->setAuthCommandReceivedCallback(nukiInst->onAuthCommandReceivedCallback); onOfficialUpdateReceived(topic, value);
});
network->setConfigUpdateReceivedCallback([this](const char* value)
{
onConfigUpdateReceived(value);
});
network->setKeypadCommandReceivedCallback([this](const char* command, const uint& id, const String& name, const String& code, const int& enabled)
{
onKeypadCommandReceived(command, id, name, code, enabled);
});
network->setKeypadJsonCommandReceivedCallback([this](const char* value)
{
onKeypadJsonCommandReceived(value);
});
network->setTimeControlCommandReceivedCallback([this](const char* value)
{
onTimeControlCommandReceived(value);
});
network->setAuthCommandReceivedCallback([this](const char* value)
{
onAuthCommandReceived(value);
});
_gpio->addCallback(NukiWrapper::gpioActionCallback); if(_lockSlot == 1)
{
_gpio->addCallback([this](const GpioAction& action, const int& pin)
{
onGpioActionReceived(action, pin);
});
}
} }
@@ -156,9 +180,9 @@ void NukiWrapper::readSettings()
_intervalKeypad = _preferences->getInt(preference_query_interval_keypad); _intervalKeypad = _preferences->getInt(preference_query_interval_keypad);
_keypadEnabled = _preferences->getBool(preference_keypad_info_enabled); _keypadEnabled = _preferences->getBool(preference_keypad_info_enabled);
_publishAuthData = _preferences->getBool(preference_publish_authdata); _publishAuthData = _preferences->getBool(preference_publish_authdata);
_maxKeypadCodeCount = _preferences->getUInt(preference_lock_max_keypad_code_count); _maxKeypadCodeCount = _preferences->getUInt(keypadMaxCountPreferenceKey());
_maxTimeControlEntryCount = _preferences->getUInt(preference_lock_max_timecontrol_entry_count); _maxTimeControlEntryCount = _preferences->getUInt(timeControlMaxCountPreferenceKey());
_maxAuthEntryCount = _preferences->getUInt(preference_lock_max_auth_entry_count); _maxAuthEntryCount = _preferences->getUInt(authMaxCountPreferenceKey());
_restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost); _restartBeaconTimeout = _preferences->getInt(preference_restart_ble_beacon_lost);
_nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200); _nrOfRetries = _preferences->getInt(preference_command_nr_of_retries, 200);
_retryDelay = _preferences->getInt(preference_command_retry_delay); _retryDelay = _preferences->getInt(preference_command_retry_delay);
@@ -487,7 +511,7 @@ void NukiWrapper::lockngounlatch()
const bool NukiWrapper::isPinValid() const bool NukiWrapper::isPinValid()
{ {
return _preferences->getInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured) == (int)NukiPinState::Valid; return _preferences->getInt(pinStatusPreferenceKey(), (int)NukiPinState::NotConfigured) == (int)NukiPinState::Valid;
} }
void NukiWrapper::setPin(const uint16_t pin) void NukiWrapper::setPin(const uint16_t pin)
@@ -513,15 +537,15 @@ const uint32_t NukiWrapper::getUltraPin()
void NukiWrapper::unpair() void NukiWrapper::unpair()
{ {
_nukiLock.unPairNuki(); _nukiLock.unPairNuki();
_preferences->remove(preference_lock_log_num); _preferences->remove(lockLogPreferenceKey());
Preferences nukiBlePref; Preferences nukiBlePref;
nukiBlePref.begin("NukiHub", false); nukiBlePref.begin(_deviceName.c_str(), false);
nukiBlePref.clear(); nukiBlePref.clear();
nukiBlePref.end(); nukiBlePref.end();
_deviceId->assignNewId(); _deviceId->assignNewId();
if (!_forceId) if (!_forceId)
{ {
_preferences->remove(preference_nuki_id_lock); _preferences->remove(nukiIdPreferenceKey());
} }
_paired = false; _paired = false;
} }
@@ -643,7 +667,7 @@ void NukiWrapper::updateConfig()
if(_nukiConfigValid) if(_nukiConfigValid)
{ {
if(!_forceId && (_preferences->getUInt(preference_nuki_id_lock, 0) == 0 || _retryConfigCount == 10)) if(!_forceId && (_preferences->getUInt(nukiIdPreferenceKey(), 0) == 0 || _retryConfigCount == 10))
{ {
char uidString[20]; char uidString[20];
itoa(_nukiConfig.nukiId, uidString, 16); itoa(_nukiConfig.nukiId, uidString, 16);
@@ -652,10 +676,10 @@ void NukiWrapper::updateConfig()
Log->print(" / "); Log->print(" / ");
Log->print(uidString); Log->print(uidString);
Log->println(")"); Log->println(")");
_preferences->putUInt(preference_nuki_id_lock, _nukiConfig.nukiId); _preferences->putUInt(nukiIdPreferenceKey(), _nukiConfig.nukiId);
} }
if(_preferences->getUInt(preference_nuki_id_lock, 0) == _nukiConfig.nukiId) if(_preferences->getUInt(nukiIdPreferenceKey(), 0) == _nukiConfig.nukiId)
{ {
_hasKeypad = _nukiConfig.hasKeypad == 1 || _nukiConfig.hasKeypadV2 == 1; _hasKeypad = _nukiConfig.hasKeypad == 1 || _nukiConfig.hasKeypadV2 == 1;
_firmwareVersion = std::to_string(_nukiConfig.firmwareVersion[0]) + "." + std::to_string(_nukiConfig.firmwareVersion[1]) + "." + std::to_string(_nukiConfig.firmwareVersion[2]); _firmwareVersion = std::to_string(_nukiConfig.firmwareVersion[0]) + "." + std::to_string(_nukiConfig.firmwareVersion[1]) + "." + std::to_string(_nukiConfig.firmwareVersion[2]);
@@ -673,7 +697,7 @@ void NukiWrapper::updateConfig()
updateAuth(false); updateAuth(false);
} }
const int pinStatus = _preferences->getInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured); const int pinStatus = _preferences->getInt(pinStatusPreferenceKey(), (int)NukiPinState::NotConfigured);
Nuki::CmdResult result = (Nuki::CmdResult)-1; Nuki::CmdResult result = (Nuki::CmdResult)-1;
@@ -687,7 +711,7 @@ void NukiWrapper::updateConfig()
Log->println("Nuki Lock PIN is invalid or not set"); Log->println("Nuki Lock PIN is invalid or not set");
if(pinStatus != 2) if(pinStatus != 2)
{ {
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::Invalid); _preferences->putInt(pinStatusPreferenceKey(), (int)NukiPinState::Invalid);
} }
} }
else else
@@ -695,7 +719,7 @@ void NukiWrapper::updateConfig()
Log->println("Nuki Lock PIN is valid"); Log->println("Nuki Lock PIN is valid");
if(pinStatus != 1) if(pinStatus != 1)
{ {
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::Valid); _preferences->putInt(pinStatusPreferenceKey(), (int)NukiPinState::Valid);
} }
} }
} }
@@ -1024,7 +1048,7 @@ void NukiWrapper::updateKeypad(bool retrieved)
if(keypadCount > _maxKeypadCodeCount) if(keypadCount > _maxKeypadCodeCount)
{ {
_maxKeypadCodeCount = keypadCount; _maxKeypadCodeCount = keypadCount;
_preferences->putUInt(preference_lock_max_keypad_code_count, _maxKeypadCodeCount); _preferences->putUInt(keypadMaxCountPreferenceKey(), _maxKeypadCodeCount);
} }
_network->publishKeypad(entries, _maxKeypadCodeCount); _network->publishKeypad(entries, _maxKeypadCodeCount);
@@ -1094,7 +1118,7 @@ void NukiWrapper::updateTimeControl(bool retrieved)
if(timeControlCount > _maxTimeControlEntryCount) if(timeControlCount > _maxTimeControlEntryCount)
{ {
_maxTimeControlEntryCount = timeControlCount; _maxTimeControlEntryCount = timeControlCount;
_preferences->putUInt(preference_lock_max_timecontrol_entry_count, _maxTimeControlEntryCount); _preferences->putUInt(timeControlMaxCountPreferenceKey(), _maxTimeControlEntryCount);
} }
_network->publishTimeControl(timeControlEntries, _maxTimeControlEntryCount); _network->publishTimeControl(timeControlEntries, _maxTimeControlEntryCount);
@@ -1161,7 +1185,7 @@ void NukiWrapper::updateAuth(bool retrieved)
if(authCount > _maxAuthEntryCount) if(authCount > _maxAuthEntryCount)
{ {
_maxAuthEntryCount = authCount; _maxAuthEntryCount = authCount;
_preferences->putUInt(preference_lock_max_auth_entry_count, _maxAuthEntryCount); _preferences->putUInt(authMaxCountPreferenceKey(), _maxAuthEntryCount);
} }
_network->publishAuth(authEntries, _maxAuthEntryCount); _network->publishAuth(authEntries, _maxAuthEntryCount);
@@ -1182,11 +1206,6 @@ void NukiWrapper::postponeBleWatchdog()
_disableBleWatchdogTs = espMillis() + 15000; _disableBleWatchdogTs = espMillis() + 15000;
} }
LockActionResult NukiWrapper::onLockActionReceivedCallback(const char *value)
{
return nukiInst->onLockActionReceived(value);
}
LockActionResult NukiWrapper::onLockActionReceived(const char *value) LockActionResult NukiWrapper::onLockActionReceived(const char *value)
{ {
NukiLock::LockAction action; NukiLock::LockAction action;
@@ -1218,7 +1237,7 @@ LockActionResult NukiWrapper::onLockActionReceived(const char *value)
{ {
if(!_nukiOfficial->getOffConnected()) if(!_nukiOfficial->getOffConnected())
{ {
nukiInst->_nextLockAction = action; _nextLockAction = action;
} }
else else
{ {
@@ -1233,7 +1252,7 @@ LockActionResult NukiWrapper::onLockActionReceived(const char *value)
} }
else else
{ {
nukiInst->_nextLockAction = action; _nextLockAction = action;
} }
} }
return LockActionResult::Success; return LockActionResult::Success;
@@ -1242,16 +1261,6 @@ LockActionResult NukiWrapper::onLockActionReceived(const char *value)
return LockActionResult::AccessDenied; return LockActionResult::AccessDenied;
} }
void NukiWrapper::onOfficialUpdateReceivedCallback(const char *topic, const char *value)
{
nukiInst->onOfficialUpdateReceived(topic, value);
}
void NukiWrapper::onConfigUpdateReceivedCallback(const char *value)
{
nukiInst->onConfigUpdateReceived(value);
}
const bool NukiWrapper::offConnected() const bool NukiWrapper::offConnected()
{ {
return _nukiOfficial->getOffConnected(); return _nukiOfficial->getOffConnected();
@@ -2285,32 +2294,6 @@ void NukiWrapper::onConfigUpdateReceived(const char *value)
return; return;
} }
void NukiWrapper::onKeypadCommandReceivedCallback(const char *command, const uint &id, const String &name, const String &code, const int& enabled)
{
nukiInst->onKeypadCommandReceived(command, id, name, code, enabled);
}
void NukiWrapper::onKeypadJsonCommandReceivedCallback(const char *value)
{
nukiInst->onKeypadJsonCommandReceived(value);
}
void NukiWrapper::onTimeControlCommandReceivedCallback(const char *value)
{
nukiInst->onTimeControlCommandReceived(value);
}
void NukiWrapper::onAuthCommandReceivedCallback(const char *value)
{
nukiInst->onAuthCommandReceived(value);
}
void NukiWrapper::gpioActionCallback(const GpioAction &action, const int& pin)
{
nukiInst->onGpioActionReceived(action, pin);
}
void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin) void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
{ {
switch(action) switch(action)
@@ -2318,7 +2301,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::Lock: case GpioAction::Lock:
if(!_nukiOfficial->getOffConnected()) if(!_nukiOfficial->getOffConnected())
{ {
nukiInst->lock(); lock();
} }
else else
{ {
@@ -2330,7 +2313,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::Unlock: case GpioAction::Unlock:
if(!_nukiOfficial->getOffConnected()) if(!_nukiOfficial->getOffConnected())
{ {
nukiInst->unlock(); unlock();
} }
else else
{ {
@@ -2342,7 +2325,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::Unlatch: case GpioAction::Unlatch:
if(!_nukiOfficial->getOffConnected()) if(!_nukiOfficial->getOffConnected())
{ {
nukiInst->unlatch(); unlatch();
} }
else else
{ {
@@ -2354,7 +2337,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::LockNgo: case GpioAction::LockNgo:
if(!_nukiOfficial->getOffConnected()) if(!_nukiOfficial->getOffConnected())
{ {
nukiInst->lockngo(); lockngo();
} }
else else
{ {
@@ -2366,7 +2349,7 @@ void NukiWrapper::onGpioActionReceived(const GpioAction &action, const int &pin)
case GpioAction::LockNgoUnlatch: case GpioAction::LockNgoUnlatch:
if(!_nukiOfficial->getOffConnected()) if(!_nukiOfficial->getOffConnected())
{ {
nukiInst->lockngounlatch(); lockngounlatch();
} }
else else
{ {
@@ -3997,7 +3980,7 @@ void NukiWrapper::notify(Nuki::EventType eventType)
} }
else if(eventType == Nuki::EventType::ERROR_BAD_PIN) else if(eventType == Nuki::EventType::ERROR_BAD_PIN)
{ {
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::Invalid); _preferences->putInt(pinStatusPreferenceKey(), (int)NukiPinState::Invalid);
} }
else if(eventType == Nuki::EventType::BLE_ERROR_ON_DISCONNECT) else if(eventType == Nuki::EventType::BLE_ERROR_ON_DISCONNECT)
{ {
@@ -4116,4 +4099,82 @@ void NukiWrapper::updateTime()
{ {
return _nukiLock.updateTime(nukiTime); return _nukiLock.updateTime(nukiTime);
}); });
} }
const char* NukiWrapper::nukiIdPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_nuki_id_lock_2;
case 3:
return preference_nuki_id_lock_3;
default:
return preference_nuki_id_lock;
}
}
const char* NukiWrapper::pinStatusPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_pin_status_2;
case 3:
return preference_lock_pin_status_3;
default:
return preference_lock_pin_status;
}
}
const char* NukiWrapper::keypadMaxCountPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_max_keypad_code_count_2;
case 3:
return preference_lock_max_keypad_code_count_3;
default:
return preference_lock_max_keypad_code_count;
}
}
const char* NukiWrapper::timeControlMaxCountPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_max_timecontrol_entry_count_2;
case 3:
return preference_lock_max_timecontrol_entry_count_3;
default:
return preference_lock_max_timecontrol_entry_count;
}
}
const char* NukiWrapper::authMaxCountPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_max_auth_entry_count_2;
case 3:
return preference_lock_max_auth_entry_count_3;
default:
return preference_lock_max_auth_entry_count;
}
}
const char* NukiWrapper::lockLogPreferenceKey() const
{
switch(_lockSlot)
{
case 2:
return preference_lock_log_num_2;
case 3:
return preference_lock_log_num_3;
default:
return preference_lock_log_num;
}
}

View File

@@ -15,7 +15,7 @@
class NukiWrapper : public Nuki::SmartlockEventHandler class NukiWrapper : public Nuki::SmartlockEventHandler
{ {
public: public:
NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize); NukiWrapper(const std::string& deviceName, NukiDeviceId* deviceId, BleScanner::Scanner* scanner, NukiNetworkLock* network, NukiOfficial* nukiOfficial, Gpio* gpio, Preferences* preferences, char* buffer, size_t bufferSize, uint8_t lockSlot = 1);
virtual ~NukiWrapper(); virtual ~NukiWrapper();
void initialize(); void initialize();
@@ -53,14 +53,6 @@ public:
void notify(Nuki::EventType eventType) override; void notify(Nuki::EventType eventType) override;
private: private:
static LockActionResult onLockActionReceivedCallback(const char* value);
static void onOfficialUpdateReceivedCallback(const char* topic, const char* value);
static void onConfigUpdateReceivedCallback(const char* value);
static void onKeypadCommandReceivedCallback(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
static void onKeypadJsonCommandReceivedCallback(const char* value);
static void onTimeControlCommandReceivedCallback(const char* value);
static void onAuthCommandReceivedCallback(const char* value);
static void gpioActionCallback(const GpioAction& action, const int& pin);
LockActionResult onLockActionReceived(const char* value); LockActionResult onLockActionReceived(const char* value);
void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled); void onKeypadCommandReceived(const char* command, const uint& id, const String& name, const String& code, const int& enabled);
void onOfficialUpdateReceived(const char* topic, const char* value); void onOfficialUpdateReceived(const char* topic, const char* value);
@@ -85,6 +77,12 @@ private:
void readConfig(); void readConfig();
void readAdvancedConfig(); void readAdvancedConfig();
const char* nukiIdPreferenceKey() const;
const char* pinStatusPreferenceKey() const;
const char* keypadMaxCountPreferenceKey() const;
const char* timeControlMaxCountPreferenceKey() const;
const char* authMaxCountPreferenceKey() const;
const char* lockLogPreferenceKey() const;
std::string _deviceName; std::string _deviceName;
NukiDeviceId* _deviceId = nullptr; NukiDeviceId* _deviceId = nullptr;
@@ -167,6 +165,7 @@ private:
std::string _firmwareVersion = ""; std::string _firmwareVersion = "";
std::string _hardwareVersion = ""; std::string _hardwareVersion = "";
volatile NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff; volatile NukiLock::LockAction _nextLockAction = (NukiLock::LockAction)0xff;
uint8_t _lockSlot = 1;
char* _buffer; char* _buffer;
const size_t _bufferSize; const size_t _bufferSize;

View File

@@ -104,6 +104,8 @@
//REQUIRE SERVICES RELOAD //REQUIRE SERVICES RELOAD
#define preference_lock_enabled (char*)"lockena" #define preference_lock_enabled (char*)"lockena"
#define preference_lock_enabled_2 (char*)"lockena2"
#define preference_lock_enabled_3 (char*)"lockena3"
#define preference_opener_enabled (char*)"openerena" #define preference_opener_enabled (char*)"openerena"
#define preference_debug_connect (char*)"dbgConnect" #define preference_debug_connect (char*)"dbgConnect"
#define preference_debug_communication (char*)"dbgCommu" #define preference_debug_communication (char*)"dbgCommu"
@@ -145,6 +147,9 @@
#define preference_mqtt_user (char*)"mqttuser" #define preference_mqtt_user (char*)"mqttuser"
#define preference_mqtt_password (char*)"mqttpass" #define preference_mqtt_password (char*)"mqttpass"
#define preference_mqtt_lock_path (char*)"mqttpath" #define preference_mqtt_lock_path (char*)"mqttpath"
#define preference_mqtt_lock_name_1 (char*)"mqttlcknm1"
#define preference_mqtt_lock_name_2 (char*)"mqttlcknm2"
#define preference_mqtt_lock_name_3 (char*)"mqttlcknm3"
#define preference_mqtt_hass_enabled (char*)"hassena" #define preference_mqtt_hass_enabled (char*)"hassena"
#define preference_mqtt_hass_discovery (char*)"hassdiscovery" #define preference_mqtt_hass_discovery (char*)"hassdiscovery"
#define preference_disable_non_json (char*)"disnonjson" #define preference_disable_non_json (char*)"disnonjson"
@@ -170,23 +175,37 @@
#define preference_updater_build (char*)"updBuild" #define preference_updater_build (char*)"updBuild"
#define preference_updater_date (char*)"updDate" #define preference_updater_date (char*)"updDate"
#define preference_lock_max_auth_entry_count (char*)"maxauth" #define preference_lock_max_auth_entry_count (char*)"maxauth"
#define preference_lock_max_auth_entry_count_2 (char*)"maxauth2"
#define preference_lock_max_auth_entry_count_3 (char*)"maxauth3"
#define preference_opener_max_auth_entry_count (char*)"opmaxauth" #define preference_opener_max_auth_entry_count (char*)"opmaxauth"
#define preference_started_before (char*)"run" #define preference_started_before (char*)"run"
#define preference_config_version (char*)"confVersion" #define preference_config_version (char*)"confVersion"
#define preference_device_id_lock (char*)"deviceId" #define preference_device_id_lock (char*)"deviceId"
#define preference_device_id_lock_2 (char*)"deviceId2"
#define preference_device_id_lock_3 (char*)"deviceId3"
#define preference_device_id_opener (char*)"deviceIdOp" #define preference_device_id_opener (char*)"deviceIdOp"
#define preference_nuki_id_lock (char*)"nukiId" #define preference_nuki_id_lock (char*)"nukiId"
#define preference_nuki_id_lock_2 (char*)"nukiId2"
#define preference_nuki_id_lock_3 (char*)"nukiId3"
#define preference_nuki_id_opener (char*)"nukidOp" #define preference_nuki_id_opener (char*)"nukidOp"
#define preference_lock_pin_status (char*)"lockpin" #define preference_lock_pin_status (char*)"lockpin"
#define preference_lock_pin_status_2 (char*)"lockpin2"
#define preference_lock_pin_status_3 (char*)"lockpin3"
#define preference_opener_pin_status (char*)"openerpin" #define preference_opener_pin_status (char*)"openerpin"
#define preference_lock_max_keypad_code_count (char*)"maxkpad" #define preference_lock_max_keypad_code_count (char*)"maxkpad"
#define preference_lock_max_keypad_code_count_2 (char*)"maxkpad2"
#define preference_lock_max_keypad_code_count_3 (char*)"maxkpad3"
#define preference_opener_max_keypad_code_count (char*)"opmaxkpad" #define preference_opener_max_keypad_code_count (char*)"opmaxkpad"
#define preference_lock_max_timecontrol_entry_count (char*)"maxtc" #define preference_lock_max_timecontrol_entry_count (char*)"maxtc"
#define preference_lock_max_timecontrol_entry_count_2 (char*)"maxtc2"
#define preference_lock_max_timecontrol_entry_count_3 (char*)"maxtc3"
#define preference_opener_max_timecontrol_entry_count (char*)"opmaxtc" #define preference_opener_max_timecontrol_entry_count (char*)"opmaxtc"
#define preference_latest_version (char*)"latest" #define preference_latest_version (char*)"latest"
#define preference_reset_mqtt_topics (char*)"rstMqtt" #define preference_reset_mqtt_topics (char*)"rstMqtt"
#define preference_nukihub_id (char*)"nukihubId" #define preference_nukihub_id (char*)"nukihubId"
#define preference_lock_log_num (char*)"lckLgNm" #define preference_lock_log_num (char*)"lckLgNm"
#define preference_lock_log_num_2 (char*)"lckLgNm2"
#define preference_lock_log_num_3 (char*)"lckLgNm3"
#define preference_opener_log_num (char*)"opLgNm" #define preference_opener_log_num (char*)"opLgNm"
//OBSOLETE //OBSOLETE
@@ -206,6 +225,8 @@ inline void initPreferences(Preferences* preferences)
preferences->putBool(preference_started_before, true); preferences->putBool(preference_started_before, true);
preferences->putBool(preference_lock_enabled, true); preferences->putBool(preference_lock_enabled, true);
preferences->putBool(preference_lock_enabled_2, false);
preferences->putBool(preference_lock_enabled_3, false);
uint32_t aclPrefs[17] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; uint32_t aclPrefs[17] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs)); preferences->putBytes(preference_acl, (byte*)(&aclPrefs), sizeof(aclPrefs));
uint32_t basicLockConfigAclPrefs[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; uint32_t basicLockConfigAclPrefs[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -218,6 +239,9 @@ inline void initPreferences(Preferences* preferences)
preferences->putBytes(preference_conf_opener_advanced_acl, (byte*)(&advancedOpenerConfigAclPrefs), sizeof(advancedOpenerConfigAclPrefs)); preferences->putBytes(preference_conf_opener_advanced_acl, (byte*)(&advancedOpenerConfigAclPrefs), sizeof(advancedOpenerConfigAclPrefs));
preferences->putString(preference_mqtt_lock_path, "nukihub"); preferences->putString(preference_mqtt_lock_path, "nukihub");
preferences->putString(preference_mqtt_lock_name_1, "lock");
preferences->putString(preference_mqtt_lock_name_2, "lock2");
preferences->putString(preference_mqtt_lock_name_3, "lock3");
preferences->putString(preference_time_server, "pool.ntp.org"); preferences->putString(preference_time_server, "pool.ntp.org");
preferences->putString(preference_cred_duo_host, ""); preferences->putString(preference_cred_duo_host, "");
preferences->putString(preference_cred_duo_ikey, ""); preferences->putString(preference_cred_duo_ikey, "");
@@ -536,11 +560,11 @@ class DebugPreferences
private: private:
std::vector<char*> _keys = std::vector<char*> _keys =
{ {
preference_started_before, preference_config_version, preference_device_id_lock, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_opener, preference_started_before, preference_config_version, preference_device_id_lock, preference_device_id_lock_2, preference_device_id_lock_3, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_lock_2, preference_nuki_id_lock_3, preference_nuki_id_opener,
preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_check_updates, preference_mqtt_broker, preference_mqtt_broker_port, preference_mqtt_user, preference_mqtt_password, preference_mqtt_log_enabled, preference_check_updates,
preference_webserver_enabled, preference_lock_enabled, preference_lock_pin_status, preference_mqtt_lock_path, preference_opener_enabled, preference_opener_pin_status, preference_webserver_enabled, preference_lock_enabled, preference_lock_enabled_2, preference_lock_enabled_3, preference_lock_pin_status, preference_lock_pin_status_2, preference_lock_pin_status_3, preference_mqtt_lock_path, preference_mqtt_lock_name_1, preference_mqtt_lock_name_2, preference_mqtt_lock_name_3, preference_opener_enabled, preference_opener_pin_status,
preference_opener_continuous_mode, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_update_time, preference_time_server, preference_opener_continuous_mode, preference_lock_max_keypad_code_count, preference_lock_max_keypad_code_count_2, preference_lock_max_keypad_code_count_3, preference_opener_max_keypad_code_count, preference_update_time, preference_time_server,
preference_lock_max_timecontrol_entry_count, preference_opener_max_timecontrol_entry_count, preference_enable_bootloop_reset, preference_lock_max_timecontrol_entry_count, preference_lock_max_timecontrol_entry_count_2, preference_lock_max_timecontrol_entry_count_3, preference_opener_max_timecontrol_entry_count, preference_enable_bootloop_reset,
preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url, preference_buffer_size, preference_ip_dhcp_enabled, preference_ip_address, preference_mqtt_hass_discovery, preference_mqtt_hass_cu_url, preference_buffer_size, preference_ip_dhcp_enabled, preference_ip_address,
preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware, preference_http_auth_type, preference_lock_gemini_pin, preference_ip_subnet, preference_ip_gateway, preference_ip_dns_server, preference_network_hardware, preference_http_auth_type, preference_lock_gemini_pin,
preference_rssi_publish_interval, preference_hostname, preference_network_timeout, preference_restart_on_disconnect, preference_hybrid_reboot_on_disconnect, preference_rssi_publish_interval, preference_hostname, preference_network_timeout, preference_restart_on_disconnect, preference_hybrid_reboot_on_disconnect,
@@ -554,7 +578,7 @@ private:
preference_update_from_mqtt, preference_show_secrets, preference_ble_tx_power, preference_webserial_enabled, preference_find_best_rssi, preference_lock_gemini_enabled, preference_update_from_mqtt, preference_show_secrets, preference_ble_tx_power, preference_webserial_enabled, preference_find_best_rssi, preference_lock_gemini_enabled,
preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq, preference_network_custom_mdc, preference_network_custom_clk, preference_network_custom_phy, preference_network_custom_addr, preference_network_custom_irq,
preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi, preference_network_custom_rst, preference_network_custom_cs, preference_network_custom_sck, preference_network_custom_miso, preference_network_custom_mosi,
preference_network_custom_pwr, preference_network_custom_mdio, preference_lock_max_auth_entry_count, preference_opener_max_auth_entry_count, preference_network_custom_pwr, preference_network_custom_mdio, preference_lock_max_auth_entry_count, preference_lock_max_auth_entry_count_2, preference_lock_max_auth_entry_count_3, preference_opener_max_auth_entry_count,
preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_wifi_ssid, preference_wifi_pass, preference_auth_control_enabled, preference_auth_topic_per_entry, preference_auth_info_enabled, preference_auth_max_entries, preference_wifi_ssid, preference_wifi_pass,
preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_mqtt_hass_enabled, preference_hass_device_discovery, preference_retain_gpio, preference_keypad_check_code_enabled, preference_disable_network_not_connected, preference_mqtt_hass_enabled, preference_hass_device_discovery, preference_retain_gpio,
preference_debug_connect, preference_debug_communication, preference_debug_readable_data, preference_debug_hex_data, preference_debug_command, preference_debug_connect, preference_debug_communication, preference_debug_readable_data, preference_debug_hex_data, preference_debug_command,
@@ -567,13 +591,13 @@ private:
}; };
std::vector<char*> _redact = std::vector<char*> _redact =
{ {
preference_mqtt_user, preference_mqtt_password, preference_cred_user, preference_cred_password, preference_nuki_id_lock, preference_nuki_id_opener, preference_wifi_pass, preference_mqtt_user, preference_mqtt_password, preference_cred_user, preference_cred_password, preference_nuki_id_lock, preference_nuki_id_lock_2, preference_nuki_id_lock_3, preference_nuki_id_opener, preference_wifi_pass,
preference_lock_gemini_pin, preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_bypass_proxy, preference_lock_gemini_pin, preference_cred_duo_host, preference_cred_duo_ikey, preference_cred_duo_skey, preference_cred_duo_user, preference_bypass_proxy,
preference_totp_secret, preference_bypass_secret, preference_admin_secret preference_totp_secret, preference_bypass_secret, preference_admin_secret
}; };
std::vector<char*> _boolPrefs = std::vector<char*> _boolPrefs =
{ {
preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_opener_enabled, preference_opener_continuous_mode, preference_started_before, preference_mqtt_log_enabled, preference_check_updates, preference_lock_enabled, preference_lock_enabled_2, preference_lock_enabled_3, preference_opener_enabled, preference_opener_continuous_mode,
preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled, preference_update_time, preference_timecontrol_topic_per_entry, preference_keypad_topic_per_entry, preference_enable_bootloop_reset, preference_webserver_enabled, preference_update_time,
preference_restart_on_disconnect, preference_keypad_control_enabled, preference_keypad_info_enabled, preference_keypad_publish_code, preference_show_secrets, preference_restart_on_disconnect, preference_keypad_control_enabled, preference_keypad_info_enabled, preference_keypad_publish_code, preference_show_secrets,
preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_register_opener_as_app, preference_ip_dhcp_enabled, preference_timecontrol_control_enabled, preference_timecontrol_info_enabled, preference_register_as_app, preference_register_opener_as_app, preference_ip_dhcp_enabled,
@@ -594,8 +618,8 @@ private:
std::vector<char*> _intPrefs = std::vector<char*> _intPrefs =
{ {
preference_config_version, preference_mqtt_broker_port, preference_command_retry_delay, preference_query_interval_hybrid_lockstate, preference_config_version, preference_mqtt_broker_port, preference_command_retry_delay, preference_query_interval_hybrid_lockstate,
preference_lock_pin_status, preference_opener_pin_status, preference_lock_max_keypad_code_count, preference_opener_max_keypad_code_count, preference_lock_pin_status, preference_lock_pin_status_2, preference_lock_pin_status_3, preference_opener_pin_status, preference_lock_max_keypad_code_count, preference_lock_max_keypad_code_count_2, preference_lock_max_keypad_code_count_3, preference_opener_max_keypad_code_count,
preference_lock_max_timecontrol_entry_count, preference_opener_max_timecontrol_entry_count, preference_buffer_size, preference_network_hardware, preference_lock_max_timecontrol_entry_count, preference_lock_max_timecontrol_entry_count_2, preference_lock_max_timecontrol_entry_count_3, preference_opener_max_timecontrol_entry_count, preference_buffer_size, preference_network_hardware,
preference_rssi_publish_interval, preference_network_timeout, preference_restart_ble_beacon_lost, preference_query_interval_lockstate, preference_rssi_publish_interval, preference_network_timeout, preference_restart_ble_beacon_lost, preference_query_interval_lockstate,
preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_command_nr_of_retries, preference_query_interval_configuration, preference_query_interval_battery, preference_query_interval_keypad, preference_command_nr_of_retries,
preference_task_size_network, preference_task_size_nuki, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries, preference_task_size_network, preference_task_size_nuki, preference_authlog_max_entries, preference_keypad_max_entries, preference_timecontrol_max_entries,
@@ -608,7 +632,7 @@ private:
}; };
std::vector<char*> _uintPrefs = std::vector<char*> _uintPrefs =
{ {
preference_device_id_lock, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_opener preference_device_id_lock, preference_device_id_lock_2, preference_device_id_lock_3, preference_device_id_opener, preference_nuki_id_lock, preference_nuki_id_lock_2, preference_nuki_id_lock_3, preference_nuki_id_opener
}; };
std::vector<char*> _uint64Prefs = std::vector<char*> _uint64Prefs =
{ {
@@ -643,4 +667,4 @@ public:
{ {
return _uint64Prefs; return _uint64Prefs;
} }
}; };

View File

@@ -24,6 +24,33 @@ extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end"); extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
extern bool timeSynced; extern bool timeSynced;
static bool isValidMqttLockName(const String& value)
{
if(value.length() < 3 || value.length() > 32)
{
return false;
}
for(size_t i = 0; i < value.length(); i++)
{
const char c = value.charAt(i);
if((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-')
{
continue;
}
return false;
}
return true;
}
static String normalizeMqttLockName(String value)
{
value.trim();
value.toLowerCase();
return value;
}
#if defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED) #if defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED)
#include "esp_hosted.h" #include "esp_hosted.h"
static esp_hosted_coprocessor_fwver_t slave_version_struct = { static esp_hosted_coprocessor_fwver_t slave_version_struct = {
@@ -43,6 +70,8 @@ static esp_hosted_coprocessor_fwver_t host_version_struct = {
#include <NetworkClientSecure.h> #include <NetworkClientSecure.h>
#include "ArduinoJson.h" #include "ArduinoJson.h"
#include <freertos/queue.h> #include <freertos/queue.h>
extern NukiWrapper* nuki2;
extern NukiWrapper* nuki3;
typedef struct typedef struct
{ {
@@ -1341,11 +1370,19 @@ void WebCfgServer::initialize()
} }
else if (value == "unpairlock") else if (value == "unpairlock")
{ {
return processUnpair(request, resp, false); return processUnpair(request, resp, false, 1);
}
else if (value == "unpairlock2")
{
return processUnpair(request, resp, false, 2);
}
else if (value == "unpairlock3")
{
return processUnpair(request, resp, false, 3);
} }
else if (value == "unpairopener") else if (value == "unpairopener")
{ {
return processUnpair(request, resp, true); return processUnpair(request, resp, true, 1);
} }
else if (value == "factoryreset") else if (value == "factoryreset")
{ {
@@ -2894,6 +2931,43 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
restartServicesReconnect = true; restartServicesReconnect = true;
} }
} }
else if(key == "MQTTLOCKNAME1" || key == "MQTTLOCKNAME2" || key == "MQTTLOCKNAME3")
{
value = normalizeMqttLockName(value);
if(!isValidMqttLockName(value))
{
message = "Invalid MQTT lock name. Allowed: a-z, 0-9, _ and -, length 3..32.";
return false;
}
const char* prefKey = preference_mqtt_lock_name_1;
if(key == "MQTTLOCKNAME2")
{
prefKey = preference_mqtt_lock_name_2;
}
else if(key == "MQTTLOCKNAME3")
{
prefKey = preference_mqtt_lock_name_3;
}
if(_preferences->getString(prefKey, "") != value)
{
const String n1 = (key == "MQTTLOCKNAME1") ? value : normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_1, "lock"));
const String n2 = (key == "MQTTLOCKNAME2") ? value : normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_2, "lock2"));
const String n3 = (key == "MQTTLOCKNAME3") ? value : normalizeMqttLockName(_preferences->getString(preference_mqtt_lock_name_3, "lock3"));
if(n1 == n2 || n1 == n3 || n2 == n3)
{
message = "Duplicate MQTT lock name is not allowed.";
return false;
}
_preferences->putString(prefKey, value);
Log->print("Setting changed: ");
Log->println(key);
restartServicesReconnect = true;
}
}
else if(key == "MQTTCA") else if(key == "MQTTCA")
{ {
if (!SPIFFS.begin(true)) if (!SPIFFS.begin(true))
@@ -4669,6 +4743,26 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
restartServicesReconnect = true; restartServicesReconnect = true;
} }
} }
else if(key == "LOCKENA2")
{
if(_preferences->getBool(preference_lock_enabled_2, false) != (value == "1"))
{
_preferences->putBool(preference_lock_enabled_2, (value == "1"));
Log->print("Setting changed: ");
Log->println(key);
restartServicesReconnect = true;
}
}
else if(key == "LOCKENA3")
{
if(_preferences->getBool(preference_lock_enabled_3, false) != (value == "1"))
{
_preferences->putBool(preference_lock_enabled_3, (value == "1"));
Log->print("Setting changed: ");
Log->println(key);
restartServicesReconnect = true;
}
}
else if(key == "GEMINIENA") else if(key == "GEMINIENA")
{ {
if(_preferences->getBool(preference_lock_gemini_enabled, false) != (value == "1")) if(_preferences->getBool(preference_lock_gemini_enabled, false) != (value == "1"))
@@ -5051,6 +5145,15 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
_preferences->putBool(preference_mfa_reconfigure, true); _preferences->putBool(preference_mfa_reconfigure, true);
} }
if(_preferences->getBool(preference_lock_enabled_2, false) || _preferences->getBool(preference_lock_enabled_3, false))
{
if(_preferences->getBool(preference_opener_enabled, false))
{
_preferences->putBool(preference_opener_enabled, false);
restartServicesReconnect = true;
}
}
if(configChanged) if(configChanged)
{ {
message = "Configuration saved, reboot required to apply some settings"; message = "Configuration saved, reboot required to apply some settings";
@@ -5106,7 +5209,12 @@ bool WebCfgServer::processImport(PsychicRequest *request, PsychicResponse* resp,
return configChanged; return configChanged;
} }
_importExport->importJson(doc); JsonDocument importResult = _importExport->importJson(doc);
if(!importResult["_import_error"].isNull())
{
message = importResult["_import_error"].as<String>();
return false;
}
configChanged = true; configChanged = true;
} }
@@ -5427,6 +5535,32 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
response.print("</table>"); response.print("</table>");
response.print("<br><button type=\"submit\">OK</button></form>"); response.print("<br><button type=\"submit\">OK</button></form>");
} }
if(_preferences->getBool(preference_lock_enabled_2, false))
{
response.print("<br><br><h3>Unpair Nuki Lock Slot 2</h3>");
response.print("<form class=\"adapt\" method=\"post\" action=\"/post\">");
response.print("<input type=\"hidden\" name=\"page\" value=\"unpairlock2\">");
response.print("<table>");
String message = "Type ";
message.concat(_confirmCode);
message.concat(" to confirm unpair");
printInputField(&response, "CONFIRMTOKEN", message.c_str(), "", 10, "");
response.print("</table>");
response.print("<br><button type=\"submit\">OK</button></form>");
}
if(_preferences->getBool(preference_lock_enabled_3, false))
{
response.print("<br><br><h3>Unpair Nuki Lock Slot 3</h3>");
response.print("<form class=\"adapt\" method=\"post\" action=\"/post\">");
response.print("<input type=\"hidden\" name=\"page\" value=\"unpairlock3\">");
response.print("<table>");
String message = "Type ";
message.concat(_confirmCode);
message.concat(" to confirm unpair");
printInputField(&response, "CONFIRMTOKEN", message.c_str(), "", 10, "");
response.print("</table>");
response.print("<br><button type=\"submit\">OK</button></form>");
}
if(_nukiOpener != nullptr) if(_nukiOpener != nullptr)
{ {
response.print("<br><br><h3>Unpair Nuki Opener</h3>"); response.print("<br><br><h3>Unpair Nuki Opener</h3>");
@@ -5516,6 +5650,9 @@ esp_err_t WebCfgServer::buildMqttConfigHtml(PsychicRequest *request, PsychicResp
printInputField(&response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true); printInputField(&response, "MQTTUSER", "MQTT User (# to clear)", _preferences->getString(preference_mqtt_user).c_str(), 30, "", false, true);
printInputField(&response, "MQTTPASS", "MQTT Password", "*", 40, "", true, true); printInputField(&response, "MQTTPASS", "MQTT Password", "*", 40, "", true, true);
printInputField(&response, "MQTTPATH", "MQTT Nuki Hub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, ""); printInputField(&response, "MQTTPATH", "MQTT Nuki Hub Path", _preferences->getString(preference_mqtt_lock_path).c_str(), 180, "");
printInputField(&response, "MQTTLOCKNAME1", "Lock Slot 1 MQTT Name (a-z,0-9,_,-; 3-32)", _preferences->getString(preference_mqtt_lock_name_1, "lock").c_str(), 32, "");
printInputField(&response, "MQTTLOCKNAME2", "Lock Slot 2 MQTT Name (a-z,0-9,_,-; 3-32)", _preferences->getString(preference_mqtt_lock_name_2, "lock2").c_str(), 32, "");
printInputField(&response, "MQTTLOCKNAME3", "Lock Slot 3 MQTT Name (a-z,0-9,_,-; 3-32)", _preferences->getString(preference_mqtt_lock_name_3, "lock3").c_str(), 32, "");
printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), "chkHass"); printCheckBox(&response, "ENHADISC", "Enable Home Assistant auto discovery", _preferences->getBool(preference_mqtt_hass_enabled), "chkHass");
response.print("</table><br>"); response.print("</table><br>");
@@ -6160,6 +6297,8 @@ esp_err_t WebCfgServer::buildNukiConfigHtml(PsychicRequest *request, PsychicResp
response.print("<h3>Basic Nuki Configuration</h3>"); response.print("<h3>Basic Nuki Configuration</h3>");
response.print("<table>"); response.print("<table>");
printCheckBox(&response, "LOCKENA", "Nuki Lock enabled", _preferences->getBool(preference_lock_enabled, true), ""); printCheckBox(&response, "LOCKENA", "Nuki Lock enabled", _preferences->getBool(preference_lock_enabled, true), "");
printCheckBox(&response, "LOCKENA2", "Nuki Lock slot 2 enabled", _preferences->getBool(preference_lock_enabled_2, false), "");
printCheckBox(&response, "LOCKENA3", "Nuki Lock slot 3 enabled", _preferences->getBool(preference_lock_enabled_3, false), "");
printCheckBox(&response, "GEMINIENA", "Nuki Smartlock Ultra/Go/5th gen enabled", _preferences->getBool(preference_lock_gemini_enabled, false), ""); printCheckBox(&response, "GEMINIENA", "Nuki Smartlock Ultra/Go/5th gen enabled", _preferences->getBool(preference_lock_gemini_enabled, false), "");
printCheckBox(&response, "OPENA", "Nuki Opener enabled", _preferences->getBool(preference_opener_enabled, false), ""); printCheckBox(&response, "OPENA", "Nuki Opener enabled", _preferences->getBool(preference_opener_enabled, false), "");
response.print("</table><br>"); response.print("</table><br>");
@@ -6865,6 +7004,42 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
} }
} }
response.print("\n\n------------ NUKI LOCK SLOT 2 ------------");
response.print("\nEnabled: ");
response.print(_preferences->getBool(preference_lock_enabled_2, false) ? "Yes" : "No");
response.print("\nMQTT name: ");
response.print(_preferences->getString(preference_mqtt_lock_name_2, "lock2"));
response.print("\nNuki Hub device ID: ");
response.print(_preferences->getUInt(preference_device_id_lock_2, 0));
response.print("\nNuki device ID: ");
response.print(_preferences->getUInt(preference_nuki_id_lock_2, 0) > 0 ? "***" : "Not set");
response.print("\nValid PIN set: ");
response.print(pinStateToString((NukiPinState)_preferences->getInt(preference_lock_pin_status_2, (int)NukiPinState::NotConfigured)));
response.print("\nKeypad highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_keypad_code_count_2, 0));
response.print("\nTimecontrol highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_timecontrol_entry_count_2, 0));
response.print("\nAuthorizations highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_auth_entry_count_2, 0));
response.print("\n\n------------ NUKI LOCK SLOT 3 ------------");
response.print("\nEnabled: ");
response.print(_preferences->getBool(preference_lock_enabled_3, false) ? "Yes" : "No");
response.print("\nMQTT name: ");
response.print(_preferences->getString(preference_mqtt_lock_name_3, "lock3"));
response.print("\nNuki Hub device ID: ");
response.print(_preferences->getUInt(preference_device_id_lock_3, 0));
response.print("\nNuki device ID: ");
response.print(_preferences->getUInt(preference_nuki_id_lock_3, 0) > 0 ? "***" : "Not set");
response.print("\nValid PIN set: ");
response.print(pinStateToString((NukiPinState)_preferences->getInt(preference_lock_pin_status_3, (int)NukiPinState::NotConfigured)));
response.print("\nKeypad highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_keypad_code_count_3, 0));
response.print("\nTimecontrol highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_timecontrol_entry_count_3, 0));
response.print("\nAuthorizations highest entries count: ");
response.print(_preferences->getInt(preference_lock_max_auth_entry_count_3, 0));
response.print("\n\n------------ NUKI OPENER ------------"); response.print("\n\n------------ NUKI OPENER ------------");
if(_nukiOpener == nullptr || !_preferences->getBool(preference_opener_enabled, false)) if(_nukiOpener == nullptr || !_preferences->getBool(preference_opener_enabled, false))
{ {
@@ -7050,7 +7225,7 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
return response.endSend(); return response.endSend();
} }
esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener) esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener, uint8_t lockSlot)
{ {
String value = ""; String value = "";
if(request->hasParam("CONFIRMTOKEN")) if(request->hasParam("CONFIRMTOKEN"))
@@ -7067,12 +7242,74 @@ esp_err_t WebCfgServer::processUnpair(PsychicRequest *request, PsychicResponse*
return buildConfirmHtml(request, resp, "Confirm code is invalid.", 3, true); return buildConfirmHtml(request, resp, "Confirm code is invalid.", 3, true);
} }
esp_err_t res = buildConfirmHtml(request, resp, opener ? "Unpairing Nuki Opener." : "Unpairing Nuki Lock.", 3, true); String confirmMsg = opener ? "Unpairing Nuki Opener." : "Unpairing Nuki Lock.";
if(!opener && lockSlot == 2)
if(!opener && _nuki != nullptr)
{ {
_nuki->unpair(); confirmMsg = "Unpairing Nuki Lock Slot 2.";
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured); }
else if(!opener && lockSlot == 3)
{
confirmMsg = "Unpairing Nuki Lock Slot 3.";
}
esp_err_t res = buildConfirmHtml(request, resp, confirmMsg, 3, true);
if(!opener)
{
auto clearLockSlot = [&](const uint8_t slot)
{
const char* prefPinKey = preference_lock_pin_status;
const char* prefNukiIdKey = preference_nuki_id_lock;
const char* prefDeviceIdKey = preference_device_id_lock;
const char* prefLogKey = preference_lock_log_num;
const char* bleNs = "NukiHub";
if(slot == 2)
{
prefPinKey = preference_lock_pin_status_2;
prefNukiIdKey = preference_nuki_id_lock_2;
prefDeviceIdKey = preference_device_id_lock_2;
prefLogKey = preference_lock_log_num_2;
bleNs = "NukiHub2";
}
else if(slot == 3)
{
prefPinKey = preference_lock_pin_status_3;
prefNukiIdKey = preference_nuki_id_lock_3;
prefDeviceIdKey = preference_device_id_lock_3;
prefLogKey = preference_lock_log_num_3;
bleNs = "NukiHub3";
}
Preferences nukiBlePref;
nukiBlePref.begin(bleNs, false);
nukiBlePref.clear();
nukiBlePref.end();
_preferences->remove(prefLogKey);
_preferences->remove(prefNukiIdKey);
_preferences->putInt(prefPinKey, (int)NukiPinState::NotConfigured);
_preferences->putUInt(prefDeviceIdKey, esp_random());
};
if(lockSlot == 1 && _nuki != nullptr)
{
_nuki->unpair();
_preferences->putInt(preference_lock_pin_status, (int)NukiPinState::NotConfigured);
}
else if(lockSlot == 2 && nuki2 != nullptr)
{
nuki2->unpair();
_preferences->putInt(preference_lock_pin_status_2, (int)NukiPinState::NotConfigured);
}
else if(lockSlot == 3 && nuki3 != nullptr)
{
nuki3->unpair();
_preferences->putInt(preference_lock_pin_status_3, (int)NukiPinState::NotConfigured);
}
else
{
clearLockSlot(lockSlot);
}
} }
if(opener && _nukiOpener != nullptr) if(opener && _nukiOpener != nullptr)
{ {
@@ -7215,6 +7452,14 @@ esp_err_t WebCfgServer::processFactoryReset(PsychicRequest *request, PsychicResp
{ {
_nuki->unpair(); _nuki->unpair();
} }
if(nuki2 != nullptr)
{
nuki2->unpair();
}
if(nuki3 != nullptr)
{
nuki3->unpair();
}
if(_nukiOpener != nullptr) if(_nukiOpener != nullptr)
{ {
_nukiOpener->unpair(); _nukiOpener->unpair();

View File

@@ -75,7 +75,7 @@ private:
#endif #endif
esp_err_t buildInfoHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildInfoHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildCustomNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildCustomNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener); esp_err_t processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener, uint8_t lockSlot = 1);
esp_err_t processUpdate(PsychicRequest *request, PsychicResponse* resp); esp_err_t processUpdate(PsychicRequest *request, PsychicResponse* resp);
esp_err_t processFactoryReset(PsychicRequest *request, PsychicResponse* resp); esp_err_t processFactoryReset(PsychicRequest *request, PsychicResponse* resp);
void printTextarea(PsychicStreamResponse *response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false); void printTextarea(PsychicStreamResponse *response, const char *token, const char *description, const char *value, const size_t& maxLength, const bool& enabled = true, const bool& showLengthRestriction = false);

View File

@@ -52,22 +52,36 @@ bool nuki_hub_https_server_enabled = false;
#include "ImportExport.h" #include "ImportExport.h"
NukiNetworkLock* networkLock = nullptr; NukiNetworkLock* networkLock = nullptr;
NukiNetworkLock* networkLock2 = nullptr;
NukiNetworkLock* networkLock3 = nullptr;
NukiNetworkOpener* networkOpener = nullptr; NukiNetworkOpener* networkOpener = nullptr;
BleScanner::Scanner* bleScanner = nullptr; BleScanner::Scanner* bleScanner = nullptr;
NukiWrapper* nuki = nullptr; NukiWrapper* nuki = nullptr;
NukiWrapper* nuki2 = nullptr;
NukiWrapper* nuki3 = nullptr;
NukiOfficial* nukiOfficial = nullptr; NukiOfficial* nukiOfficial = nullptr;
NukiOfficial* nukiOfficial2 = nullptr;
NukiOfficial* nukiOfficial3 = nullptr;
NukiOpenerWrapper* nukiOpener = nullptr; NukiOpenerWrapper* nukiOpener = nullptr;
NukiDeviceId* deviceIdLock = nullptr; NukiDeviceId* deviceIdLock = nullptr;
NukiDeviceId* deviceIdLock2 = nullptr;
NukiDeviceId* deviceIdLock3 = nullptr;
NukiDeviceId* deviceIdOpener = nullptr; NukiDeviceId* deviceIdOpener = nullptr;
Gpio* gpio = nullptr; Gpio* gpio = nullptr;
SerialReader* serialReader = nullptr; SerialReader* serialReader = nullptr;
bool bleDone = false; bool bleDone = false;
bool lockEnabled = false; bool lockEnabled = false;
bool lockEnabled2 = false;
bool lockEnabled3 = false;
bool openerEnabled = false; bool openerEnabled = false;
bool wifiConnected = false; bool wifiConnected = false;
bool rebootLock = false; bool rebootLock = false;
bool rebootLock2 = false;
bool rebootLock3 = false;
uint8_t lockRestartControllerCount = 0; uint8_t lockRestartControllerCount = 0;
uint8_t lockRestartControllerCount2 = 0;
uint8_t lockRestartControllerCount3 = 0;
uint8_t openerRestartControllerCount = 0; uint8_t openerRestartControllerCount = 0;
char16_t buffer_size = CHAR_BUFFER_SIZE; char16_t buffer_size = CHAR_BUFFER_SIZE;
@@ -703,14 +717,33 @@ void startNuki(bool lock)
{ {
if (lock) if (lock)
{ {
nukiOfficial = new NukiOfficial(preferences); if(lockEnabled)
networkLock = new NukiNetworkLock(network, nukiOfficial, preferences, CharBuffer::get(), buffer_size);
if(!disableNetwork)
{ {
networkLock->initialize(); nukiOfficial = new NukiOfficial(preferences);
networkLock = new NukiNetworkLock(network, nukiOfficial, preferences, CharBuffer::get(), buffer_size, 1);
if(!disableNetwork)
{
networkLock->initialize();
}
}
if(lockEnabled2)
{
nukiOfficial2 = new NukiOfficial(preferences);
networkLock2 = new NukiNetworkLock(network, nukiOfficial2, preferences, CharBuffer::get(), buffer_size, 2);
if(!disableNetwork)
{
networkLock2->initialize();
}
}
if(lockEnabled3)
{
nukiOfficial3 = new NukiOfficial(preferences);
networkLock3 = new NukiNetworkLock(network, nukiOfficial3, preferences, CharBuffer::get(), buffer_size, 3);
if(!disableNetwork)
{
networkLock3->initialize();
}
} }
lockStarted = true; lockStarted = true;
} }
else else
@@ -730,7 +763,14 @@ void restartServices(bool reconnect)
{ {
bleDone = false; bleDone = false;
lockEnabled = preferences->getBool(preference_lock_enabled); lockEnabled = preferences->getBool(preference_lock_enabled);
lockEnabled2 = preferences->getBool(preference_lock_enabled_2, false);
lockEnabled3 = preferences->getBool(preference_lock_enabled_3, false);
openerEnabled = preferences->getBool(preference_opener_enabled); openerEnabled = preferences->getBool(preference_opener_enabled);
if(lockEnabled2 || lockEnabled3)
{
openerEnabled = false;
}
importExport->readSettings(); importExport->readSettings();
network->readSettings(); network->readSettings();
gpio->setPins(); gpio->setPins();
@@ -775,13 +815,25 @@ void restartServices(bool reconnect)
Log->println("Deleting nuki"); Log->println("Deleting nuki");
delete nuki; delete nuki;
nuki = nullptr; nuki = nullptr;
delete nuki2;
nuki2 = nullptr;
delete nuki3;
nuki3 = nullptr;
if (reconnect) if (reconnect)
{ {
lockStarted = false; lockStarted = false;
delete networkLock; delete networkLock;
networkLock = nullptr; networkLock = nullptr;
delete networkLock2;
networkLock2 = nullptr;
delete networkLock3;
networkLock3 = nullptr;
delete nukiOfficial; delete nukiOfficial;
nukiOfficial = nullptr; nukiOfficial = nullptr;
delete nukiOfficial2;
nukiOfficial2 = nullptr;
delete nukiOfficial3;
nukiOfficial3 = nullptr;
} }
Log->println("Deleting nuki done"); Log->println("Deleting nuki done");
} }
@@ -830,7 +882,7 @@ void restartServices(bool reconnect)
} }
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
if(lockEnabled || openerEnabled) if(lockEnabled || lockEnabled2 || lockEnabled3 || openerEnabled)
{ {
#if defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED) #if defined(CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE) || defined(CONFIG_ESP_WIFI_REMOTE_ENABLED)
hostedInitBLE(); hostedInitBLE();
@@ -843,7 +895,7 @@ void restartServices(bool reconnect)
Log->println("Restarting BLE Scanner done"); Log->println("Restarting BLE Scanner done");
} }
if(lockEnabled) if(lockEnabled || lockEnabled2 || lockEnabled3)
{ {
Log->println("Restarting Nuki lock"); Log->println("Restarting Nuki lock");
@@ -852,9 +904,24 @@ void restartServices(bool reconnect)
startNuki(true); startNuki(true);
} }
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size); if(lockEnabled && networkLock != nullptr && nukiOfficial != nullptr)
nuki->initialize(); {
bleScanner->whitelist(nuki->getBleAddress()); nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size, 1);
nuki->initialize();
bleScanner->whitelist(nuki->getBleAddress());
}
if(lockEnabled2 && networkLock2 != nullptr && nukiOfficial2 != nullptr)
{
nuki2 = new NukiWrapper("NukiHub2", deviceIdLock2, bleScanner, networkLock2, nukiOfficial2, gpio, preferences, CharBuffer::get(), buffer_size, 2);
nuki2->initialize();
bleScanner->whitelist(nuki2->getBleAddress());
}
if(lockEnabled3 && networkLock3 != nullptr && nukiOfficial3 != nullptr)
{
nuki3 = new NukiWrapper("NukiHub3", deviceIdLock3, bleScanner, networkLock3, nukiOfficial3, gpio, preferences, CharBuffer::get(), buffer_size, 3);
nuki3->initialize();
bleScanner->whitelist(nuki3->getBleAddress());
}
Log->println("Restarting Nuki lock done"); Log->println("Restarting Nuki lock done");
} }
@@ -1014,7 +1081,18 @@ void networkTask(void *pvParameters)
if(connected && lockStarted) if(connected && lockStarted)
{ {
rebootLock = networkLock->update(); if(networkLock != nullptr)
{
rebootLock = networkLock->update();
}
if(networkLock2 != nullptr)
{
rebootLock2 = networkLock2->update();
}
if(networkLock3 != nullptr)
{
rebootLock3 = networkLock3->update();
}
if (esp_task_wdt_status(NULL) == ESP_OK) if (esp_task_wdt_status(NULL) == ESP_OK)
{ {
esp_task_wdt_reset(); esp_task_wdt_reset();
@@ -1148,7 +1226,10 @@ void nukiTask(void *pvParameters)
vTaskDelay(20 / portTICK_PERIOD_MS); vTaskDelay(20 / portTICK_PERIOD_MS);
} }
bool needsPairing = (lockStarted && !nuki->isPaired()) || (openerStarted && !nukiOpener->isPaired()); bool needsPairing = ((lockStarted && nuki != nullptr && !nuki->isPaired()) ||
(lockStarted && nuki2 != nullptr && !nuki2->isPaired()) ||
(lockStarted && nuki3 != nullptr && !nuki3->isPaired()) ||
(openerStarted && !nukiOpener->isPaired()));
if (needsPairing) if (needsPairing)
{ {
@@ -1161,10 +1242,18 @@ void nukiTask(void *pvParameters)
else if (!whiteListed) else if (!whiteListed)
{ {
whiteListed = true; whiteListed = true;
if(lockEnabled) if(lockEnabled && nuki != nullptr)
{ {
bleScanner->whitelist(nuki->getBleAddress()); bleScanner->whitelist(nuki->getBleAddress());
} }
if(lockEnabled2 && nuki2 != nullptr)
{
bleScanner->whitelist(nuki2->getBleAddress());
}
if(lockEnabled3 && nuki3 != nullptr)
{
bleScanner->whitelist(nuki3->getBleAddress());
}
if(openerEnabled) if(openerEnabled)
{ {
bleScanner->whitelist(nukiOpener->getBleAddress()); bleScanner->whitelist(nukiOpener->getBleAddress());
@@ -1173,35 +1262,104 @@ void nukiTask(void *pvParameters)
if(lockStarted) if(lockStarted)
{ {
if (nuki->restartController() > 0) if(nuki != nullptr)
{ {
if (lockRestartControllerCount > 3) if (nuki->restartController() > 0)
{ {
if (nuki->restartController() == 1) if (lockRestartControllerCount > 3)
{ {
restartEsp(RestartReason::BLEError); if (nuki->restartController() == 1)
{
restartEsp(RestartReason::BLEError);
}
else if (nuki->restartController() == 2)
{
restartEsp(RestartReason::BLEBeaconWatchdog);
}
} }
else if (nuki->restartController() == 2) else
{ {
restartEsp(RestartReason::BLEBeaconWatchdog); lockRestartControllerCount += 1;
restartServices(false);
continue;
} }
} }
else else
{ {
lockRestartControllerCount += 1; if (lockRestartControllerCount > 0 && nuki->hasConnected())
restartServices(false); {
continue; lockRestartControllerCount = 0;
}
nuki->update(rebootLock);
rebootLock = false;
} }
} }
else if(nuki2 != nullptr)
{ {
if (lockRestartControllerCount > 0 && nuki->hasConnected()) if (nuki2->restartController() > 0)
{ {
lockRestartControllerCount = 0; if (lockRestartControllerCount2 > 3)
{
if (nuki2->restartController() == 1)
{
restartEsp(RestartReason::BLEError);
}
else if (nuki2->restartController() == 2)
{
restartEsp(RestartReason::BLEBeaconWatchdog);
}
}
else
{
lockRestartControllerCount2 += 1;
restartServices(false);
continue;
}
} }
else
{
if (lockRestartControllerCount2 > 0 && nuki2->hasConnected())
{
lockRestartControllerCount2 = 0;
}
nuki->update(rebootLock); nuki2->update(rebootLock2);
rebootLock = false; rebootLock2 = false;
}
}
if(nuki3 != nullptr)
{
if (nuki3->restartController() > 0)
{
if (lockRestartControllerCount3 > 3)
{
if (nuki3->restartController() == 1)
{
restartEsp(RestartReason::BLEError);
}
else if (nuki3->restartController() == 2)
{
restartEsp(RestartReason::BLEBeaconWatchdog);
}
}
else
{
lockRestartControllerCount3 += 1;
restartServices(false);
continue;
}
}
else
{
if (lockRestartControllerCount3 > 0 && nuki3->hasConnected())
{
lockRestartControllerCount3 = 0;
}
nuki3->update(rebootLock3);
rebootLock3 = false;
}
} }
} }
if(openerStarted) if(openerStarted)
@@ -1400,7 +1558,7 @@ void setupTasks(bool ota)
xTaskCreatePinnedToCore(networkTask, "ntw", preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), NULL, 3, &networkTaskHandle, (espCores > 1) ? 1 : 0); xTaskCreatePinnedToCore(networkTask, "ntw", preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), NULL, 3, &networkTaskHandle, (espCores > 1) ? 1 : 0);
} }
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
if(!network->isApOpen() && (lockEnabled || openerEnabled)) if(!network->isApOpen() && (lockEnabled || lockEnabled2 || lockEnabled3 || openerEnabled))
{ {
xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 0); xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 0);
} }
@@ -1702,6 +1860,8 @@ void setup()
uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener); uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener);
deviceIdLock = new NukiDeviceId(preferences, preference_device_id_lock); deviceIdLock = new NukiDeviceId(preferences, preference_device_id_lock);
deviceIdLock2 = new NukiDeviceId(preferences, preference_device_id_lock_2);
deviceIdLock3 = new NukiDeviceId(preferences, preference_device_id_lock_3);
deviceIdOpener = new NukiDeviceId(preferences, preference_device_id_opener); deviceIdOpener = new NukiDeviceId(preferences, preference_device_id_opener);
if(deviceIdLock->get() != 0 && devIdOpener == 0) if(deviceIdLock->get() != 0 && devIdOpener == 0)
@@ -1723,20 +1883,29 @@ void setup()
network->initialize(); network->initialize();
lockEnabled = preferences->getBool(preference_lock_enabled); lockEnabled = preferences->getBool(preference_lock_enabled);
lockEnabled2 = preferences->getBool(preference_lock_enabled_2, false);
lockEnabled3 = preferences->getBool(preference_lock_enabled_3, false);
openerEnabled = preferences->getBool(preference_opener_enabled); openerEnabled = preferences->getBool(preference_opener_enabled);
if(lockEnabled2 || lockEnabled3)
{
openerEnabled = false;
}
if(network->isApOpen()) if(network->isApOpen())
{ {
forceEnableWebServer = true; forceEnableWebServer = true;
doOta = false; doOta = false;
lockEnabled = false; lockEnabled = false;
lockEnabled2 = false;
lockEnabled3 = false;
openerEnabled = false; openerEnabled = false;
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
serialReader = new SerialReader(importExport, network); serialReader = new SerialReader(importExport, network);
#endif #endif
} }
if(lockEnabled || openerEnabled) if(lockEnabled || lockEnabled2 || lockEnabled3 || openerEnabled)
{ {
bleScanner = new BleScanner::Scanner(); bleScanner = new BleScanner::Scanner();
// Scan interval and window according to Nuki recommendations: // Scan interval and window according to Nuki recommendations:
@@ -1746,13 +1915,27 @@ void setup()
bleScannerStarted = true; bleScannerStarted = true;
} }
Log->println(lockEnabled ? F("Nuki Lock enabled") : F("Nuki Lock disabled")); const bool anyLockEnabled = lockEnabled || lockEnabled2 || lockEnabled3;
if(lockEnabled) Log->println(anyLockEnabled ? F("Nuki Lock enabled") : F("Nuki Lock disabled"));
if(anyLockEnabled)
{ {
startNuki(true); startNuki(true);
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size); if(lockEnabled && networkLock != nullptr && nukiOfficial != nullptr)
nuki->initialize(); {
nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, nukiOfficial, gpio, preferences, CharBuffer::get(), buffer_size, 1);
nuki->initialize();
}
if(lockEnabled2 && networkLock2 != nullptr && nukiOfficial2 != nullptr)
{
nuki2 = new NukiWrapper("NukiHub2", deviceIdLock2, bleScanner, networkLock2, nukiOfficial2, gpio, preferences, CharBuffer::get(), buffer_size, 2);
nuki2->initialize();
}
if(lockEnabled3 && networkLock3 != nullptr && nukiOfficial3 != nullptr)
{
nuki3 = new NukiWrapper("NukiHub3", deviceIdLock3, bleScanner, networkLock3, nukiOfficial3, gpio, preferences, CharBuffer::get(), buffer_size, 3);
nuki3->initialize();
}
} }
Log->println(openerEnabled ? F("Nuki Opener enabled") : F("Nuki Opener disabled")); Log->println(openerEnabled ? F("Nuki Opener enabled") : F("Nuki Opener disabled"));

View File

@@ -1,363 +1,344 @@
0x2f, 0x2a, 0x0d, 0x0a, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68, 0x2f, 0x2a, 0x0a, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68, 0x74,
0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x73, 0x63, 0x61, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x73, 0x63, 0x61, 0x70,
0x70, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x0d, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x20,
0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x6e, 0x2e, 0x6a, 0x73, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x6e, 0x2e, 0x6a, 0x73, 0x64, 0x65,
0x64, 0x65, 0x6c, 0x69, 0x76, 0x72, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x70, 0x6d, 0x2f, 0x40, 0x6c, 0x69, 0x76, 0x72, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x70, 0x6d, 0x2f, 0x40, 0x65, 0x78,
0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x63, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x63, 0x73, 0x73,
0x73, 0x73, 0x40, 0x31, 0x2e, 0x31, 0x2e, 0x32, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x6d, 0x69, 0x6e, 0x40, 0x31, 0x2e, 0x31, 0x2e, 0x32, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x6d, 0x69, 0x6e, 0x2e, 0x63,
0x2e, 0x63, 0x73, 0x73, 0x0d, 0x0a, 0x2a, 0x2f, 0x0d, 0x0a, 0x0d, 0x0a, 0x2f, 0x2a, 0x0d, 0x0a, 0x73, 0x73, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x55, 0x73, 0x61,
0x20, 0x2a, 0x20, 0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x67, 0x65, 0x3a, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x20, 0x2f,
0x6d, 0x70, 0x61, 0x63, 0x74, 0x20, 0x2f, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x66, 0x79, 0x20, 0x74, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x66, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x64,
0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6e, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x0a,
0x79, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x20, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x2d, 0x6c, 0x69, 0x6e, 0x65,
0x6f, 0x6e, 0x65, 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x72, 0x63, 0x2f, 0x57,
0x74, 0x6f, 0x20, 0x73, 0x72, 0x63, 0x2f, 0x57, 0x65, 0x62, 0x43, 0x66, 0x67, 0x53, 0x65, 0x72, 0x65, 0x62, 0x43, 0x66, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x73, 0x74,
0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x20, 0x61, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x20, 0x61, 0x73, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x63,
0x73, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x63, 0x73, 0x73, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x73, 0x73, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f,
0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x6d, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x20, 0x75, 0x70, 0x6f, 0x6e, 0x20, 0x73, 0x20, 0x75, 0x70, 0x6f, 0x6e, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3a, 0x29, 0x0d, 0x0a, 0x2a, 0x2f, 0x0d, 0x3a, 0x29, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0a, 0x20,
0x0a, 0x0d, 0x0a, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x73, 0x3a, 0x20, 0x27, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x2d, 0x61, 0x70, 0x70, 0x6c,
0x27, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x2d, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2d, 0x73, 0x65, 0x2d, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x42, 0x6c, 0x69, 0x6e, 0x6b, 0x4d, 0x61,
0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x42, 0x6c, 0x69, 0x6e, 0x6b, 0x4d, 0x61, 0x63, 0x53, 0x79, 0x63, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x46, 0x6f, 0x6e, 0x74, 0x2c, 0x27, 0x53, 0x65, 0x67,
0x73, 0x74, 0x65, 0x6d, 0x46, 0x6f, 0x6e, 0x74, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x27, 0x2c, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x6f, 0x2c, 0x4f, 0x78,
0x55, 0x49, 0x27, 0x2c, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x6f, 0x2c, 0x4f, 0x78, 0x79, 0x67, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x2c, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2c, 0x43, 0x61, 0x6e, 0x74,
0x6e, 0x2c, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2c, 0x43, 0x61, 0x6e, 0x74, 0x61, 0x72, 0x65, 0x61, 0x72, 0x65, 0x6c, 0x6c, 0x2c, 0x27, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x53, 0x61, 0x6e, 0x73,
0x6c, 0x6c, 0x2c, 0x27, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x27, 0x2c, 0x27, 0x27, 0x2c, 0x27, 0x48, 0x65, 0x6c, 0x76, 0x65, 0x74, 0x69, 0x63, 0x61, 0x20, 0x4e, 0x65, 0x75,
0x48, 0x65, 0x6c, 0x76, 0x65, 0x74, 0x69, 0x63, 0x61, 0x20, 0x4e, 0x65, 0x75, 0x65, 0x27, 0x2c, 0x65, 0x27, 0x2c, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x2c, 0x27, 0x41,
0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x2c, 0x27, 0x41, 0x70, 0x70, 0x6c, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69,
0x65, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69, 0x27, 0x2c, 0x27, 0x27, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x45, 0x6d, 0x6f, 0x6a,
0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69, 0x27, 0x2c, 0x69, 0x27, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x53, 0x79, 0x6d,
0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x62, 0x6f, 0x6c, 0x27, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66,
0x27, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x6f, 0x6e, 0x74, 0x2d, 0x6d, 0x6f, 0x6e, 0x6f, 0x3a, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c,
0x74, 0x2d, 0x6d, 0x6f, 0x6e, 0x6f, 0x3a, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x61, 0x73, 0x61, 0x73, 0x2c, 0x6d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x2c, 0x27, 0x55, 0x62, 0x75, 0x6e, 0x74,
0x2c, 0x6d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x2c, 0x27, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x75, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x4c, 0x69, 0x62, 0x65, 0x72, 0x61, 0x74,
0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x4c, 0x69, 0x62, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x69, 0x6f, 0x6e, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x43, 0x6f, 0x75, 0x72, 0x69,
0x6e, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x65, 0x72, 0x20, 0x4e, 0x65, 0x77, 0x27, 0x2c, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x2c,
0x20, 0x4e, 0x65, 0x77, 0x27, 0x2c, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x2c, 0x6d, 0x6f, 0x6d, 0x6f, 0x6e, 0x6f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x6e, 0x6f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0a,
0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20, 0x23,
0x31, 0x61, 0x31, 0x61, 0x31, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x31, 0x61, 0x31, 0x61, 0x31, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63,
0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x66, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x66, 0x36, 0x66,
0x36, 0x66, 0x38, 0x66, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x38, 0x66, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x65, 0x35, 0x65, 0x37, 0x65, 0x62, 0x3b, 0x0d, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x65, 0x35, 0x65, 0x37, 0x65, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37,
0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x30, 0x66, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b,
0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x33, 0x36, 0x36, 0x64, 0x36, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x33, 0x36, 0x36, 0x64, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66,
0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31,
0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x66, 0x66, 0x65, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x66, 0x66, 0x65, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x30, 0x63, 0x34, 0x30,
0x78, 0x3a, 0x20, 0x23, 0x30, 0x63, 0x34, 0x30, 0x34, 0x37, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x34, 0x37, 0x0a, 0x7d, 0x0a, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x28, 0x70, 0x72, 0x65,
0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x28, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x73, 0x2d, 0x66, 0x65, 0x72, 0x73, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2d, 0x73, 0x63, 0x68, 0x65, 0x6d,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2d, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x3a, 0x20, 0x64, 0x61, 0x65, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3a,
0x72, 0x6b, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20,
0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20, 0x3a, 0x20, 0x23, 0x65, 0x65, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x23, 0x65, 0x65, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b,
0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x32, 0x3a, 0x20, 0x23, 0x31, 0x31, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x31, 0x31, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x32, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x32, 0x32,
0x32, 0x32, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x33, 0x32, 0x39, 0x31, 0x66, 0x66, 0x3b, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x33, 0x32, 0x39, 0x31, 0x66, 0x66, 0x3b, 0x0a, 0x20,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32,
0x6b, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23,
0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e,
0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x32, 0x38, 0x63, 0x61, 0x3b,
0x32, 0x38, 0x63, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63,
0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x0d, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2a, 0x20, 0x7b, 0x7d, 0x0a, 0x0a, 0x2a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x3b, 0x6e, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x67, 0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6d, 0x67, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6d, 0x67, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x74, 0x2c, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x70, 0x2c, 0x74, 0x61, 0x62, 0x6c, 0x65,
0x2c, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x70, 0x2c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2c, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x75, 0x6c, 0x20, 0x7b, 0x0a, 0x20,
0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x75, 0x6c, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x62, 0x75, 0x74, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e,
0x74, 0x6f, 0x6e, 0x2c, 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x2c, 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61,
0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66,
0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x29, 0x0d, 0x0a, 0x7d, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x62, 0x6f, 0x64,
0x0d, 0x0a, 0x0d, 0x0a, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x37, 0x35, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20,
0x37, 0x35, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x72, 0x65, 0x6d, 0x3b,
0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x36, 0x75, 0x73, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x78, 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b,
0x77, 0x2d, 0x78, 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3a,
0x20, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3a, 0x20, 0x6e, 0x6f, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65,
0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x3a, 0x20, 0x61, 0x6e, 0x79, 0x77,
0x6c, 0x6f, 0x77, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x3a, 0x20, 0x61, 0x6e, 0x79, 0x77, 0x68, 0x65, 0x68, 0x65, 0x72, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x72, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62,
0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x67, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20,
0x20, 0x31, 0x2e, 0x30, 0x33, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x31, 0x2e, 0x30, 0x33, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e,
0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x0d, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x0a, 0x7d, 0x0a,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x3a, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x0a, 0x3a, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20,
0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76,
0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20,
0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x29,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x2c, 0x68, 0x32, 0x2c, 0x68, 0x33, 0x2c,
0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x2e, 0x38,
0x37, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x2c, 0x68,
0x32, 0x2c, 0x68, 0x33, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31,
0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d,
0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x32, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a,
0x20, 0x38, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65,
0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f,
0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d,
0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68,
0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d,
0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x2e, 0x33, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x2e, 0x32, 0x35, 0x72, 0x65, 0x6d,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x32, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x38, 0x35,
0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x33, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31,
0x2e, 0x35, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x34, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x31, 0x2e, 0x32, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x68, 0x35, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x68, 0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x69, 0x7a, 0x65, 0x3a, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31,
0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61,
0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29, 0x20, 0x21, 0x69, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61,
0x62, 0x62, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f,
0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61, 0x62,
0x62, 0x72, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c, 0x62, 0x75, 0x74, 0x74,
0x6f, 0x6e, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65,
0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78,
0x20, 0x31, 0x32, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f,
0x77, 0x72, 0x61, 0x70, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74,
0x78, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a,
0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d,
0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62,
0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61,
0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65,
0x73, 0x65, 0x74, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74,
0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
0x3a, 0x20, 0x2e, 0x35, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f,
0x72, 0x3a, 0x20, 0x6e, 0x6f, 0x74, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x0d, 0x0a,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63,
0x75, 0x73, 0x2c, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72,
0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x66, 0x6f,
0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62,
0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x66,
0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d,
0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a,
0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64,
0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6c, 0x61,
0x70, 0x73, 0x65, 0x3a, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x2c, 0x74, 0x68, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73,
0x6f, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x33, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61,
0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x0d,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x68, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d,
0x0a, 0x74, 0x72, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x65, 0x76,
0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x62, 0x67, 0x2d, 0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x65, 0x78, 0x74,
0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x2c, 0x74,
0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70,
0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f,
0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61,
0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x2c,
0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x68, 0x32, 0x2c, 0x68, 0x33, 0x2c, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0a,
0x6f, 0x78, 0x2d, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f, 0x70, 0x3a,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x2c, 0x68,
0x3a, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x0d, 0x0a, 0x7d, 0x0d, 0x32, 0x2c, 0x68, 0x33, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x0a, 0x0d, 0x0a, 0x69, 0x6d, 0x67, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29,
0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f,
0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x3e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x32, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x38, 0x70,
0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f,
0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20,
0x64, 0x3e, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0a, 0x7d,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x0a, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x3e, 0x2e, 0x33, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x2e, 0x32,
0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x32, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x38, 0x35,
0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x33, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x67, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x35, 0x72,
0x72, 0x65, 0x64, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x34, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x32, 0x35, 0x72, 0x65,
0x20, 0x28, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x35, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x70, 0x78, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a,
0x74, 0x20, 0x74, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x68, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0d, 0x0a, 0x20, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76,
0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x74, 0x65, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x0a, 0x7d, 0x0a,
0x78, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x0a, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5d, 0x2c, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c,
0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x6b, 0x2d, 0x32, 0x29, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b,
0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x0a, 0x7d, 0x0a, 0x0a, 0x61, 0x62, 0x62, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x20, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x7d, 0x0a, 0x0a, 0x61,
0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x62, 0x72, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x7d, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c,
0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x3a, 0x68, 0x61, 0x73, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x29, 0x6e, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x73, 0x65, 0x74, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0d, 0x0a, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20,
0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69,
0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61,
0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78, 0x3b,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a,
0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f,
0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70,
0x6c, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x77, 0x72, 0x61, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x6c, 0x61, 0x73, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0a,
0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x70, 0x78, 0x0d, 0x0a, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b,
0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x76, 0x20, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x61,
0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x5d, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x6c, 0x69, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75,
0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x3a, 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x5b, 0x64,
0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x36, 0x72, 0x65, 0x6d, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3b, 0x0a,
0x20, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x2e, 0x35, 0x3b,
0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x6e, 0x6f, 0x74,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x62, 0x75, 0x74,
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66,
0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65,
0x6c, 0x65, 0x66, 0x74, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74,
0x20, 0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x68, 0x6f,
0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x68, 0x6f,
0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x32, 0x73, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73,
0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a,
0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x2d, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2c, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x20, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20,
0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c,
0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x72, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3a, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3b,
0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x64, 0x2c, 0x74, 0x68, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29,
0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x70, 0x6f, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64,
0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x68,
0x6c, 0x6c, 0x20, 0x2e, 0x34, 0x35, 0x73, 0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e,
0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x61, 0x63, 0x74, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32,
0x69, 0x76, 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x72, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c,
0x64, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61,
0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x78, 0x74,
0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77,
0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x61,
0x72, 0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70,
0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x3a, 0x20,
0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69,
0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78,
0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6d, 0x67, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a,
0x0a, 0x74, 0x64, 0x3e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x64, 0x3e, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72,
0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d,
0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67,
0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a,
0x74, 0x64, 0x3e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x65, 0x64,
0x0a, 0x7d, 0x0a, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20,
0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x70, 0x78, 0x29, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x74, 0x65, 0x78, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x2c, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30,
0x30, 0x25, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61,
0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x3a, 0x68, 0x61, 0x73, 0x28, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d,
0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74,
0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63,
0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e,
0x35, 0x65, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x66,
0x69, 0x72, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74,
0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64,
0x3a, 0x6c, 0x61, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x70,
0x3a, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23,
0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77,
0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x70, 0x78, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x7b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f,
0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a,
0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d,
0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x36, 0x72, 0x65, 0x6d,
0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31,
0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69,
0x6e, 0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f,
0x20, 0x6c, 0x65, 0x66, 0x74, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e,
0x74, 0x20, 0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32,
0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29,
0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30,
0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x32, 0x73, 0x20,
0x65, 0x61, 0x73, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20,
0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69,
0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x2c, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x20, 0x35, 0x30, 0x25, 0x2c, 0x72,
0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c,
0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d,
0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a,
0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x68, 0x6f, 0x76,
0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f,
0x75, 0x6e, 0x64, 0x2d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6c, 0x65,
0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x34, 0x35, 0x73, 0x20, 0x65, 0x61, 0x73,
0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x61,
0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x31, 0x35, 0x73, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x31, 0x35, 0x73,
0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x20, 0x65, 0x61, 0x73, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x73, 0x74,
0x6c, 0x69, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d,
0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69,
0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a,
0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61,
0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72,
0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31,
0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, 0x23, 0x66, 0x37, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77,
0x37, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x74, 0x61, 0x6c, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x74, 0x61, 0x6c,
0x69, 0x63, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x63, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x74, 0x64, 0x62, 0x74, 0x6e,
0x64, 0x62, 0x74, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6d,
0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x6e, 0x61, 0x76, 0x65, 0x6e, 0x74,
0x0d, 0x0a, 0x2e, 0x6e, 0x61, 0x76, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x72, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20,
0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x33, 0x37, 0x35, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x33, 0x37, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d,
0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d,

File diff suppressed because it is too large Load Diff