#include #include #include #include #include #include #include #include "utils.h" #include "main.h" CMain g_Main; CMain::CMain() : m_FingerLogic(this) { } static void fileUploadMapper(HttpFiles& files) { files["firmware"] = new OtaUpgradeStream; } 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 if(!m_StationConnected) 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(); //m_Settings.Save(); WifiAccessPoint.enable(false); if(m_Settings.m_aSSID[0]) { debugf("Station: %s", m_Settings.m_aSSID); WifiStation.enable(true); WifiStation.config(m_Settings.m_aSSID, m_Settings.m_aPSK); if(m_Settings.m_DHCP && m_Settings.m_aHostname[0]) WifiStation.setHostname(m_Settings.m_aHostname); if(!m_Settings.m_DHCP && !m_Settings.m_Address.isNull()) WifiStation.setIP(m_Settings.m_Address, m_Settings.m_Netmask, m_Settings.m_Gateway); WifiStation.connect(); } else { debugf("Access Point 'admin': %s", m_Settings.m_aPassword); WifiAccessPoint.config("safeweb", m_Settings.m_aPassword, AUTH_WPA2_PSK); WifiAccessPoint.enable(true); } 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.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)); m_FingerPrint.Init(serial, 0xFFFFFFFF, 0x00000000); m_FingerLogic.Init(&m_FingerPrint); m_LightSleepTimer.initializeMs(60 * 1000, TimerDelegate(&CMain::EnterLightSleep, this)); } static void wakeupCallback() { debugf("Wakeing up @ %lu", millis()); debugf("Wakeing up @ %s", SystemClock.getSystemTimeString().c_str()); wifi_fpm_close(); wifi_set_opmode(STATION_MODE); wifi_station_connect(); system_soft_wdt_feed(); } void CMain::EnterLightSleep() { debugf("Going to sleep @ %lu", millis()); debugf("Going to sleep @ %s", SystemClock.getSystemTimeString().c_str()); wifi_station_disconnect(); wifi_set_opmode(NULL_MODE); wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); wifi_fpm_open(); wifi_fpm_set_wakeup_cb(wakeupCallback); wifi_fpm_do_sleep(0xFFFFFFF); system_soft_wdt_feed(); } bool CMain::HttpAuthorized(HttpRequest &request, HttpResponse &response) { String auth = request.getHeader("Authorization"); if(auth.startsWith("Basic ")) { int headerLength = auth.length() - 6; if(headerLength <= 64) { auth = base64_decode(auth.c_str() + 6, headerLength); if(auth) { int sep = auth.indexOf(':'); if(sep != -1) { String username = auth.substring(0, sep); String password = auth.substring(sep + 1); if(username == m_Settings.m_aUsername && password == m_Settings.m_aPassword) return true; } } } } response.code = HTTP_STATUS_UNAUTHORIZED; response.setHeader("WWW-Authenticate", "Basic realm=\"safeweb\""); response.setHeader("401 Wrong credentials", "Authentication required"); response.setHeader("Connection", "close"); return false; } void CMain::HttpOnApi(HttpRequest &request, HttpResponse &response) { if(!HttpAuthorized(request, response)) return; String path = request.uri.Path; if(path.length() < 6) { response.code = HTTP_STATUS_NOT_FOUND; return; } String endpoint = path.substring(5); DynamicJsonDocument jsonReq(1024); deserializeJson(jsonReq, request.getBody()); DynamicJsonDocument jsonResp(1024); response.setAllowCrossDomainOrigin("*"); response.setHeader("Access-Control-Allow-Headers", "Content-Type"); if(request.method == HTTP_OPTIONS) { response.code = HTTP_STATUS_OK; return; } HttpStatus status = HandleApi(request.method, endpoint, jsonReq, jsonResp); if(status != HTTP_STATUS_OK) { response.code = status; return; } String respString; serializeJson(jsonResp, respString); response.setContentType(MIME_JSON); response.sendString(respString); } HttpStatus CMain::HandleApi(HttpMethod method, String endpoint, JsonDocument &req, JsonDocument &resp) { if(endpoint == "state" || endpoint == "dashboard") { if(method != HTTP_GET) return HTTP_STATUS_METHOD_NOT_ALLOWED; 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; if(endpoint == "dashboard") { 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; 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) { 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); 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") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; if(!req.containsKey("index") || !req.containsKey("label")) return HTTP_STATUS_BAD_REQUEST; int index = req["index"].as() - 1; String label = req["label"]; if(index < 0 || index >= Settings().m_FingerPrints.count()) return HTTP_STATUS_BAD_REQUEST; CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(index); strncpy(finger.m_aLabel, label.c_str(), sizeof(finger.m_aLabel)); Settings().Save(); } else if(endpoint == "fingerprint/delete") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; if(!req.containsKey("index")) return HTTP_STATUS_BAD_REQUEST; int index = req["index"].as() - 1; if(index < 0 || index >= Settings().m_FingerPrints.count()) return HTTP_STATUS_BAD_REQUEST; const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(index); FingerPrint().DeleteTemplate(finger.m_FingerNum, 1); Settings().m_FingerPrints.removeAt(index); Settings().Save(); } else if(endpoint == "fingerprint/enroll") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; if(req.containsKey("cancel")) { if(req["cancel"].as() && m_Enrolling) { FingerLogic().EnrollFinger(false); m_Enrolling = false; m_Enrolled = false; } } else { if(!req.containsKey("enrolling")) return HTTP_STATUS_BAD_REQUEST; bool enrolling = req["enrolling"]; if(enrolling && !m_Enrolling) { if(m_Enrolled) { resp["done"] = true; resp["message"] = m_EnrollMessage; m_Enrolled = false; } else { resp["error"] = m_EnrollMessage; } } else if(enrolling && m_Enrolling) { resp["done"] = false; resp["message"] = m_EnrollMessage; } else // if(!enrolling) { if(!req.containsKey("label")) return HTTP_STATUS_BAD_REQUEST; if(!m_Enrolling) { FingerLogic().EnrollFinger(); m_Enrolling = true; m_Enrolled = false; m_EnrollLabel = String(req["label"].as()); resp["done"] = false; m_EnrollMessage = "Started enroll process. Please place your finger on the sensor."; resp["message"] = m_EnrollMessage; } else { resp["done"] = true; m_Enrolled = false; resp["message"] = m_EnrollMessage; } } } } else { return HTTP_STATUS_NOT_FOUND; } return HTTP_STATUS_OK; } void CMain::HttpOnFile(HttpRequest &request, HttpResponse &response) { if(!HttpAuthorized(request, response)) return; String file = request.uri.Path; if(file == "/") file = "index.html"; if(file[0] == '/') file = file.substring(1); if(file[0] == '.') { response.code = HTTP_STATUS_FORBIDDEN; return; } response.setCache(86400, true); // It's important to use cache for better performance. response.sendFile(file); } 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::CheckSleep() { if(!m_FingerPlaced && !m_LockUnlocked && !m_DoorOpened) { m_LightSleepTimer.startOnce(); } else { m_LightSleepTimer.stop(); } } void CMain::OnFinger(bool finger) { m_FingerPlaced = finger; m_FingerLogic.OnFinger(finger); if(finger) MqttSendMessage("safeweb/finger/placed", "true"); else MqttSendMessage("safeweb/finger/placed", "false"); CheckSleep(); } void CMain::OnLock(bool unlocked) { m_LockUnlocked = unlocked; if(unlocked) MqttSendMessage("safeweb/lock/unlocked", "true"); else MqttSendMessage("safeweb/lock/unlocked", "false"); CheckSleep(); } void CMain::OnDoor(bool opened) { m_DoorOpened = opened; if(opened) MqttSendMessage("safeweb/door/opened", "true"); else MqttSendMessage("safeweb/door/opened", "false"); CheckSleep(); } void CMain::FingerPower(bool enable) { const int pin = FINGER_ENABLE_PIN; digitalWrite(pin, !enable); if(enable) { wifi_set_sleep_level(MAX_SLEEP_T); wifi_set_listen_interval(3); wifi_set_sleep_type(MODEM_SLEEP_T); } else { wifi_set_sleep_type(LIGHT_SLEEP_T); } } void CMain::LockSendBytes(uint8_t *pBytes, uint8_t len) { const int pin = SAFELOCK_DATA_PIN; // Init digitalWrite(pin, 0); delayMicroseconds(10 * 1000); digitalWrite(pin, 1); delayMicroseconds(10 * 1000); // Send data, calculate checksum and send checksum uint8_t chk = 0x00; for(uint8_t i = 0; i <= len; i++) { uint8_t byte; if(i == len) { byte = chk; } else { byte = pBytes[i]; chk ^= byte; } for(int8_t j = 0; j < 8; j++) { digitalWrite(pin, 0); delayMicroseconds(100); digitalWrite(pin, 1); uint8_t val = byte & (1 << j); if(val) delayMicroseconds(300); else delayMicroseconds(100); } digitalWrite(pin, 0); delayMicroseconds(100); digitalWrite(pin, 1); delayMicroseconds(8); } } void CMain::LockSendCode(uint8_t pass[8]) { uint8_t packet[9] = {0x51}; memcpy(&packet[1], pass, 8); LockSendBytes(packet, sizeof(packet)); } void CMain::LockUnlock() { LockSendCode(Settings().m_aLockCode); } void CMain::EnrollMessage(const char *msg, bool error) { m_EnrollMessage = msg; if(error) m_Enrolling = false; } void CMain::OnFingerVerified(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]) { int fingerIndex = Settings().m_FingerPrints.indexOf(fingerNum); if(fingerIndex == -1) { debugf("OnFingerVerified: fingerIndex == -1"); return; } const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(fingerIndex); if(memcmp(digest, finger.m_aDigest, SHA256_SIZE) != 0) { debugf("OnFingerVerified: SHA256 mismatch"); return; } LockUnlock(); debugf("OnFingerVerified: OK!!!"); } void CMain::OnFingerEnrolled(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]) { CSettings::CFingerPrint finger; finger.m_FingerNum = fingerNum; strncpy(finger.m_aLabel, m_EnrollLabel.c_str(), sizeof(finger.m_aLabel)); memcpy(finger.m_aDigest, digest, SHA256_SIZE); char aHexDigest[SHA256_SIZE*2+1]; bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); debugf("OnFingerEnrolled: \"%s\"", aHexDigest); Settings().m_FingerPrints[fingerNum] = finger; Settings().Save(); m_Enrolled = true; m_Enrolling = false; m_EnrolledFinger = finger; char aBuf[512]; m_snprintf(aBuf, sizeof(aBuf), "Successfully enrolled new finger \"%s\".", finger.m_aLabel); EnrollMessage(aBuf); debugf("OnFingerEnrolled: OK!!!"); }