diff --git a/app/FingerLogic.cpp b/app/FingerLogic.cpp index c0ac909..5062c89 100644 --- a/app/FingerLogic.cpp +++ b/app/FingerLogic.cpp @@ -32,7 +32,7 @@ void CFingerLogic::SetState(FingerLogicState state) m_State = state; if(m_State == STATE_READY || m_State == STATE_ERROR) - m_PowerOffTimer.start(false); + m_PowerOffTimer.startOnce(); else { m_PowerOffTimer.stop(); @@ -64,9 +64,9 @@ void CFingerLogic::PowerOn() delayMilliseconds(100); } -void CFingerLogic::OnFingerInterrupt(bool finger) +void CFingerLogic::OnFinger(bool finger) { - debugf("OnFingerInterrupt: %s", finger ? "DOWN" : "UP"); + debugf("OnFinger: %s", finger ? "DOWN" : "UP"); m_Finger = finger; if(finger) @@ -157,6 +157,27 @@ void CFingerLogic::InitFinger_OnReadSystemParameters(CFingerLogic *pThis, Finger debugf("packetLength: %d", param->packetLength); debugf("baudRate: %d", param->baudRate); + if(param->securityLevel != pThis->Main().Settings().m_SecurityLevel) + { + pThis->SetState(STATE_INIT_SETSYSTEM_SECURITYLEVEL); + pThis->FingerPrint().AsyncSetSystemParameter((SetSystemParameterCallback)InitFinger_OnSetSystemSecurityLevel, pThis, FINGERPRINT_SECURITY_REG_ADDR, pThis->Main().Settings().m_SecurityLevel); + } + else + { + pThis->SetState(STATE_INIT_READTEMPLATEMAP); + pThis->FingerPrint().AsyncReadTemplateMap((ReadTemplateMapCallback)InitFinger_OnReadTemplateMap, pThis, 0); + } + } + else + pThis->SetState(STATE_ERROR); +} + +void CFingerLogic::InitFinger_OnSetSystemSecurityLevel(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("InitFinger_OnSetSystemSecurityLevel: (%d) %s", error, errorStr); + + if(error == ERROR_OK) + { pThis->SetState(STATE_INIT_READTEMPLATEMAP); pThis->FingerPrint().AsyncReadTemplateMap((ReadTemplateMapCallback)InitFinger_OnReadTemplateMap, pThis, 0); } @@ -596,3 +617,15 @@ void CFingerLogic::EnrollFinger_OnDownloadCharacteristics(CFingerLogic *pThis, F pThis->SetState(STATE_READY); } + +void CFingerLogic::SetSecurityLevel(int securityLevel) +{ + SetState(STATE_SETSYSTEM_SECURITYLEVEL); + FingerPrint().AsyncSetSystemParameter((SetSystemParameterCallback)SetSecurityLevel_OnSetSystemSecurityLevel, this, FINGERPRINT_SECURITY_REG_ADDR, securityLevel); +} + +void CFingerLogic::SetSecurityLevel_OnSetSystemSecurityLevel(CFingerLogic *pThis, FingerPrintError error, const char *errorStr) +{ + debugf("SetSecurityLevel_OnSetSystemSecurityLevel: (%d) %s", error, errorStr); + pThis->SetState(STATE_READY); +} diff --git a/app/FingerLogic.h b/app/FingerLogic.h index ddc5081..ea08784 100644 --- a/app/FingerLogic.h +++ b/app/FingerLogic.h @@ -11,6 +11,7 @@ enum FingerLogicState STATE_INIT_VERIFYPASSWORD, STATE_INIT_READSYSTEMPARAMETERS, + STATE_INIT_SETSYSTEM_SECURITYLEVEL, STATE_INIT_READTEMPLATEMAP, STATE_INIT_VERIFYTEMPLATES, @@ -33,7 +34,9 @@ enum FingerLogicState STATE_ENROLL_CREATETEMPLATE, STATE_ENROLL_STORETEMPLATE, STATE_ENROLL_LOADTEMPLATE, - STATE_ENROLL_DOWNLOADCHARACTERISTICS + STATE_ENROLL_DOWNLOADCHARACTERISTICS, + + STATE_SETSYSTEM_SECURITYLEVEL }; class CFingerLogic @@ -41,7 +44,7 @@ class CFingerLogic public: CFingerLogic(class CMain *pMain); void Init(CFingerPrint *pFingerPrint); - void OnFingerInterrupt(bool finger); + void OnFinger(bool finger); bool FingerSlot(uint16_t index); bool FingerSlot(uint16_t index, bool value); @@ -50,6 +53,7 @@ public: void InitFinger(); static void InitFinger_OnVerifyPassword(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); static void InitFinger_OnReadSystemParameters(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, CFingerSystemParameters *param); + static void InitFinger_OnSetSystemSecurityLevel(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); static void InitFinger_OnGetTemplates(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); static void InitFinger_OnReadTemplateMap(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, uint8_t *pData, uint16_t dataLen); void InitFinger_VerifyTemplates(); @@ -76,6 +80,9 @@ public: static void EnrollFinger_OnLoadTemplate(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); static void EnrollFinger_OnDownloadCharacteristics(CFingerLogic *pThis, FingerPrintError error, const char *errorStr, int8_t *pChar, uint16_t charLen); + void SetSecurityLevel(int securityLevel); + static void SetSecurityLevel_OnSetSystemSecurityLevel(CFingerLogic *pThis, FingerPrintError error, const char *errorStr); + CMain &Main() { return *m_pMain; } CFingerPrint &FingerPrint() { return *m_pFingerPrint; } diff --git a/app/FingerPrint.h b/app/FingerPrint.h index 2bccf4b..feb9898 100644 --- a/app/FingerPrint.h +++ b/app/FingerPrint.h @@ -69,6 +69,36 @@ enum FingerPrintError ERROR_COMMUNICATIONPORT = 0x1D, // fail to operate the communication port }; +#define FINGERPRINT_BAUD_REG_ADDR 0x4 //!< BAUDRATE register address +#define FINGERPRINT_BAUDRATE_9600 0x1 //!< UART baud 9600 +#define FINGERPRINT_BAUDRATE_19200 0x2 //!< UART baud 19200 +#define FINGERPRINT_BAUDRATE_28800 0x3 //!< UART baud 28800 +#define FINGERPRINT_BAUDRATE_38400 0x4 //!< UART baud 38400 +#define FINGERPRINT_BAUDRATE_48000 0x5 //!< UART baud 48000 +#define FINGERPRINT_BAUDRATE_57600 0x6 //!< UART baud 57600 +#define FINGERPRINT_BAUDRATE_67200 0x7 //!< UART baud 67200 +#define FINGERPRINT_BAUDRATE_76800 0x8 //!< UART baud 76800 +#define FINGERPRINT_BAUDRATE_86400 0x9 //!< UART baud 86400 +#define FINGERPRINT_BAUDRATE_96000 0xA //!< UART baud 96000 +#define FINGERPRINT_BAUDRATE_105600 0xB //!< UART baud 105600 +#define FINGERPRINT_BAUDRATE_115200 0xC //!< UART baud 115200 + +#define FINGERPRINT_SECURITY_REG_ADDR 0x5 //!< Security level register address +// The safety level is 1 The highest rate of false recognition , The rejection +// rate is the lowest . The safety level is 5 The lowest tate of false +// recognition, The rejection rate is the highest . +#define FINGERPRINT_SECURITY_LEVEL_1 0X1 //!< Security level 1 +#define FINGERPRINT_SECURITY_LEVEL_2 0X2 //!< Security level 2 +#define FINGERPRINT_SECURITY_LEVEL_3 0X3 //!< Security level 3 +#define FINGERPRINT_SECURITY_LEVEL_4 0X4 //!< Security level 4 +#define FINGERPRINT_SECURITY_LEVEL_5 0X5 //!< Security level 5 + +#define FINGERPRINT_PACKET_REG_ADDR 0x6 //!< Packet size register address +#define FINGERPRINT_PACKET_SIZE_32 0X0 //!< Packet size is 32 Byte +#define FINGERPRINT_PACKET_SIZE_64 0X1 //!< Packet size is 64 Byte +#define FINGERPRINT_PACKET_SIZE_128 0X2 //!< Packet size is 128 Byte +#define FINGERPRINT_PACKET_SIZE_256 0X3 //!< Packet size is 256 Byte + enum RecvStates { RECV_DONE = 0, @@ -92,6 +122,7 @@ typedef void (CFingerPrint::*RecvCallback)(FingerPrintIdent ident, uint8_t *pDat typedef void (*VerifyPasswordCallback)(void *pUser, FingerPrintError error, const char *errorStr); typedef void (*ReadSystemParametersCallback)(void *pUser, FingerPrintError error, const char *errorStr, CFingerSystemParameters *param); +typedef void (*SetSystemParameterCallback)(void *pUser, FingerPrintError error, const char *errorStr); typedef void (*ReadImageCallback)(void *pUser, FingerPrintError error, const char *errorStr); typedef void (*ConvertImageCallback)(void *pUser, FingerPrintError error, const char *errorStr); typedef void (*SearchTemplateCallback)(void *pUser, FingerPrintError error, const char *errorStr, int16_t position, int16_t score); @@ -112,11 +143,13 @@ public: int VerifyPassword(); int ReadSystemParameters(uint8_t aResponse[17]); + int SetSystemParameter(uint8_t regAddr, uint8_t value); int DeleteTemplate(uint16_t positionStart, uint16_t count); int EmptyDatabase(); int AsyncVerifyPassword(VerifyPasswordCallback fnCallback, void *pUser); int AsyncReadSystemParameters(ReadSystemParametersCallback fnCallback, void *pUser); + int AsyncSetSystemParameter(SetSystemParameterCallback fnCallback, void *pUser, uint8_t regAddr, uint8_t value); int AsyncReadImage(ReadImageCallback fnCallback, void *pUser); int AsyncConvertImage(ConvertImageCallback fnCallback, void *pUser, uint8_t numCharBuffer); int AsyncSearchTemplate(SearchTemplateCallback fnCallback, void *pUser, uint8_t numCharBuffer, uint16_t positionStart, uint16_t numTemplates); @@ -130,6 +163,7 @@ public: private: void OnAsyncVerifyPassword(FingerPrintIdent ident, uint8_t *pData, uint16_t length); void OnAsyncReadSystemParameters(FingerPrintIdent ident, uint8_t *pData, uint16_t length); + void OnAsyncSetSystemParameter(FingerPrintIdent ident, uint8_t *pData, uint16_t length); void OnAsyncReadImage(FingerPrintIdent ident, uint8_t *pData, uint16_t length); void OnAsyncConvertImage(FingerPrintIdent ident, uint8_t *pData, uint16_t length); void OnAsyncSearchTemplate(FingerPrintIdent ident, uint8_t *pData, uint16_t length); diff --git a/app/FingerPrint_API.cpp b/app/FingerPrint_API.cpp index a2ab40b..473fce3 100644 --- a/app/FingerPrint_API.cpp +++ b/app/FingerPrint_API.cpp @@ -50,6 +50,31 @@ int CFingerPrint::ReadSystemParameters(uint8_t aResponse[17]) return error; } +int CFingerPrint::SetSystemParameter(uint8_t regAddr, uint8_t value) +{ + uint8_t aPayload[] = { + COMMAND_SETSYSPARA, + regAddr, + value + }; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + FingerPrintIdent ident; + uint8_t aResponse[1]; + int ret = Recv(&ident, aResponse, sizeof(aResponse)); + m_RecvState = RECV_DROP; + + if(ret < 0) + return ret; + + if(ident != IDENT_ACK) + return ERROR_COMMUNICATION; + + uint8_t error = aResponse[0]; + return error; +} + int CFingerPrint::DeleteTemplate(uint16_t positionStart, uint16_t count) { uint8_t aPayload[] = { diff --git a/app/FingerPrint_AsyncAPI.cpp b/app/FingerPrint_AsyncAPI.cpp index 8fcbe50..40e42b8 100644 --- a/app/FingerPrint_AsyncAPI.cpp +++ b/app/FingerPrint_AsyncAPI.cpp @@ -90,6 +90,40 @@ void CFingerPrint::OnAsyncReadSystemParameters(FingerPrintIdent ident, uint8_t * } +int CFingerPrint::AsyncSetSystemParameter(SetSystemParameterCallback fnCallback, void *pUser, uint8_t regAddr, uint8_t value) +{ + uint8_t aPayload[] = { + COMMAND_SETSYSPARA, + regAddr, + value + }; + + m_fnRecvCallback = &CFingerPrint::OnAsyncSetSystemParameter; + m_fnUserCallback = (void *)fnCallback; + m_pUserData = pUser; + + Write(IDENT_COMMAND, aPayload, sizeof(aPayload)); + + return 0; +} + +void CFingerPrint::OnAsyncSetSystemParameter(FingerPrintIdent ident, uint8_t *pData, uint16_t length) +{ + m_RecvState = RECV_DROP; + + if(ident != IDENT_ACK) + { + ((SetSystemParameterCallback)m_fnUserCallback)(m_pUserData, ERROR_COMMUNICATION, "The received packet is no ack packet!"); + return; + } + + uint8_t error = pData[0]; + const char *errorStr = ExplainFingerError(error); + + ((SetSystemParameterCallback)m_fnUserCallback)(m_pUserData, (FingerPrintError)error, errorStr); +} + + int CFingerPrint::AsyncReadImage(ReadImageCallback fnCallback, void *pUser) { uint8_t aPayload[] = { diff --git a/app/Settings.cpp b/app/Settings.cpp index 4a7fa0d..7044fdc 100644 --- a/app/Settings.cpp +++ b/app/Settings.cpp @@ -11,10 +11,11 @@ CSettings::CSettings() strcpy(m_aPSK, ""); m_DHCP = true; strcpy(m_aHostname, "safeweb"); - m_Address = IPAddress(0, 0, 0, 0); - m_Netmask = IPAddress(0, 0, 0, 0); - m_Gateway = IPAddress(0, 0, 0, 0); + m_Address = IpAddress(0, 0, 0, 0); + m_Netmask = IpAddress(0, 0, 0, 0); + m_Gateway = IpAddress(0, 0, 0, 0); memset(m_aLockCode, 0, sizeof(m_aLockCode)); + m_SecurityLevel = 4; } bool CSettings::Exists() @@ -53,6 +54,11 @@ bool CSettings::Load() m_Netmask = network["netmask"].as(); m_Gateway = network["gateway"].as(); + JsonObject mqtt = doc["mqtt"]; + strncpy(m_aMqttName, mqtt["client_name"], sizeof(m_aMqttName)); + m_MqttURL = Url("mqtt", mqtt["username"], mqtt["password"], mqtt["host"], mqtt["port"]); + m_MqttUnlock = bool(mqtt["unlock"]); + JsonArray lockCode = doc["lock_code"]; uint8_t tmp = min(lockCode.size(), sizeof(m_aLockCode)); for(uint8_t i = 0; i < tmp; i++) @@ -73,6 +79,8 @@ bool CSettings::Load() m_FingerPrints[finger.m_FingerNum] = finger; } + m_SecurityLevel = min(max(int(doc["security_level"]), 1), 5); + delete[] pData; return true; } @@ -93,6 +101,14 @@ void CSettings::Save() network["netmask"] = m_Netmask.toString(); network["gateway"] = m_Gateway.toString(); + JsonObject mqtt = doc.createNestedObject("mqtt"); + mqtt["client_name"] = m_aMqttName; + mqtt["host"] = m_MqttURL.Host; + mqtt["port"] = m_MqttURL.getPort(); + mqtt["username"] = m_MqttURL.User; + mqtt["password"] = m_MqttURL.Password; + mqtt["unlock"] = m_MqttUnlock; + JsonArray lockCode = doc.createNestedArray("lock_code"); for(uint8_t i = 0; i < sizeof(m_aLockCode); i++) lockCode.add(m_aLockCode[i]); @@ -112,6 +128,8 @@ void CSettings::Save() obj["digest"] = String(aHexDigest); } + doc["security_level"] = m_SecurityLevel; + String docString; serializeJsonPretty(doc, docString); fileSetContent(APP_SETTINGS_FILE, docString); diff --git a/app/Settings.h b/app/Settings.h index d5a71fb..c08060e 100644 --- a/app/Settings.h +++ b/app/Settings.h @@ -24,6 +24,10 @@ public: IpAddress m_Netmask; IpAddress m_Gateway; + Url m_MqttURL; + char m_aMqttName[64]; + bool m_MqttUnlock; + uint8_t m_aLockCode[8]; struct CFingerPrint @@ -33,6 +37,8 @@ public: uint8_t m_aDigest[SHA256_SIZE]; }; HashMap m_FingerPrints; + + int m_SecurityLevel; }; #endif diff --git a/app/application.cpp b/app/application.cpp index 7dc4bbb..37b28f9 100644 --- a/app/application.cpp +++ b/app/application.cpp @@ -5,12 +5,32 @@ HardwareSerial Serial1(UART_ID_1); NtpClient ntpClient("at.pool.ntp.org", 3600); -void IRAM_ATTR OnFingerInterrupt() +void IRAM_ATTR OnFingerISR() { + detachInterrupt(FINGER_DETECT_PIN); // LOW = FINGER, HIGH = NO FINGER - bool status = digitalRead(FINGER_DETECT_PIN); + const bool finger = !digitalRead(FINGER_DETECT_PIN); - g_Main.OnFingerInterrupt(!status); + // TODO: disable interrupt and start timer + g_Main.OnFinger(finger); +} + +void IRAM_ATTR OnLockISR() +{ + detachInterrupt(SAFELOCK_DETECT_PIN); + // LOW = UNLOCKED, HIGH = LOCKED + const bool unlocked = !digitalRead(SAFELOCK_DETECT_PIN); + + g_Main.OnLock(unlocked); +} + +void IRAM_ATTR OnDoorISR() +{ + detachInterrupt(DOOR_DETECT_PIN); + // LOW = OPEN, HIGH = CLOSED + const bool opened = digitalRead(DOOR_DETECT_PIN); + + g_Main.OnDoor(opened); } void ready() @@ -18,10 +38,14 @@ void ready() debugf("READY!"); gpio_pin_wakeup_enable(GPIO_ID_PIN(FINGER_DETECT_PIN), GPIO_PIN_INTR_LOLEVEL); + gpio_pin_wakeup_enable(GPIO_ID_PIN(SAFELOCK_DETECT_PIN), GPIO_PIN_INTR_LOLEVEL); + gpio_pin_wakeup_enable(GPIO_ID_PIN(DOOR_DETECT_PIN), GPIO_PIN_INTR_LOLEVEL); g_Main.Init(Serial); - attachInterrupt(FINGER_DETECT_PIN, OnFingerInterrupt, CHANGE); + attachInterrupt(FINGER_DETECT_PIN, OnFingerISR, CHANGE); + attachInterrupt(SAFELOCK_DETECT_PIN, OnLockISR, CHANGE); + attachInterrupt(DOOR_DETECT_PIN, OnDoorISR, CHANGE); } void init() @@ -45,7 +69,7 @@ void init() pinMode(FINGER_DETECT_PIN, INPUT); pinMode(SAFELOCK_DETECT_PIN, INPUT); - pinMode(DOOR_DETECT_PIN, INPUT); + pinMode(DOOR_DETECT_PIN, INPUT_PULLUP); // mount spiffs spiffs_mount(); diff --git a/app/main.cpp b/app/main.cpp index 3fd98fd..93c32f0 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,6 +1,10 @@ #include +#include #include #include +#include +#include +#include #include "utils.h" #include "main.h" @@ -10,17 +14,97 @@ CMain::CMain() : m_FingerLogic(this) { } -static void STAGotIP(IpAddress ip, IpAddress mask, IpAddress gateway) +static void fileUploadMapper(HttpFiles& files) { - debugf("GOTIP - IP: %s, MASK: %s, GW: %s\n", ip.toString().c_str(), mask.toString().c_str(), - gateway.toString().c_str()); + files["firmware"] = new OtaUpgradeStream; } -static void STADisconnect(const String& ssid, MacAddress bssid, WifiDisconnectReason reason) +static int onMessageDelivered(MqttClient& client, mqtt_message_t* message) { + Serial1 << _F("Message with id ") << message->puback.message_id << _F(" and QoS ") << message->puback.qos + << _F(" was delivered successfully.") << endl; + return 0; +} + +// Callback for messages, arrived from MQTT server +static int onMessageReceived(MqttClient& client, mqtt_message_t* message) +{ + Serial1 << _F("Received: ") << MqttBuffer(message->publish.topic_name) << ':' << endl; + Serial1 << '\t' << MqttBuffer(message->publish.content) << endl; + return 0; +} + +void CMain::OnStationGotIP(IpAddress ip, IpAddress mask, IpAddress gateway) +{ + m_StationConnected = true; + debugf("GOTIP - IP: %s, MASK: %s, GW: %s\n", ip.toString().c_str(), mask.toString().c_str(), gateway.toString().c_str()); + StartMqttClient(); +} + +void CMain::OnStationDisconnect(const String& ssid, MacAddress bssid, WifiDisconnectReason reason) +{ + m_StationConnected = false; debugf("DISCONNECT - SSID: %s, REASON: %d", ssid.c_str(), WifiEvents.getDisconnectReasonDesc(reason).c_str()); } +int CMain::MqttOnConnect(MqttClient& client, mqtt_message_t* message) +{ + debugf("MQTT Connected!"); + + MqttSendMessage("safeweb/status", "online"); + + MqttSendDescription(); + return 0; +} + +void CMain::MqttOnDisconnect(TcpClient& client, bool flag) +{ + if(flag == true) + debugf("MQTT Broker Disconnected!!"); + else + debugf("MQTT Broker Unreachable!!"); + + // Restart connection attempt after few seconds + m_MqttTimer.initializeMs(2 * 1000, TimerDelegate(&CMain::StartMqttClient, this)).start(); +} + +void CMain::MqttSendDescription() +{ + if(m_Mqtt.getConnectionState() != eTCS_Connected) { + StartMqttClient(); // Auto reconnect + } +} + +void CMain::MqttSendMessage(const char *topic, const char *msg) +{ + if(m_Mqtt.getConnectionState() != eTCS_Connected) { + StartMqttClient(); // Auto reconnect + } + + m_Mqtt.publish(topic, msg); +} + +void CMain::StartMqttClient() +{ + m_MqttTimer.stop(); + if(!m_StationConnected || !Settings().m_MqttURL.Port || !Settings().m_MqttURL.Host) + return; + + if(!m_Mqtt.setWill("safeweb/status", "offline", MqttClient::getFlags(MQTT_QOS_AT_LEAST_ONCE, MQTT_RETAIN_TRUE))) { + debugf("Unable to mqtt.setWill"); + } + + m_Mqtt.setEventHandler(MQTT_TYPE_PUBACK, onMessageDelivered); + + m_Mqtt.setConnectedHandler(MqttDelegate(&CMain::MqttOnConnect, this)); + + m_Mqtt.setCompleteDelegate(TcpClientCompleteDelegate(&CMain::MqttOnDisconnect, this)); + m_Mqtt.setMessageHandler(onMessageReceived); + + m_Mqtt.connect(Settings().m_MqttURL, Settings().m_aMqttName); +} + + void CMain::Init(HardwareSerial &serial) { m_Settings.Load(); @@ -45,22 +129,32 @@ void CMain::Init(HardwareSerial &serial) WifiAccessPoint.enable(true); } - WifiEvents.onStationGotIP(STAGotIP); - WifiEvents.onStationDisconnect(STADisconnect); + WifiEvents.onStationGotIP(StationGotIPDelegate(&CMain::OnStationGotIP, this)); + WifiEvents.onStationDisconnect(StationDisconnectDelegate(&CMain::OnStationDisconnect, this)); m_FTP.listen(21); m_FTP.addUser(m_Settings.m_aUsername, m_Settings.m_aPassword); + HttpServerSettings settings; + settings.closeOnContentError = false; + settings.keepAliveSeconds = 5; + + m_HttpServer.configure(settings); + m_HttpServer.setBodyParser(MIME_JSON, bodyToStringParser); + m_HttpServer.setBodyParser(MIME_FORM_MULTIPART, formMultipartParser); m_HttpServer.listen(80); - m_HttpServer.setBodyParser("application/json", bodyToStringParser); m_HttpServer.paths.set("/api", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/state", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/dashboard", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint/label", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint/delete", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint/enroll", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/fingerprint/security", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/unlock", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/api/reset", HttpPathDelegate(&CMain::HttpOnApi, this)); + m_HttpServer.paths.set("/upgrade", new HttpMultipartResource(fileUploadMapper, HttpResourceDelegate(&CMain::HttpOnUpload, this))); m_HttpServer.paths.setDefault(HttpPathDelegate(&CMain::HttpOnFile, this)); @@ -124,7 +218,6 @@ void CMain::HttpOnApi(HttpRequest &request, HttpResponse &response) return; } - HttpStatus status = HandleApi(request.method, endpoint, jsonReq, jsonResp); if(status != HTTP_STATUS_OK) { @@ -140,73 +233,96 @@ void CMain::HttpOnApi(HttpRequest &request, HttpResponse &response) HttpStatus CMain::HandleApi(HttpMethod method, String endpoint, JsonDocument &req, JsonDocument &resp) { - String test; - serializeJsonPretty(req, test); - - debugf("request: %s\n", test.c_str()); - - if(endpoint == "dashboard") + if(endpoint == "state" || endpoint == "dashboard") { if(method != HTTP_GET) return HTTP_STATUS_METHOD_NOT_ALLOWED; - JsonObject lock = resp.createNestedObject("lock"); - lock["locked"] = true; + JsonObject state = resp.createNestedObject("state"); + state["unlocked"] = (bool)!digitalRead(SAFELOCK_DETECT_PIN); + state["opened"] = (bool)digitalRead(DOOR_DETECT_PIN); + state["battery"] = (system_adc_read() / 1024.f) / 0.237; - JsonObject door = resp.createNestedObject("door"); - door["closed"] = true; - - JsonObject battery = resp.createNestedObject("battery"); - debugf("adc: %d\n", system_adc_read()); - battery["voltage"] = (system_adc_read() / 1024.f) / 0.23; - battery["color"] = "success"; - if(battery["voltage"] < 3.6f) - battery["color"] = "warning"; - - JsonObject network = resp.createNestedObject("network"); - if(WifiStation.isEnabled()) + if(endpoint == "dashboard") { - network["type"] = "Station"; - network["ssid"] = WifiStation.getSSID(); - network["channel"] = WifiStation.getChannel(); - network["dhcp"] = WifiStation.isEnabledDHCP(); - network["rssi"] = WifiStation.getRssi(); - network["signal"] = Rssi2Quality(WifiStation.getRssi()); - network["address"] = WifiStation.getIP().toString(); - } - else - { - network["type"] = "Access Point"; - network["ssid"] = WifiAccessPoint.getSSID(); - network["channel"] = WifiStation.getChannel(); - network["dhcp"] = true; - network["address"] = WifiAccessPoint.getIP().toString(); + JsonObject network = resp.createNestedObject("network"); + if(WifiStation.isEnabled()) + { + network["type"] = "Station"; + network["ssid"] = WifiStation.getSSID(); + network["channel"] = WifiStation.getChannel(); + network["dhcp"] = WifiStation.isEnabledDHCP(); + network["rssi"] = WifiStation.getRssi(); + network["signal"] = Rssi2Quality(network["rssi"]); + network["address"] = WifiStation.getIP().toString(); + } + else + { + network["type"] = "Access Point"; + network["ssid"] = WifiAccessPoint.getSSID(); + network["channel"] = WifiStation.getChannel(); + network["dhcp"] = true; + network["address"] = WifiAccessPoint.getIP().toString(); + } } } else if(endpoint == "unlock") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; - LockUnlock(); - resp["success"] = "true"; + if(!req.containsKey("unlock")) + return HTTP_STATUS_BAD_REQUEST; + + if(req["unlock"]) + LockUnlock(); + } + else if(endpoint == "reset") + { + if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; + + if(!req.containsKey("reset")) + return HTTP_STATUS_BAD_REQUEST; + + if(req["reset"]) + System.restart(500); } else if(endpoint == "fingerprint") { - if(method != HTTP_GET) return HTTP_STATUS_METHOD_NOT_ALLOWED; - - JsonArray fingerprints = resp.createNestedArray("fingerprints"); - uint16_t tmp = Settings().m_FingerPrints.count(); - for(uint16_t i = 0; i < tmp; i++) + if(method == HTTP_GET) { - JsonObject obj = fingerprints.createNestedObject(); - const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(i); + JsonArray fingerprints = resp.createNestedArray("fingerprints"); + uint16_t tmp = Settings().m_FingerPrints.count(); + for(uint16_t i = 0; i < tmp; i++) + { + JsonObject obj = fingerprints.createNestedObject(); + const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(i); - obj["num"] = finger.m_FingerNum; - obj["label"] = String(finger.m_aLabel); + obj["num"] = finger.m_FingerNum; + obj["label"] = String(finger.m_aLabel); - char aHexDigest[SHA256_SIZE*2+1]; - bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); - obj["digest"] = String(aHexDigest); + char aHexDigest[SHA256_SIZE*2+1]; + bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); + obj["digest"] = String(aHexDigest); + } + resp["securityLevel"] = Settings().m_SecurityLevel; } + else if(method == HTTP_POST) + { + if(!req.containsKey("securityLevel")) + return HTTP_STATUS_BAD_REQUEST; + + int newLevel = req["securityLevel"].as(); + if(newLevel > FINGERPRINT_SECURITY_LEVEL_5 || newLevel < FINGERPRINT_SECURITY_LEVEL_1) + return HTTP_STATUS_BAD_REQUEST; + + if(newLevel != Settings().m_SecurityLevel) + { + Settings().m_SecurityLevel = newLevel; + Settings().Save(); + FingerLogic().SetSecurityLevel(newLevel); + } + } + else + return HTTP_STATUS_METHOD_NOT_ALLOWED; } else if(endpoint == "fingerprint/label") { @@ -224,8 +340,6 @@ HttpStatus CMain::HandleApi(HttpMethod method, String endpoint, JsonDocument &re CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(index); strncpy(finger.m_aLabel, label.c_str(), sizeof(finger.m_aLabel)); Settings().Save(); - - resp["success"] = true; } else if(endpoint == "fingerprint/delete") { @@ -243,8 +357,6 @@ HttpStatus CMain::HandleApi(HttpMethod method, String endpoint, JsonDocument &re FingerPrint().DeleteTemplate(finger.m_FingerNum, 1); Settings().m_FingerPrints.removeAt(index); Settings().Save(); - - resp["success"] = true; } else if(endpoint == "fingerprint/enroll") { @@ -254,7 +366,7 @@ HttpStatus CMain::HandleApi(HttpMethod method, String endpoint, JsonDocument &re { if(req["cancel"].as() && m_Enrolling) { - resp["success"] = FingerLogic().EnrollFinger(false); + FingerLogic().EnrollFinger(false); m_Enrolling = false; m_Enrolled = false; } @@ -336,11 +448,52 @@ void CMain::HttpOnFile(HttpRequest &request, HttpResponse &response) response.sendFile(file); } -void CMain::OnFingerInterrupt(bool finger) +int CMain::HttpOnUpload(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) +{ + if(!HttpAuthorized(request, response)) + return 1; + + ReadWriteStream* file = request.files["firmware"]; + auto otaStream = static_cast(file); + if(otaStream == nullptr) { + debug_e("Something went wrong with the file upload"); + return 1; + } + + if(response.isSuccess() && !otaStream->hasError()) { + // defer the reboot by 1000 milliseconds to give time to the web server to return the response + System.restart(1000); + + response.sendFile("otadone.html"); + response.headers[HTTP_HEADER_CONNECTION] = "close"; + + return 0; + } + + response.code = HTTP_STATUS_BAD_REQUEST; + response.setContentType(MIME_HTML); + String html = toString(otaStream->errorCode); + response.headers[HTTP_HEADER_CONTENT_LENGTH] = html.length(); + response.sendString(html); + + return 0; +} + +void CMain::OnFinger(bool finger) { m_FingerLogic.OnFingerInterrupt(finger); } +void CMain::OnLock(bool unlocked); +{ + +} + +void CMain::OnDoor(bool opened); +{ + +} + void CMain::FingerEnable(bool enable) { const int pin = FINGER_ENABLE_PIN; @@ -443,11 +596,6 @@ void CMain::OnFingerEnrolled(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]) bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); debugf("OnFingerEnrolled: \"%s\"", aHexDigest); - Serial1.printf("(sz: %d) Finger hexdigest: ", sizeof(finger.m_aDigest)); - for(uint8_t i = 0; i < sizeof(finger.m_aDigest); i++) - Serial1.printf("%x", finger.m_aDigest[i]); - Serial1.printf("\n"); - Settings().m_FingerPrints[fingerNum] = finger; Settings().Save(); m_Enrolled = true; diff --git a/app/main.h b/app/main.h index 3e6143a..d7ce458 100644 --- a/app/main.h +++ b/app/main.h @@ -20,7 +20,14 @@ public: CMain(); void Init(HardwareSerial &serial); - void OnFingerInterrupt(bool finger); + void OnStationGotIP(IpAddress ip, IpAddress mask, IpAddress gateway); + void OnStationDisconnect(const String& ssid, MacAddress bssid, WifiDisconnectReason reason); + int MqttOnConnect(MqttClient& client, mqtt_message_t* message); + void MqttOnDisconnect(TcpClient& client, bool flag); + + void OnFinger(bool finger); + void OnLock(bool unlocked); + void OnDoor(bool opened); void FingerEnable(bool enable); @@ -38,9 +45,14 @@ private: bool HttpAuthorized(HttpRequest &request, HttpResponse &response); void HttpOnApi(HttpRequest &request, HttpResponse &response); void HttpOnFile(HttpRequest &request, HttpResponse &response); + int HttpOnUpload(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response); HttpStatus HandleApi(HttpMethod method, String endpoint, JsonDocument &req, JsonDocument &resp); + void StartMqttClient(); + void MqttSendDescription(); + void MqttSendMessage(const char *topic, const char *msg); + private: void LockSendBytes(uint8_t *pBytes, uint8_t len); void LockSendCode(uint8_t code[8]); @@ -51,12 +63,16 @@ private: FtpServer m_FTP; HttpServer m_HttpServer; + MqttClient m_Mqtt; + Timer m_MqttTimer; String m_EnrollMessage; String m_EnrollLabel; CSettings::CFingerPrint m_EnrolledFinger; bool m_Enrolling; bool m_Enrolled; + + bool m_StationConnected; }; extern CMain g_Main; diff --git a/component.mk b/component.mk index dcb4953..cfff8c6 100644 --- a/component.mk +++ b/component.mk @@ -1,4 +1,11 @@ -ARDUINO_LIBRARIES := ArduinoJson6 +ARDUINO_LIBRARIES := ArduinoJson6 MultipartParser OtaUpgrade -HWCONFIG = spiffs +HWCONFIG = spiffs-two-roms SPIFF_FILES = files + +ifndef MAKE_DOCS + +web-upload: spiffs-image-update + $(call WriteFlash,$(SPIFF_START_ADDR)=$(SPIFF_BIN_OUT)) + +endif diff --git a/files/favicon.ico b/files/favicon.ico deleted file mode 100644 index 23ceec5..0000000 Binary files a/files/favicon.ico and /dev/null differ diff --git a/files/favicon.ico.gz b/files/favicon.ico.gz new file mode 100644 index 0000000..140b70d Binary files /dev/null and b/files/favicon.ico.gz differ diff --git a/files/index.html b/files/index.html deleted file mode 100644 index 1c18dbe..0000000 --- a/files/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - Safe control panel - - - - - - - -
- - - - - - - - - - diff --git a/files/index.html.gz b/files/index.html.gz new file mode 100644 index 0000000..a7a989f Binary files /dev/null and b/files/index.html.gz differ diff --git a/files/main.css.gz b/files/main.css.gz index 772ab62..69b0b75 100644 Binary files a/files/main.css.gz and b/files/main.css.gz differ diff --git a/files/main.js.gz b/files/main.js.gz index 8044855..9629bd8 100644 Binary files a/files/main.js.gz and b/files/main.js.gz differ diff --git a/files/otadone.html.gz b/files/otadone.html.gz new file mode 100644 index 0000000..cbe8466 Binary files /dev/null and b/files/otadone.html.gz differ diff --git a/files/settings.conf b/files/settings.conf index 99c40d7..e4ad423 100644 --- a/files/settings.conf +++ b/files/settings.conf @@ -10,6 +10,13 @@ "netmask": "0.0.0.0", "gateway": "0.0.0.0" }, + "mqtt": { + "client_name": "safeweb", + "host": "", + "port": 0, + "username": "safeweb", + "password": "password" + }, "lock_code": [ 0, 1, @@ -21,5 +28,6 @@ 7 ], "fingerprints": [ - ] + ], + "security_level": 4 } \ No newline at end of file diff --git a/files/templates.js.gz b/files/templates.js.gz deleted file mode 100644 index ff7a6ba..0000000 Binary files a/files/templates.js.gz and /dev/null differ diff --git a/files/wifi-sprites.png b/files/wifi-sprites.png.gz similarity index 96% rename from files/wifi-sprites.png rename to files/wifi-sprites.png.gz index 863d0d7..53e078f 100644 Binary files a/files/wifi-sprites.png and b/files/wifi-sprites.png.gz differ