2020年9月5日 星期六

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

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

1. 安裝ESPHome套件

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

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

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

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

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

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

(此設定檔是用我做的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

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進去,如果日後有更多人有此需求,我考慮一次買一卷比較便宜一些。

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

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

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

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

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

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

總共有5個物件

到lovelace介面新增RFID的物件

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

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

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

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

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

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

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

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

沒有留言:

張貼留言