2020/09/05

[HomeAssistant]實現ESPHome - RC522模組,實現NFC控制器

本篇教學實現使用RC522接入ESPHome,用ESP-12F,讓NFC感應成為智能家庭的一環。

This project designs an ESPHOME RFID reader device, using the cheap solution ESP8266(ESP-12F) and RC522 module.

1. 安裝ESPHome套件(Install ESPHome in Hassio add-on store)

我用Hassio架設,
Supervisor > Add-Ons > [Menu]Repositories > [新增]https://github.com/esphome/hassio

Installing the ESPHome Home Assistant add-on is easy. Navigate to the Supervisor panel in your Home Assistant frontend, then enter ESPHome in the searchbar of the "Add-on Store" tab. And add ESPHOME Github(https://github.com/esphome/hassio) to Repositories. Click on ESPHome, then INSTALL.

Supervisor > Add-Ons > ESPHome Home Assistant Add-Ons > ESPHome > 點開安裝

安裝完成之後,到Configuration設定,把SSL改為false,回到Info頁面就可以Start Service了

If you don't have SSL, Setting in Configuration change to SSL false, then Start Service.

到ESPHome的Web UI,新增一組裝置,名稱只能英文小小+數字+底線

Open ESPHome Web UI, Add a new device, device name only allow [a-z0-9_].

Device Type 選 Generic ESP8266(for example sonoff),底下新增就隨便填就好,新增之後我們要直接修改esprfid.yaml

Just fill in casually, because we want write yaml file after create.

新增完成之後,點選EDIT,直接把下面的內容取代過去,注意,裡面紅色部分的名稱、密碼、Wifi SSID 密碼跟API密碼記得修改成自己的(ESP8266只支援2.4G Wifi)

Replace all the yaml content, and then remember to modify the red part to your own settings. It is important to note that esp8266 only supports 2.4g wifi, if you use 5g, it does not support it.

(此設定檔是用我做的PCB腳位,有需要可以跟我購買,所有元件都幫你焊好,NT.880元,免運費)

  

esprfid.yaml

esphome: name: esprfid platform: ESP8266 board: d1_mini includes: - esprfid.h libraries: - "ESP8266HTTPClient" - "SPI" - "MFRC522" # Need to override -Wl,-T - the only way is by setting all build_flags platformio_options: build_flags: -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_DEBUG -DUSE_STORE_LOG_STR_IN_FLASH -Wl,-Teagle.flash.4m1m.ld -fno-exceptions custom_component: - lambda: |- auto component = new RfidSensorsComponent(); return { component }; id: rfid_component wifi: ssid: WIFI_SSID password: 123456789 ap: ssid: 'ESPRFID' password: '123456789' # Enable logging logger: # Enable OTA updates ota: password: 123456789 # Enable Home Assistant API api: password: 123456789 services: - service: print_users_list then: lambda: |- auto component_id = id(rfid_component); RfidSensorsComponent* component = ((RfidSensorsComponent*) (component_id.get_component(0))); component->send_users_list_notification(); - service: upload_users_list variables: list: string[] then: lambda: |- auto component_id = id(rfid_component); RfidSensorsComponent* component = ((RfidSensorsComponent*) (component_id.get_component(0))); component->upload_users_list(list); binary_sensor: - platform: custom lambda: |- auto component_id = id(rfid_component); RfidSensorsComponent* component = ((RfidSensorsComponent*) (component_id.get_component(0))); return { component->valid_tag_sensor, component->invalid_tag_sensor }; binary_sensors: - name: "Valid tag" on_press: - switch.turn_on: esprfid_relay - switch.turn_on: alarm_led - output.esp8266_pwm.set_frequency: id: buzzer frequency: 1800Hz - output.set_level: id: buzzer level: 50% - delay: 300ms - output.esp8266_pwm.set_frequency: id: buzzer frequency: 2400Hz - output.set_level: id: buzzer level: 50% - delay: 400ms - output.turn_off: buzzer - switch.turn_off: alarm_led - delay: 1300ms - name: "Invalid tag" on_press: - output.esp8266_pwm.set_frequency: id: buzzer frequency: 600Hz - output.set_level: id: buzzer level: 50% - switch.turn_on: alarm_led - delay: 200ms - output.turn_off: buzzer - switch.turn_off: alarm_led - delay: 200ms - output.esp8266_pwm.set_frequency: id: buzzer frequency: 600Hz - output.set_level: id: buzzer level: 50% - switch.turn_on: alarm_led - delay: 200ms - output.turn_off: buzzer - switch.turn_off: alarm_led - delay: 200ms - output.esp8266_pwm.set_frequency: id: buzzer frequency: 600Hz - output.set_level: id: buzzer level: 50% - switch.turn_on: alarm_led - delay: 200ms - output.turn_off: buzzer - switch.turn_off: alarm_led - delay: 200ms - output.esp8266_pwm.set_frequency: id: buzzer frequency: 600Hz - output.set_level: id: buzzer level: 50% - switch.turn_on: alarm_led - delay: 200ms - output.turn_off: buzzer - switch.turn_off: alarm_led - delay: 200ms - output.esp8266_pwm.set_frequency: id: buzzer frequency: 600Hz - output.set_level: id: buzzer level: 50% - switch.turn_on: alarm_led - delay: 200ms - output.turn_off: buzzer - switch.turn_off: alarm_led - delay: 200ms text_sensor: - platform: custom lambda: |- auto component_id = id(rfid_component); RfidSensorsComponent* component = ((RfidSensorsComponent*) (component_id.get_component(0))); return { component->last_tag_id_sensor, component->last_tag_description_sensor }; text_sensors: - name: "Last Tag ID" icon: "mdi:card-bulleted-outline" - name: "Last Tag Description" icon: "mdi:card-bulleted-outline" output: - platform: esp8266_pwm pin: GPIO4 #蜂鳴器(需自行外接) id: 'buzzer' status_led: pin: number: GPIO2 #系統預設網路狀態燈不要改(8266上藍燈) inverted: true switch: - platform: gpio pin: GPIO0 #讀卡機指示燈(需外接) restore_mode: ALWAYS_ON inverted: yes id: alarm_led name: "Alarm LED" icon: "mdi:alarm-light" - platform: gpio pin: GPIO5 #控制繼電器(需自行外接) restore_mode: ALWAYS_OFF id: esprfid_relay name: "Esprfid Relay" icon: "mdi:power-plug" on_turn_on: - delay: 2s - switch.turn_off: esprfid_relay

把設定檔(esprfid.h)放到ESPHome目錄底下,透過FTP、SAMBA、IDE都可以,我習慣用Cloud9 IDE(可透過Add Ons安裝),目錄就是workspace>config>esphome

Put the esprfid.h file in the ESPhome directory(/workspace/config/esphome). You can use FTP/SAMBA/IDE tools.

esprfid.h

/* * ESPhome RC522 RFID plugin with access control * * Created on: 17.03.2019 * * Copyright (c) 2019 Jakub Pachciarek. All rights reserved. * */ #include "esphome.h" #include #include #include using namespace esphome; using namespace std; class RfidSensorsComponent : public Component { private: MFRC522 rfid; typedef struct { String id; String description; } user; vector users_list; unsigned long last_tag_read_milis = 0; //https://www.esphome.io/api/custom__api__device_8h_source.html void send_notification(String title, String message, String notification_id) { api::HomeassistantServiceResponse resp; resp.service = "persistent_notification.create"; HomeassistantServiceMap kv1; kv1.key = "title"; kv1.value = title.c_str(); resp.data.push_back(kv1); HomeassistantServiceMap kv2; kv2.key = "message"; kv2.value = message.c_str(); resp.data.push_back(kv2); HomeassistantServiceMap kv3; kv3.key = "notification_id"; kv3.value = notification_id.c_str(); resp.data.push_back(kv3); api::global_api_server->send_homeassistant_service_call(resp); } void send_event(String name) { api::HomeassistantServiceResponse resp; resp.service = name.c_str(); resp.is_event = true; api::global_api_server->send_homeassistant_service_call(resp); } bool load_users_list() { ESP_LOGD("rfid", "Loading users list from file..."); File f = SPIFFS.open("/rfid_users_list.db", "r"); if (!f) { ESP_LOGE("rfid", "Failed to open users list file!"); return false; } ESP_LOGD("rfid", "Parsing users list..."); users_list.clear(); while (f.available()) { String line; char tmp = 0; while (f.available() && tmp != '\n'){ tmp = f.read(); line += tmp; } line.trim(); size_t split_pos = line.indexOf('='); if (split_pos == 11) { //Naive ID check: XX:XX:XX:XX=DESCRIPTION user row; row.id = line.substring(0, split_pos); row.description = line.substring(split_pos + 1); users_list.push_back(row); } } f.close(); return true; } bool save_users_list() { ESP_LOGD("rfid", "Saving users list to file..."); File f = SPIFFS.open("/rfid_users_list.db", "w"); if (!f) { ESP_LOGE("rfid", "Failed to open users list file!"); return false; } for (auto &user : users_list) { f.print(user.id + "=" + user.description + '\n'); } f.close(); return true; } void handle_tag_read() { if (last_tag_read_milis != 0 && (last_tag_read_milis + 2000) > millis()) return; valid_tag_sensor->publish_state(false); invalid_tag_sensor->publish_state(false); if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) return; user current_tag; bool known_tag = false; char tag_id_buffer[12]; sprintf(tag_id_buffer, "%02X:%02X:%02X:%02X", rfid.uid.uidByte[0], rfid.uid.uidByte[1], rfid.uid.uidByte[2], rfid.uid.uidByte[3]); current_tag.id = tag_id_buffer; current_tag.description = "UNKNOWN"; rfid.PICC_HaltA(); rfid.PCD_StopCrypto1(); for (size_t i = 0; i < users_list.size(); i++) { if (users_list[i].id == current_tag.id) { current_tag = users_list[i]; known_tag = true; break; } } ESP_LOGD("rfid", "Tag with id %s found!", current_tag.id.c_str()); ESP_LOGD("rfid", "Setting sensor states..."); last_tag_read_milis = millis(); valid_tag_sensor->publish_state(known_tag); invalid_tag_sensor->publish_state(!known_tag); last_tag_id_sensor->publish_state(current_tag.id.c_str()); last_tag_description_sensor->publish_state(current_tag.description.c_str()); } public: binary_sensor::BinarySensor *valid_tag_sensor = new binary_sensor::BinarySensor(); binary_sensor::BinarySensor *invalid_tag_sensor = new binary_sensor::BinarySensor(); text_sensor::TextSensor *last_tag_id_sensor = new text_sensor::TextSensor(); text_sensor::TextSensor *last_tag_description_sensor = new text_sensor::TextSensor(); void send_users_list_notification() { bool first = true; String message; message += "{\"list\":[\n"; if (users_list.size() == 0) { message += "\"XX:XX:XX:XX=Example 1\",\n\"XX:XX:XX:XX=Example 2\""; } for (auto &user : users_list) { if (!first) message += ",\n"; first = false; message += "\"" + user.id + "=" + user.description + "\""; } message += "\n]}"; send_notification((App.get_name() + ": users list").c_str(), message, (App.get_name() + "_users_list_print").c_str()); } void upload_users_list(vector list) { users_list.clear(); for (auto &line : list) { size_t split_pos = line.find('='); if (split_pos == 11) { //Naive ID check: XX:XX:XX:XX=DESCRIPTION user row; row.id = line.substr(0, split_pos).c_str(); row.description = line.substr(split_pos + 1).c_str(); users_list.push_back(row); } } save_users_list(); send_users_list_notification(); } void setup() override { SPIFFS.begin(); SPI.begin(); rfid.PCD_Init(D0, D3); load_users_list(); } void loop() override { handle_tag_read(); } };

插上寫程式工具(板子上Write),5V、GND、TX、RX(需要USB to TTL線),如果跟我買的話送你一條,板子上的Micro USB單純只供電使用,因為USB to TTL晶片居然比現成的線還要貴,而刷ESPHome通常只會用到一次,日後用OTA更新,一顆晶片的價錢可以買三條線了,上晶片不符合經濟效益,所以我沒把轉換晶片Layout進去,如果日後有更多人有此需求,我考慮一次買一卷比較便宜一些。

For foreign users, I cannot sell circuit boards to you. Please use Nodemcu or D1-Mini circuit boards for testing.

RC522 SDA - GPIO16(D0)
RC522 SCK - GPIO14(D5)
RC522 MOSI - GPIO13(D7)
RC522 MISO - GPIO12(D6)
Alarm LED - GPIO0(D3)
RELAY - GPIO5(D1)

插到上Hassio的機器上,重新啟動服務(RESTART),在WEB UI就可以看到USB的Port了

Just plugin usb to your Hassio, And restart ESPhome Service, and you can see USB Port in upload select list.

按住RST按鈕+FLASH按鈕 > 放開REST按鈕 > 3秒 > 放開FLASH按鈕 > 進入USB燒寫模式

If you use the nodemcu module, it will automatically enter the write mode, just upload it. For other modules, let him enter the programming mode first, connect GPIO0 to HIGH and then power on.

WEB UI就可以UPLOAD程式碼上去囉

按一下RESET,看LOG就可以得到IP位置

After the programming is complete, you can access the LOG of the device through OTA, and you can see all the device information from the LOG. You need to remember the IP of the device.

把ESPHome裝置整合到Hassio裡面,設定 > 整合 > 新增 > ESPHome,輸入API密碼(預設123456789)

Add the ESPHOME device to the integrated interface, enter the IP, and then your password(default is 123456789), you can add the device to your Homeassistant.

新增完成之後就可以從整合裡面看到了

總共有5個物件

到lovelace介面新增RFID的物件

After adding the device successfully, put the objects inside the device on the lovelace interface.

entities:
  - entity: binary_sensor.invalid_tag
  - entity: binary_sensor.valid_tag
  - entity: sensor.last_tag_id
  - entity: sensor.last_tag_description
  - entity: switch.esprfid_relay
  - entity: switch.alarm_led
title: RFID
type: entities
show_header_toggle: false

隨便刷一張卡片,看看有沒有抓到卡號

Just swipe a card, you can see the card ID.

查詢/新增卡片,[開發工具>服務],選擇[esphome.esprfid_print_users_list],執行服務

Query card, [Development Tools>Service], select [esphome.esprfid_print_users_list], execute service. And you can see all ligal card ID in notice.

執行後,就可以在[通知提示]裡面看到現在所有合法的RFID卡片卡號

格式如下,用json格式,使用[esphome.esprfid_upload_users_list]方法即可上傳新的合法RFID卡片。

The format is as follows. Use the json format and use the [esphome.esprfid_upload_users_list] method to upload a new legal RFID card. And you can see all ligal card ID in notice.

{"list":[
"F1:EF:BB:21=TEST",
"30:F2:FF:35=HC"
]}

上傳成功之後,跟查詢卡號一樣,會在[通知提示]列出一次現在所有的卡片ID。

另外,我設計的這張PCB版也支援esp-rfid(腳位一樣),有需要可以去參考看看,https://github.com/esprfid/esp-rfid,但目前韌體有BUG,不定期會斷線,沒辦法恢復,但作者已經很久沒更新了,我沒耐心去研究程式碼了,有興趣的也能玩玩看,如果有新的結果也請跟我分享。

The electrical circuit of this project also supports esp-rfid, but esp-rfid still has unresolved connection problems. It may stop the service at any time. The most important thing is that his code has not been updated for two years.

Reference -> https://github.com/HausnerR/esphome-door-lock

update: 2021/07/17新增ESPHome新版本程式碼(esprfid.yaml)

esprfid.yaml

# Basic substitutions: # Modify variables based on your settings hostname: 'esprfid' ssid: WIFI_SSID password: 123456789 esphome: name: $hostname platform: ESP8266 board: nodemcuv2 arduino_version: 2.4.2 # board_flash_mode: dout wifi: ssid: $ssid password: $password fast_connect: True power_save_mode: NONE api: password: 123456789 ota: password: 123456789 logger: web_server: port: 80 auth: username: esprfid password: 123456789 spi: clk_pin: GPIO14 miso_pin: GPIO12 mosi_pin: GPIO13 rc522_spi: # or rc522_i2c cs_pin: GPIO16 #update_interval: 1s on_tag: then: - homeassistant.tag_scanned: !lambda 'return x;' - switch.turn_on: alarm_led - text_sensor.template.publish: id: rfid_tag state: !lambda 'return x;' - output.esp8266_pwm.set_frequency: id: buzzer frequency: 1800Hz - output.set_level: id: buzzer level: 50% - delay: 200ms - output.esp8266_pwm.set_frequency: id: buzzer frequency: 2400Hz - output.set_level: id: buzzer level: 50% - delay: 200ms - output.turn_off: buzzer - switch.turn_off: alarm_led - delay: 1000ms output: - platform: esp8266_pwm pin: GPIO4 #蜂鳴器(需外接) id: 'buzzer' status_led: pin: number: GPIO2 #系統預設網路狀態燈不要改(8266上藍燈) inverted: true text_sensor: - platform: template name: $hostname last_tag id: rfid_tag switch: - platform: gpio pin: GPIO0 #讀卡機指示燈(需外接) restore_mode: ALWAYS_OFF inverted: yes id: alarm_led name: $hostname alarm_led icon: "mdi:alarm-light" - platform: gpio pin: GPIO5 #控制繼電器(需外接) restore_mode: ALWAYS_OFF id: esprfid_relay name: $hostname relay icon: "mdi:power-plug" on_turn_on: - delay: 2s - switch.turn_off: esprfid_relay

6 則留言:

  1. 感謝HC工程師,我是對岸的智能家居小白玩家,按您的教程目前成功在ha里面录入了卡片ID,非常感谢您的教程!

    回覆刪除
  2. https://www.youtube.com/watch?v=QxxKmTVpx5k 這個是我之前做的一個小玩具項目

    回覆刪除
    回覆
    1. 很酷耶,可惜我的車沒辦法這樣控制,2020年的Mazda3

      刪除
    2. 其實我是用了偷懶的辦法,只是用了個小機器手指按備用開關而已...同理您的車子應該也可以.大陸這邊叫昂克塞拉,也是一鍵進入的.當然正經的做法是接入車子的CAN總線發送指令,不過這個對于我來說太難了,而且可能需要很多時間和精力.我的初衷只是在家不需要鑰匙開車門而已,所以就這樣了.

      刪除
    3. 原來如此,我以為你用OBD2的接入,這樣的話不難,我住大樓,車停在地下室沒有訊號,無法實現,日後如果買的起別墅,再來考慮

      刪除