From d91207fdb7672e7bae26ebd90ae2e718644bdf89 Mon Sep 17 00:00:00 2001 From: Jonny Davey Date: Wed, 3 Jan 2024 16:58:05 +0000 Subject: [PATCH] Added Components for FR1002 modules --- FaceRec_FR1002.h | 226 ++++++++++++++++++++++++++++++++++++++++++++ FaceRec_FR1002.yaml | 155 ++++++++++++++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 FaceRec_FR1002.h create mode 100644 FaceRec_FR1002.yaml diff --git a/FaceRec_FR1002.h b/FaceRec_FR1002.h new file mode 100644 index 0000000..d168ba7 --- /dev/null +++ b/FaceRec_FR1002.h @@ -0,0 +1,226 @@ +#include "esphome.h" + +using namespace esphome; + +#define SYNCWORD1 0xEF +#define SYNCWORD2 0xAA +// Commands: +#define IDENT 0x12 // Identify +#define REGIS 0x13 // Register User +#define DEL 0x20 // Delete UserID +#define CLR 0x21 // Clear All UserIDs +#define VERS 0x30 // Query Version +#define REBT 0xC3 // Reboot +#define NUMBERREC 0xC4 // Query number recorded entries +#define TURNON 0x01 +#define TURNOFF 0xED +// Responds from FR1002 +//#define SUCC 0x00 // Successful +//#define FAIL 0x01 // Failed +//#define ANGL 0x03 // Angle failure +//#define FAIL2D 0x06 // 2D failure +//#define FAIL3D 0x07 // 3D failure +//#define NOMATCH 0x08 // No mactching entry +//#define EXIST 0x09 // Entry already exist +#define SUCC 0x00 // Success +#define FAIL 0x01 // The module rejected the command +#define ALGO 0x02 // The entry / matching algorithm has been terminated +#define ERROR 0x03 // Send message error +#define CAMFAIL 0x04 // The camera failed to open +#define UNKN 0x05 // Unknown error +#define INVALID 0x06 // Invalid parameters +#define MEMOUT 0x07 // Run out of memory +#define NOUSER 0x08 // No entered users +#define MAXUSER 0x09 // Entry exceeds the maximum number of users +#define FACEENTERED 0x0A // The face has been entered +#define VIVIAN 0x0C // Vivian detection failed +#define TIMEOUT 0x0D // Enter or unlock the timeout +#define ENCFAIL 0x0E // Encryption chip licensing failed +#define READFAIL 0x13 // Failed to read the file +#define WRITEFAIL 0x14 // Failed to write files +#define NOTENC 0x15 // The communication protocol is not encrypted +#define RGBNOTRED 0x17 // The RGB images are not ready +#define INVALIDID 0xFD // Invalid information ID, key is not written +#define DETECTERROR 0xFE // Detection error +#define CODEERR 0xFF // Coding error + + +class UARTSensor : public Component, public UARTDevice { + public: + UARTSensor(UARTComponent *parent) : UARTDevice(parent) {} + TextSensor* presence_sensor = new TextSensor(); + TextSensor* text_userid = new TextSensor(); + TextSensor* text_reply = new TextSensor(); + + void setup() { + ESP_LOGD("custom", "UARTSensor setup..."); + } + +std::vector bytes; + +void loop() override +{ + static int MAX_PACKET_SIZE = 45; + static int packetIndex = 0; + static bool receivingData = false; + static byte receivedParity = 0; + + while (available()) { + + bytes.push_back(read()); + + if (receivingData) { + if (bytes[packetIndex] == receivedParity && packetIndex > 7) { + processPacket(); + receivedParity = 0; + packetIndex = 0; + receivingData = false; + bytes.clear(); + } else { + receivedParity += bytes[packetIndex]; + packetIndex++; + } + + } else if (packetIndex <= MAX_PACKET_SIZE) { + + if (bytes[0] == SYNCWORD1 && packetIndex == 0) { + packetIndex = 1; + } else if (bytes[1] == SYNCWORD2 && packetIndex == 1) { + packetIndex++; + receivingData = true; + } + else { + receivingData = false; + packetIndex = 0; + bytes.clear(); + } + } else { + packetIndex = 0; + receivedParity = 0; + receivingData = false; + bytes.clear(); + } + } +} + +void processPacket() { + ESP_LOGD("custom", "Processing packet..."); + switch(bytes[7]){ + case IDENT: + case REGIS: + case DEL: + case CLR: + case REBT: + case NUMBERREC: + switch(bytes[8]){ + case SUCC: + id(text_reply).publish_state("Success"); + if(bytes[7] == IDENT || bytes[7] == REGIS || bytes[7] == NUMBERREC) { + byte userId; + userId = bytes[9] + bytes[10]; + if(bytes[7] != NUMBERREC){ + id(text_userid).publish_state(String(userId).c_str()); + id(presence_sensor).publish_state("on"); + } else { + String sum = String(userId); + String text = "Sum: " + String(userId); + if( sum != "0"){ + for(int i = 11; i < bytes.size() - 2; i+=2){ + byte id = bytes[i] + bytes[i+1]; + text+= ", User ID: " + String(id); + } + } + id(text_reply).publish_state(text.c_str()); + } + } + break; + default: + if(bytes[7] == IDENT || bytes[7] == REGIS) { + id(text_userid).publish_state("None"); + id(presence_sensor).publish_state("off"); + } + switch(bytes[8]){ + case FAIL: + id(text_reply).publish_state("The module rejected the command"); + break; + case ALGO: + id(text_reply).publish_state("The entry / matching algorithm has been terminated"); + break; + case ERROR: + id(text_reply).publish_state("Send message error"); + break; + case CAMFAIL: + id(text_reply).publish_state("The camera failed to open"); + break; + case UNKN: + id(text_reply).publish_state("Unknown error"); + break; + case INVALID: + id(text_reply).publish_state("Invalid parameters"); + break; + case MEMOUT: + id(text_reply).publish_state("Run out of memory"); + break; + case NOUSER: + id(text_reply).publish_state("No entered users"); + break; + case MAXUSER: + id(text_reply).publish_state("Entry exceeds the maximum number of users"); + break; + case VIVIAN: + id(text_reply).publish_state("Vivian detection failed"); + break; + case TIMEOUT: + id(text_reply).publish_state("Enter or unlock the timeout"); + break; + case ENCFAIL: + id(text_reply).publish_state("Encryption chip licensing failed"); + break; + case READFAIL: + id(text_reply).publish_state("Failed to read the file"); + break; + case WRITEFAIL: + id(text_reply).publish_state("Failed to write files"); + break; + case NOTENC: + id(text_reply).publish_state("The communication protocol is not encrypted"); + break; + case RGBNOTRED: + id(text_reply).publish_state("The RGB images are not ready"); + break; + case INVALIDID: + id(text_reply).publish_state("Invalid information ID, key is not written"); + break; + case DETECTERROR: + id(text_reply).publish_state("Detection error"); + break; + case CODEERR: + id(text_reply).publish_state("Coding error"); + break; + } + break; + } + break; + case VERS: { + std::vector version; + for (int i = 16; i < bytes.size() - 1; i++) { + version.push_back(bytes[i]); + } + String ver = byteArrayToAsciiString(version.data(), version.size()); + id(text_reply).publish_state(ver.c_str()); + break; + } + } +} + + String byteArrayToAsciiString(byte* byteArray, int arraySize) { + String result = ""; + + for (int i = 0; i < arraySize; i++) { + result += char(byteArray[i]); + } + + return result; +} + +}; \ No newline at end of file diff --git a/FaceRec_FR1002.yaml b/FaceRec_FR1002.yaml new file mode 100644 index 0000000..2e218a8 --- /dev/null +++ b/FaceRec_FR1002.yaml @@ -0,0 +1,155 @@ +substitutions: + # change device name to match your desired name + device_name: "3dface" + # change sensor name below to the one you want to see in Home Assistant + device_name_pretty: 3DFace + +esphome: + name: $device_name + friendly_name: $device_name_pretty + includes: + - fr1002/fr1002.h + +esp8266: + board: d1_mini + +# Enable logging +logger: +# level: VERBOSE + +# Enable Home Assistant API +api: + encryption: + key: "svWlH+FL8jyu+eeDao0l/1zE5xZljWK2sl15kJoYbPU=" + +ota: + password: "f4896c7a3eb6073bdd0c460c58e21f88" + +wifi: + ssid: !secret wifi_ssid_homeauto + password: !secret wifi_password_homeauto + fast_connect : true + domain: ".HomeAuto.Daveys.xyz" + + # Enable fallback hotspot (captive portal) in case wifi connection fails + ap: + ssid: "3Dface Fallback Hotspot" + password: "duamVei8cBB5" + +web_server: + port: 80 + version: 2 + include_internal: true + +http_request: + useragent: esphome/$device_name + timeout: 2s + +uart: + id: uart_bus + rx_pin: GPIO3 + tx_pin: GPIO1 + baud_rate: 115200 + debug: + direction: BOTH + dummy_receiver: false # enabling this, prevents UART byte read in main program, use only for debugging of raw frames and pin assignments + sequence: + - lambda: UARTDebug::log_hex(direction, bytes, ','); + +button: + - platform: template + name: "Start Identify: ${device_name_pretty}" # Button to start the identification process + id: identify + entity_category: config + on_press: + - uart.write: [0xEF, 0xAA, 0x12, 0x00, 0x02, 0x00, 0x02, 0x12] + - platform: template + name: "Register User: ${device_name_pretty}" # Button to register the user currently facing the camera + id: registration + entity_category: config + on_press: + - uart.write: [0xEF, 0xAA, 0x13, 0x00, 0x23, 0x00, 0x00, 0x13] + - platform: template + name: "Query Num. Reg. UserIDs: ${device_name_pretty}" # Button to query the number of registered faces + id: query_num + entity_category: config + on_press: + - uart.write: [0xEF, 0xAA, 0xC4, 0x00, 0x00, 0xC4] + - platform: restart + name: "Restart MCU: ${device_name_pretty}" # Button to restart your ESP + entity_category: diagnostic + on_press: + - uart.write: + id: uart_bus + data: "resetSystem 0" + - platform: template + name: "Reboot FR1002 Module: ${device_name_pretty}" # Button to reboot the FR1002 + id: reboot_module + entity_category: diagnostic + on_press: + - uart.write: [0xEF, 0xAA, 0xC3, 0x00, 0x00, 0xC3] + - platform: template + name: "Query FR1002 Version: ${device_name_pretty}" # Button to query the FR1002 version + id: query_version + entity_category: diagnostic + on_press: + - uart.write: [0xEF, 0xAA, 0x30, 0x00, 0x00, 0x30] + - platform: template + name: "Query FR1002 State: ${device_name_pretty}" # Button to query the FR1002 state + id: query_state + entity_category: diagnostic + on_press: + - uart.write: [0xEF, 0xAA, 0x11, 0x00, 0x00, 0x11] + +binary_sensor: + - platform: template + id: presence_template + name: "${device_name_pretty} Presence" # This should indicate if someone is recognised (true) or not (false) + device_class: presence + +text_sensor: + - platform: custom + lambda: |- + auto my_text_sensor = new UARTSensor(id(uart_bus)); + App.register_component(my_text_sensor); + return {my_text_sensor->text_userid, my_text_sensor->text_reply, my_text_sensor->presence_sensor}; + text_sensors: + - id: text_userid + name: "internal_text_userid_to_text_template" + internal: true + on_value: + then: + - text_sensor.template.publish: + id: user_id_template + state: !lambda return x; + - id: text_reply + name: "internal_text_reply_to_text_template" + internal: true + on_value: + then: + - text_sensor.template.publish: + id: reply_template + state: !lambda return x; + - id: presence_sensor + name: "internal_presense_sensor_to_binary_template" + internal: true + on_value: + - binary_sensor.template.publish: + id: presence_template + state: !lambda return x == "on"; + - platform: template + id: user_id_template + name: "${device_name_pretty} User ID" # Returns the User ID from the recognised face, note the first face will have the ID 0, so it matches the responce in the display + - platform: template + id: reply_template + name: "${device_name_pretty} Responce" + +number: + - platform: template + name: "${device_name_pretty} User Input" + id: user_input + optimistic: true + min_value: 0 + max_value: 99 + step: 1 + restore_value: true \ No newline at end of file