2024年2月17日 星期六

ES32 + MFRC522 RFID + MQTT + Node-Red

 ES32 + MFRC522 RFID +  MQTT + Node-Red 









Arduino 程式  

需要一個檔案:arduino_secrets.h 內容如下

#define SECRET_SSID ""

#define SECRET_PASS ""



//定義MFRC522 RFID read 與 ESP32 介面 接腳連接Pin assign
/* Wiring RFID RC522 module  
==============================================================
GND     = GND   3.3V    = 3.3V
The following table shows the typical pin layout used:
 *             MFRC522      ESP32    
 *             Reader/PCD            
 * Signal      Pin          Pin        
 * -----------------------------------
 * RST/Reset   RST          GPIO27  
 * SPI SS      SDA(SS)      GPIO5    
 * SPI MOSI    MOSI         GPIO23    
 * SPI MISO    MISO         GPIO19    
 * SPI SCK     SCK          GPIO18    
 *
[1] (1, 2) Configurable, typically defined as RST_PIN in sketch/program.
[2] (1, 2) Configurable, typically defined as SS_PIN in sketch/program.
[3] The SDA pin might be labeled SS on some/older MFRC522 boards
=============================================================
*/
// Wifi 與 MQttClient 程式庫
#include <ArduinoMqttClient.h>
#include <WiFi.h>
#include "arduino_secrets.h"

//MFRC522 程式庫
#include <SPI.h>
#include <MFRC522.h>

//GPIO 2 D1 Build in LED

#define LED 13           //定義LED接腳

///////please enter your sensitive data in the Secret tab/arduino_secrets.h
//char ssid[] = "TOTOLINK_A3002MU";    // your network SSID (name)
//char pass[] = "24063173";    // your network password (use for WPA, or use as key for WEP)
// WiFi SSID password , SSID 和密碼進行Wi-Fi 設定
//const char ssid[] = "alex9ufo"; // Enter your Wi-Fi name
//const char pass[] = "alex9981";  // Enter Wi-Fi password

char ssid[] = "dlink-103A";    // your network SSID (name)
char pass[] = "bdcce12882";    // your network password (use for WPA, or use as key for WEP)

WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);

//const char broker[] = "test.mosquitto.org";
const char broker[] = "broker.mqtt-dashboard.com";
int        port     = 1883;
String json = "";

const char *SubTopic1 = "alex9ufo/2024/LED_control";
const char *PubTopic2 = "alex9ufo/2024/LED_status";
const char *PubTopic3 = "alex9ufo/2024/RFID_UID";

const char willTopic[] = "alex9ufo/esp32/Starting";
//======================================================
#define RST_PIN      27        // 讀卡機的重置腳位
#define SS_PIN       5        // 晶片選擇腳位
MFRC522 mfrc522(SS_PIN, RST_PIN);    // 建立MFRC522物件
MFRC522::MIFARE_Key key;  // 儲存金鑰
MFRC522::StatusCode status;
//===========================================================
//布林代數 LED狀態 是否連上網路ESP32 ready ?
bool ledState = false;
bool atwork = false;
bool Send = false;  //true
String LEDjson = "";
int Count= 0;
//===========================================================
void onMqttMessage(int messageSize) {
  // we received a message, print out the topic and contents
  Serial.print("Received a message with topic '");
  Serial.print(mqttClient.messageTopic());
  String Topic= mqttClient.messageTopic();
  Serial.print("', duplicate = ");
  Serial.print(mqttClient.messageDup() ? "true" : "false");
  Serial.print(", QoS = ");
  Serial.print(mqttClient.messageQoS());
  Serial.print(", retained = ");
  Serial.print(mqttClient.messageRetain() ? "true" : "false");
  Serial.print("', length ");
  Serial.print(messageSize);
  Serial.println(" bytes:");
  String message="";
  // use the Stream interface to print the contents
  while (mqttClient.available()) {
    //Serial.print((char)mqttClient.read());
    message += (char)mqttClient.read();
  }

  Serial.println(message);
  message.trim();
  Topic.trim();

  if (Topic=="alex9ufo/2024/LED_control") {
  if (message == "on") {
    digitalWrite(LED, LOW);  // Turn on the LED
    //ledState = true;  //ledState = ture HIGH
    //設定 各個 旗號
    LEDjson ="ON";
    Send = true ;
    Serial.print("LED =");
    Serial.println(LEDjson);
    }
  if (message == "off" ) {
    digitalWrite(LED, HIGH); // Turn off the LED
    //ledState = false; //ledState = false LOW
    LEDjson ="OFF";
    Send = true ;
    Serial.print("LED =");
    Serial.println(LEDjson);
  }

    Serial.println();
    Serial.println("-----------------------");
  }  
}

//===========================================================
String printHex(byte *buffer, byte bufferSize) {
      String id = "";
      for (byte i = 0; i < bufferSize; i++) {
        id += buffer[i] < 0x10 ? "0" : "";
        id += String(buffer[i], HEX);
        id +=" ";
      }
      return id;
}
//===========================================================
//副程式  setup wifi
void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);     //print ssid
  WiFi.begin(ssid, pass);  //初始化WiFi 函式庫並回傳目前的網路狀態
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }   //假設 wifi 未連接 show ………

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}  
//===========================================================
//判斷 旗號Flash , Timer 是否為真
void LED_Message() {
  ////判斷 旗號 Send 是否為真 回傳MQTT訊息到MQTT Broker 
  if (Send) {
    // Convert JSON string to character array
    Serial.print("Publish message: ");
    Serial.println(LEDjson);
    LEDjson.trim();

    bool retained = false;
    int qos = 1;
    bool dup = false;
   
    // Publish JSON character array to MQTT topic
    mqttClient.beginMessage(PubTopic2,  LEDjson.length(), retained, qos, dup);  //LED Status
    mqttClient.print(LEDjson);
    mqttClient.endMessage();
    Send = false;    //處理過後 旗號 Send為假
  }

}
//===========================================================
void setup() {
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);  // Turn off the LED initially
  //Initialize serial and wait for port to open:
  Serial.begin(115200);   // Initialize serial communications with the PC
  while (!Serial);    // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
 
  setup_wifi();
  Serial.println("You're connected to the network");
  Serial.println();
 
  String willPayload = "ESP32 Start working....!";
  bool willRetain = true;
  int willQos = 1;

  mqttClient.beginWill(willTopic, willPayload.length(), willRetain, willQos);
  mqttClient.print(willPayload);
  mqttClient.endWill();

  Serial.print("Attempting to connect to the MQTT broker: ");
  Serial.println(broker);

  if (!mqttClient.connect(broker, port)) {
    Serial.print("MQTT connection failed! Error code = ");
    Serial.println(mqttClient.connectError());

    while (1);
  }

  Serial.println("You're connected to the MQTT broker!");
  Serial.println();

  // set the message receive callback
  mqttClient.onMessage(onMqttMessage);
  Serial.print("Subscribing to topic: ");
  Serial.println(SubTopic1);
  // subscribe to a topic
  // the second parameter sets the QoS of the subscription,
  // the the library supports subscribing at QoS 0, 1, or 2
  int subscribeQos = 1;
  mqttClient.subscribe(SubTopic1, subscribeQos);


  Serial.println();
  SPI.begin();      // Init SPI bus
  mfrc522.PCD_Init();   // Init MFRC522
  delay(4);       // Optional delay. Some board do need more time after init to be ready, see Readme
  //mfrc522.PCD_DumpVersionToSerial();  // Show details of PCD - MFRC522 Card Reader details
  Serial.println(F("Scan PICC to see UID, SAK, type, and data blocks..."));
}
//===========================================================
void loop() {
 
  // call poll() regularly to allow the library to receive MQTT messages and
  // send MQTT keep alives which avoids being disconnected by the broker
  mqttClient.poll();
 
  LED_Message();
  // to avoid having delays in loop, we'll use the strategy from BlinkWithoutDelay
  // see: File -> Examples -> 02.Digital -> BlinkWithoutDelay for more info
  unsigned long currentMillis = millis();


 if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
   
     Serial.println(F("Please scan MIFARE Classic card..."));
    // 確認是否有新卡片
   
    byte *id = mfrc522.uid.uidByte;   // 取得卡片的UID
    byte idSize = mfrc522.uid.size;   // 取得UID的長度
    String Type;
    Serial.print("PICC type: ");      // 顯示卡片類型
    // 根據卡片回應的SAK值(mfrc522.uid.sak)判斷卡片類型
    MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
    Type= mfrc522.PICC_GetTypeName(piccType);
    Serial.println(mfrc522.PICC_GetTypeName(piccType));

    Serial.print("UID Size: ");       // 顯示卡片的UID長度值
    Serial.println(idSize);
 
    for (byte i = 0; i < idSize; i++) {  // 逐一顯示UID碼
      Serial.print("id[");
      Serial.print(i);
      Serial.print("]: ");
      Serial.println(id[i], HEX);       // 以16進位顯示UID值
    }
    Serial.println();



    json="";
    //json = json +(" Card UID: ");
    String json1=printHex(mfrc522.uid.uidByte, mfrc522.uid.size);
    json1.toUpperCase();
    json = json + json1;
    json.trim();
    //json = json +(" , PICC type: ");
    //json =  json + Type;
    //json.trim();

    bool retained = false;
    int qos = 1;
    bool dup = false;

    mqttClient.beginMessage(PubTopic3,  json.length(), retained, qos, dup);
    mqttClient.print(json);
    mqttClient.endMessage();

    Serial.println();
    // Dump debug info about the card; PICC_HaltA() is automatically called
    // 令卡片進入停止狀態
    // Dump debug info about the card; PICC_HaltA() is automatically called
    // mfrc522.PICC_DumpToSerial(&(mfrc522.uid));
   
    mfrc522.PICC_HaltA();
    mfrc522.PCD_StopCrypto1(); // stop encryption on PCD
  }
}
////===========================================================

Node-Red 程式

[{"id":"c59686b37b309e43","type":"ui_switch","z":"86b3e6eeb5484ac5","name":"","label":"LED Control switch","tooltip":"","group":"d93d93998de35374","order":14,"width":5,"height":2,"passthru":true,"decouple":"false","topic":"topic","topicType":"msg","style":"","onvalue":"true","onvalueType":"bool","onicon":"","oncolor":"","offvalue":"false","offvalueType":"bool","officon":"","offcolor":"","animate":false,"className":"","x":510,"y":340,"wires":[["cb1ffbab67b58d64","e7ca113edc03d511"]]},{"id":"95af94893f2d6e53","type":"ui_led","z":"86b3e6eeb5484ac5","order":3,"group":"d93d93998de35374","width":6,"height":3,"label":"ESP32 LED Status","labelPlacement":"right","labelAlignment":"center","colorForValue":[{"color":"#ff0000","value":"false","valueType":"bool"},{"color":"#008000","value":"true","valueType":"bool"}],"allowColorForValueInMessage":false,"shape":"circle","showGlow":true,"name":"","x":990,"y":240,"wires":[]},{"id":"7a079be5f155366f","type":"ui_text","z":"86b3e6eeb5484ac5","group":"d93d93998de35374","order":1,"width":11,"height":2,"name":"","label":"RFID UID -->","format":"{{msg.payload}}","layout":"row-left","className":"","x":1010,"y":160,"wires":[]},{"id":"67c41933549d0e8b","type":"mqtt out","z":"86b3e6eeb5484ac5","name":"LED_control","topic":"alex9ufo/2024/LED_control","qos":"1","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"603bb104.d6134","x":970,"y":400,"wires":[]},{"id":"a55d82342a298cc4","type":"mqtt in","z":"86b3e6eeb5484ac5","name":"LED_status","topic":"alex9ufo/2024/LED_status","qos":"1","datatype":"auto-detect","broker":"603bb104.d6134","nl":false,"rap":true,"rh":0,"inputs":0,"x":470,"y":240,"wires":[["7173ea47b004a475","72ee320a21953114"]]},{"id":"06443b497f771269","type":"mqtt in","z":"86b3e6eeb5484ac5","name":"RFID_UID","topic":"alex9ufo/2024/RFID_UID","qos":"1","datatype":"auto-detect","broker":"603bb104.d6134","nl":false,"rap":true,"rh":0,"inputs":0,"x":460,"y":160,"wires":[["7a079be5f155366f","64e956d82a770c6b"]]},{"id":"7173ea47b004a475","type":"function","z":"86b3e6eeb5484ac5","name":"function 93","func":"msg.payload = (msg.payload === \"ON\")? true:false;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":240,"wires":[["95af94893f2d6e53","3f9ac8d95ed0645e"]]},{"id":"cb1ffbab67b58d64","type":"function","z":"86b3e6eeb5484ac5","name":"function 94","func":"var a=msg.payload;\nif (a==true)\n    msg.payload=\"on\";\nelse\n    msg.payload=\"off\";   \n    \nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":400,"wires":[["67c41933549d0e8b","9b5d7e426f8692ce","17ca9b9e.d2a564"]]},{"id":"e7ca113edc03d511","type":"debug","z":"86b3e6eeb5484ac5","name":"debug 266","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":730,"y":340,"wires":[]},{"id":"72ee320a21953114","type":"debug","z":"86b3e6eeb5484ac5","name":"debug 267","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":730,"y":200,"wires":[]},{"id":"9b5d7e426f8692ce","type":"debug","z":"86b3e6eeb5484ac5","name":"debug 268","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":970,"y":360,"wires":[]},{"id":"e535f7a246f73c7a","type":"ui_button","z":"86b3e6eeb5484ac5","name":"","group":"d93d93998de35374","order":15,"width":3,"height":1,"passthru":false,"label":"ON","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"true","payloadType":"bool","topic":"topic","topicType":"msg","x":550,"y":400,"wires":[["cb1ffbab67b58d64"]]},{"id":"ba5236a0cb5158a5","type":"ui_button","z":"86b3e6eeb5484ac5","name":"","group":"d93d93998de35374","order":16,"width":3,"height":1,"passthru":false,"label":"OFF","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"false","payloadType":"bool","topic":"topic","topicType":"msg","x":550,"y":460,"wires":[["cb1ffbab67b58d64"]]},{"id":"3f9ac8d95ed0645e","type":"debug","z":"86b3e6eeb5484ac5","name":"debug 269","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":970,"y":300,"wires":[]},{"id":"64e956d82a770c6b","type":"delay","z":"86b3e6eeb5484ac5","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":660,"y":120,"wires":[["f4f23a169be7144a"]]},{"id":"f4f23a169be7144a","type":"function","z":"86b3e6eeb5484ac5","name":"function 95","func":"msg.payload=\"\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":120,"wires":[["7a079be5f155366f"]]},{"id":"36b481c29223d734","type":"group","z":"86b3e6eeb5484ac5","name":"","style":{"fill":"#ffff00","label":true},"nodes":["17ca9b9e.d2a564","872b1d71.45076","f81160301e1b0e63","c6ac5bac.e91e38","9d39dfc1.aff32"],"x":454,"y":519,"w":592,"h":242},{"id":"17ca9b9e.d2a564","type":"function","z":"86b3e6eeb5484ac5","g":"36b481c29223d734","name":"Test","func":"msg.color = (msg.payload === \"on\")?\"lime\":\"red\";\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":770,"y":640,"wires":[["872b1d71.45076","f81160301e1b0e63"]]},{"id":"872b1d71.45076","type":"ui_text","z":"86b3e6eeb5484ac5","g":"36b481c29223d734","group":"d93d93998de35374","order":9,"width":3,"height":2,"name":"","label":"LED","format":"<font color={{msg.color}} ><i class=\"fa fa-circle\" style=\"font-size:48px;\"></i></font>","layout":"row-left","className":"","x":970,"y":560,"wires":[]},{"id":"f81160301e1b0e63","type":"ui_template","z":"86b3e6eeb5484ac5","g":"36b481c29223d734","group":"d93d93998de35374","name":"","order":10,"width":3,"height":3,"format":"<svg width=\"120\" height=\"200\">\n    <circle id=\"circle1\" cx=\"50\" cy=\"50\" r=\"40\" stroke={{msg.color}} fill=\"transparent\" stroke-width=\"20\"/>\n</svg>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":960,"y":700,"wires":[[]]},{"id":"c6ac5bac.e91e38","type":"inject","z":"86b3e6eeb5484ac5","g":"36b481c29223d734","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"on","payloadType":"str","x":550,"y":560,"wires":[["17ca9b9e.d2a564","7173ea47b004a475"]]},{"id":"9d39dfc1.aff32","type":"inject","z":"86b3e6eeb5484ac5","g":"36b481c29223d734","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"off","payloadType":"str","x":550,"y":720,"wires":[["17ca9b9e.d2a564","7173ea47b004a475"]]},{"id":"d93d93998de35374","type":"ui_group","name":"Group 1 for MQTT","tab":"14f030d69035e0d1","order":1,"disp":true,"width":11,"collapse":false,"className":""},{"id":"603bb104.d6134","type":"mqtt-broker","name":"","broker":"broker.mqtt-dashboard.com","port":"1883","clientid":"","autoConnect":true,"usetls":false,"compatmode":false,"protocolVersion":"4","keepalive":"15","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"14f030d69035e0d1","type":"ui_tab","name":"Tab  for RFID Control","icon":"dashboard","order":101,"disabled":false,"hidden":false}]

2024年2月10日 星期六

寫入與讀取Mifare卡片資料的程式碼

 寫入與讀取Mifare卡片資料的程式碼

https://swf.com.tw/?p=941


寫入與讀取



讀取



讀取
/*
源自於
https://swf.com.tw/?p=941
使用到的MFRC522程式物件的方法和屬性:
MFRC522物件.PCD_Authenticate():驗證金鑰,相當於比對輸入密碼和卡片裡的密碼,唯通過驗證才能存取區段資料。
MFRC522物件.GetStatusCodeName():取得狀態碼的名稱
MFRC522物件.MIFARE_Read():讀取指定區塊的內容
MFRC522物件.MIFARE_Write():在指定區塊寫入資料
MFRC522物件.PICC_DumpMifareClassicSectorToSerial():在序列埠監控視窗顯示指定的區段內容
*/

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN      27        // 讀卡機的重置腳位
#define SS_PIN       5        // 晶片選擇腳位

MFRC522 mfrc522(SS_PIN, RST_PIN);    // 建立MFRC522物件

MFRC522::MIFARE_Key key;  // 儲存金鑰

byte sector = 15;   // 指定讀寫的「區段」,可能值:0~15
byte block = 1;     // 指定讀寫的「區塊」,可能值:0~3
byte blockData[16];
//byte blockData = "KeepHacking";   // 最多可存入16個字元
// 若要清除區塊內容,請寫入16個 0
//byte blockData[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

// 暫存讀取區塊內容的陣列,MIFARE_Read()方法要求至少要18位元組空間,來存放16位元組。
byte buffer[18];
bool WrOK=false;
bool RdOK=false;
byte WrCNT=0;

MFRC522::StatusCode status;
//===================================================================
void writeBlock(byte _sector, byte _block, byte _blockData[]) {
  if (_sector < 0 || _sector > 15 || _block < 0 || _block > 3) {
    // 顯示「區段或區塊碼錯誤」,然後結束函式。
    Serial.println(F("Wrong sector or block number."));
    return;
  }

  if (_sector == 0 && _block == 0) {
    // 顯示「第一個區塊只能讀取」,然後結束函式。
    Serial.println(F("First block is read-only."));
    return;
  }

  byte blockNum = _sector * 4 + _block;  // 計算區塊的實際編號(0~63)
  byte trailerBlock = _sector * 4 + 3;   // 控制區塊編號

  // 驗證金鑰
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
  // 若未通過驗證…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("PCD_Authenticate() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  // 在指定區塊寫入16位元組資料
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockNum, _blockData, 16);
  // 若寫入不成功…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("MIFARE_Write() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  // 顯示「寫入成功!」
  Serial.println(F("Data was written."));
  WrOK=true;

}

void readBlock(byte _sector, byte _block, byte _blockData[])  {
  if (_sector < 0 || _sector > 15 || _block < 0 || _block > 3) {
    // 顯示「區段或區塊碼錯誤」,然後結束函式。
    Serial.println(F("Wrong sector or block number."));
    return;
  }

  byte blockNum = _sector * 4 + _block;  // 計算區塊的實際編號(0~63)
  byte trailerBlock = _sector * 4 + 3;   // 控制區塊編號

  // 驗證金鑰
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
  // 若未通過驗證…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("PCD_Authenticate() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  byte buffersize = 18;
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockNum, _blockData, &buffersize);

  // 若讀取不成功…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("MIFARE_read() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  // 顯示「讀取成功!」
  Serial.println(F("Data was read."));
  RdOK=true;

}

void setup() {
  Serial.begin(115200);
  SPI.begin();               // 初始化SPI介面
  mfrc522.PCD_Init();        // 初始化MFRC522卡片

  Serial.println(F("Please scan MIFARE Classic card..."));

  // 準備金鑰(用於key A和key B),出廠預設為6組 0xFF。
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }

}

void loop() {
  // 查看是否感應到卡片
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return;  // 退回loop迴圈的開頭
  }

  // 選取一張卡片
  if ( ! mfrc522.PICC_ReadCardSerial()) {  // 若傳回1,代表已讀取到卡片的ID
    return;
  }
 
 
  //===================================================================
  RdOK=false;
  readBlock(sector, block, buffer);      // 區段編號、區塊編號、存放讀取資料的陣列
  if (RdOK==true) {
    Serial.println("Read OK");
  }
  else
  {
    Serial.println("Read failure");  
  }

  Serial.print(F("Read block: "));        // 顯示儲存讀取資料的陣列元素值
  for (byte i = 0 ; i < 16 ; i++) {
    Serial.write (buffer[i]);
  }
  Serial.println();

  // 令卡片進入停止狀態
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1(); // stop encryption on PCD
  WrCNT+=1;
  if (WrCNT==100)
    WrCNT=0;
}


寫入與讀取

/*

源自於
https://swf.com.tw/?p=941
使用到的MFRC522程式物件的方法和屬性:
MFRC522物件.PCD_Authenticate():驗證金鑰,相當於比對輸入密碼和卡片裡的密碼,唯通過驗證才能存取區段資料。
MFRC522物件.GetStatusCodeName():取得狀態碼的名稱
MFRC522物件.MIFARE_Read():讀取指定區塊的內容
MFRC522物件.MIFARE_Write():在指定區塊寫入資料
MFRC522物件.PICC_DumpMifareClassicSectorToSerial():在序列埠監控視窗顯示指定的區段內容
*/

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN      27        // 讀卡機的重置腳位
#define SS_PIN       5        // 晶片選擇腳位

MFRC522 mfrc522(SS_PIN, RST_PIN);    // 建立MFRC522物件

MFRC522::MIFARE_Key key;  // 儲存金鑰

byte sector = 15;   // 指定讀寫的「區段」,可能值:0~15
byte block = 1;     // 指定讀寫的「區塊」,可能值:0~3
byte blockData[16];
//byte blockData = "KeepHacking";   // 最多可存入16個字元
// 若要清除區塊內容,請寫入16個 0
//byte blockData[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

// 暫存讀取區塊內容的陣列,MIFARE_Read()方法要求至少要18位元組空間,來存放16位元組。
byte buffer[18];
bool WrOK=false;
bool RdOK=false;
byte WrCNT=0;

MFRC522::StatusCode status;
//===================================================================
void writeBlock(byte _sector, byte _block, byte _blockData[]) {
  if (_sector < 0 || _sector > 15 || _block < 0 || _block > 3) {
    // 顯示「區段或區塊碼錯誤」,然後結束函式。
    Serial.println(F("Wrong sector or block number."));
    return;
  }

  if (_sector == 0 && _block == 0) {
    // 顯示「第一個區塊只能讀取」,然後結束函式。
    Serial.println(F("First block is read-only."));
    return;
  }

  byte blockNum = _sector * 4 + _block;  // 計算區塊的實際編號(0~63)
  byte trailerBlock = _sector * 4 + 3;   // 控制區塊編號

  // 驗證金鑰
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
  // 若未通過驗證…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("PCD_Authenticate() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  // 在指定區塊寫入16位元組資料
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockNum, _blockData, 16);
  // 若寫入不成功…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("MIFARE_Write() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  // 顯示「寫入成功!」
  Serial.println(F("Data was written."));
  WrOK=true;

}

void readBlock(byte _sector, byte _block, byte _blockData[])  {
  if (_sector < 0 || _sector > 15 || _block < 0 || _block > 3) {
    // 顯示「區段或區塊碼錯誤」,然後結束函式。
    Serial.println(F("Wrong sector or block number."));
    return;
  }

  byte blockNum = _sector * 4 + _block;  // 計算區塊的實際編號(0~63)
  byte trailerBlock = _sector * 4 + 3;   // 控制區塊編號

  // 驗證金鑰
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
  // 若未通過驗證…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("PCD_Authenticate() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  byte buffersize = 18;
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockNum, _blockData, &buffersize);

  // 若讀取不成功…
  if (status != MFRC522::STATUS_OK) {
    // 顯示錯誤訊息
    Serial.print(F("MIFARE_read() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    return;
  }

  // 顯示「讀取成功!」
  Serial.println(F("Data was read."));
  RdOK=true;

}

void setup() {
  Serial.begin(115200);
  SPI.begin();               // 初始化SPI介面
  mfrc522.PCD_Init();        // 初始化MFRC522卡片

  Serial.println(F("Please scan MIFARE Classic card..."));

  // 準備金鑰(用於key A和key B),出廠預設為6組 0xFF。
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }

}

void loop() {
  // 查看是否感應到卡片
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    return;  // 退回loop迴圈的開頭
  }

  // 選取一張卡片
  if ( ! mfrc522.PICC_ReadCardSerial()) {  // 若傳回1,代表已讀取到卡片的ID
    return;
  }
 
  WrOK=false;

  String WriteSTR="alex9ufo RFID";   // 最多可存入16個字元
  String stringOne = String(WrCNT, DEC);
  WriteSTR+=stringOne;
  WriteSTR.getBytes(blockData, 16);
  for (byte i = 0 ; i < 16 ; i++) {
    Serial.write (blockData[i]);
  }
  writeBlock(sector, block, blockData);  // 區段編號、區塊編號、包含寫入資料的陣列
 
  if (WrOK==true) {
    Serial.println("Write OK");
  } else
  {
    Serial.println("Write failure");  
  }

  //===================================================================
  RdOK=false;
  readBlock(sector, block, buffer);      // 區段編號、區塊編號、存放讀取資料的陣列
  if (RdOK==true) {
    Serial.println("Read OK");
  }
  else
  {
    Serial.println("Read failure");  
  }

  Serial.print(F("Read block: "));        // 顯示儲存讀取資料的陣列元素值
  for (byte i = 0 ; i < 16 ; i++) {
    Serial.write (buffer[i]);
  }
  Serial.println();

  // 令卡片進入停止狀態
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1(); // stop encryption on PCD
  WrCNT+=1;
  if (WrCNT==100)
    WrCNT=0;
}

2024年2月5日 星期一

opendata 的取得

opendata 的取得








https://data.moenv.gov.tw

https://data.moenv.gov.tw/api/v2/aqf_p_01?api_key=aa1b57c2bf-24c2-4c557-980f-a120775c2caec


 "records": [

    {

      "sitename": "塔塔加",

      "uvi": "0",

      "unit": "環境部",

      "county": "嘉義縣",

      "wgs84_lon": "120,51,35",

      "wgs84_lat": "23,28,19",

      "datacreationdate": "2024-02-05 19:00"

    },

    {

      "sitename": "阿里山",

      "uvi": "0",

      "unit": "環境部",

      "county": "嘉義縣",

      "wgs84_lon": "120,48,8",

      "wgs84_lat": "23,30,37",

      "datacreationdate": "2024-02-05 19:00"

    },

    {

      "sitename": "屏東",

      "uvi": "0",

      "unit": "環境部",

      "county": "屏東縣",

      "wgs84_lon": "120,29,17",

      "wgs84_lat": "22,40,23",

      "datacreationdate": "2024-02-05 19:00"

    },

    {

      "sitename": "橋頭",

      "uvi": "0",

      "unit": "環境部",

      "county": "高雄市",

      "wgs84_lon": "120,18,20",

      "wgs84_lat": "22,45,27",

      "datacreationdate": "2024-02-05 19:00"

    },

    {

      "sitename": "新營",

      "uvi": "0",

      "unit": "環境部",

      "county": "臺南市",

      "wgs84_lon": "120,19,2",

      "wgs84_lat": "23,18,20",

      "datacreationdate": "2024-02-05 19:00"

    },

2024年2月4日 星期日

Node-Red 取得opendata PM2.5 經MQTT Server 回傳至Line Notify+Node-Red

Node-Red 取得opendata PM2.5 經MQTT Server 回傳至Line Notify+Node-Red






  {
            "sitename": "沙鹿",
            "county": "臺中市",
            "aqi": "37",
            "pollutant": "",
            "status": "良好",
            "so2": "1.5",
            "co": "0.36",
            "o3": "22.9",
            "o3_8hr": "26.8",
            "pm10": "37",
            "pm2.5": "14",
            "no2": "15",
            "nox": "18.3",
            "no": "3.3",
            "wind_speed": "3",
            "wind_direc": "320",
            "publishtime": "2024\/02\/05 16:00:00",
            "co_8hr": "0.3",
            "pm2.5_avg": "11.5",
            "pm10_avg": "23",
            "so2_avg": "1",
            "longitude": "120.568794",
            "latitude": "24.225628",
            "siteid": "29"
        },
        {
            "sitename": "大里",
            "county": "臺中市",
            "aqi": "31",
            "pollutant": "",
            "status": "良好",
            "so2": "1.1",
            "co": "0.26",
            "o3": "34.1",
            "o3_8hr": "34.2",
            "pm10": "8",
            "pm2.5": "8",
            "no2": "",
            "nox": "",
            "no": "",
            "wind_speed": "1.4",
            "wind_direc": "302",
            "publishtime": "2024\/02\/05 16:00:00",
            "co_8hr": "0.2",
            "pm2.5_avg": "7.9",
            "pm10_avg": "9",
            "so2_avg": "1",
            "longitude": "120.67844444",
            "latitude": "24.09961111",
            "siteid": "30"
        },
        {
            "sitename": "忠明",
            "county": "臺中市",
            "aqi": "37",
            "pollutant": "",
            "status": "良好",
            "so2": "1.6",
            "co": "0.3",
            "o3": "31.1",
            "o3_8hr": "28.2",
            "pm10": "14",
            "pm2.5": "11",
            "no2": "13.7",
            "nox": "14.5",
            "no": "0.7",
            "wind_speed": "3.1",
            "wind_direc": "302",
            "publishtime": "2024\/02\/05 16:00:00",
            "co_8hr": "0.3",
            "pm2.5_avg": "11.3",
            "pm10_avg": "19",
            "so2_avg": "1",
            "longitude": "120.641092",
            "latitude": "24.151958",
            "siteid": "31"
        },

https://data.moenv.gov.tw/api/v2/aqx_p_432?api_key=e81dd42e6-9b18b-43f8-991e-b3dee723a52d&limit=1000&sort=ImportDate%20desc&format=JSON

改成自己的金鑰



[{"id":"89ad6b4aa5ce1629","type":"inject","z":"74c417894ebe4fde","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"120","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":100,"y":60,"wires":[["00c7035b7f63d64c"]]},{"id":"1e8a983474b291e8","type":"function","z":"74c417894ebe4fde","name":"func2","func":"var a = msg.payload;\nvar pm25;\n\na=Array.from(a);\n\na.forEach(function(e,i){\n    if(e.sitename=='大里'){\n        pm25 = e['pm2.5_avg'];\n    }\n});\nmsg.payload=pm25;\n\n//to store values use:\nflow.set('flowPM25',pm25);\n\nreturn msg;\n\n\n\n//var flowPM25=flow.get('flowPM25') || 0;\n//var flowStatus=flow.get('flowStatus') ;\n//var flowPublishTime=flow.get('flowPublishTime') ;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":390,"y":180,"wires":[["d922fbd375c022d2","5e07f4ba688793a1"]]},{"id":"5e07f4ba688793a1","type":"debug","z":"74c417894ebe4fde","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":550,"y":180,"wires":[]},{"id":"00c7035b7f63d64c","type":"http request","z":"74c417894ebe4fde","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://data.moenv.gov.tw/api/v2/aqx_p_432?api_key=e8dd42e6-9b8b-43f8-991e-b3dee723a52d&limit=1000&sort=ImportDate%20desc&format=JSON","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":240,"y":60,"wires":[["439bff74d05a15eb","ccedd3ceed710617"]]},{"id":"ebd8971451667511","type":"ui_text","z":"74c417894ebe4fde","group":"f64f0af7fe862c9e","order":3,"width":0,"height":0,"name":"","label":"空氣品質","format":"{{msg.payload}}","layout":"row-left","className":"","x":560,"y":400,"wires":[]},{"id":"3e40ccdb93825cd5","type":"ui_text","z":"74c417894ebe4fde","group":"f64f0af7fe862c9e","order":4,"width":0,"height":0,"name":"","label":"發佈時間","format":"{{msg.payload}}","layout":"row-left","className":"","x":560,"y":440,"wires":[]},{"id":"d922fbd375c022d2","type":"ui_gauge","z":"74c417894ebe4fde","name":"","group":"f64f0af7fe862c9e","order":1,"width":8,"height":4,"gtype":"gage","title":"細懸浮微粒PM2.5","label":"units","format":"{{value}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","className":"","x":590,"y":220,"wires":[]},{"id":"439bff74d05a15eb","type":"debug","z":"74c417894ebe4fde","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":390,"y":60,"wires":[]},{"id":"ccedd3ceed710617","type":"function","z":"74c417894ebe4fde","name":"func1","func":"msg.payload= msg.payload.records\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":210,"y":140,"wires":[["017d6225cc331fd5","1dac0906f16a8150","1e8a983474b291e8"]]},{"id":"017d6225cc331fd5","type":"debug","z":"74c417894ebe4fde","name":"debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":390,"y":140,"wires":[]},{"id":"1dac0906f16a8150","type":"function","z":"74c417894ebe4fde","name":"function 88","func":"for (var i = 0; i < msg.payload.length; i++) {\n    if(msg.payload[i].sitename==\"大里\"){  \n        var newMsg = {};\n        newMsg.payload = msg.payload[i];\n        node.send(newMsg);\n    }\n}\nreturn null;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":230,"y":280,"wires":[["aa3cf2a22e96c8eb","941d2f1dc53652e3","399739dcb882c814","665716649f881332"]]},{"id":"aa3cf2a22e96c8eb","type":"debug","z":"74c417894ebe4fde","name":"debug  ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":430,"y":280,"wires":[]},{"id":"941d2f1dc53652e3","type":"json","z":"74c417894ebe4fde","name":"","property":"payload","action":"str","pretty":false,"x":430,"y":340,"wires":[["4431b433e1f7c50d"]]},{"id":"4431b433e1f7c50d","type":"debug","z":"74c417894ebe4fde","name":"debug ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":550,"y":340,"wires":[]},{"id":"399739dcb882c814","type":"function","z":"74c417894ebe4fde","name":"func2","func":"var PM25,Status,PublishTime;\nStatus=msg.payload.status;        \nmsg.payload=Status;\n\n//to store values use:\nflow.set('flowStatus',Status);\n\nreturn msg;\n\n\n\n//var flowPM25=flow.get('flowPM25') || 0;\n//var flowStatus=flow.get('flowStatus') ;\n//var flowPublishTime=flow.get('flowPublishTime') ;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":400,"wires":[["ebd8971451667511"]]},{"id":"665716649f881332","type":"function","z":"74c417894ebe4fde","name":"func2","func":"var PM25,Status,PublishTime;\n\n \nPublishTime=msg.payload.publishtime;\nmsg.payload=PublishTime;\n\n//to store values use:\nflow.set('flowPublishTime',PublishTime);\n\nreturn msg;\n\n\n\n//var flowPM25=flow.get('flowPM25') || 0;\n//var flowStatus=flow.get('flowStatus') ;\n//var flowPublishTime=flow.get('flowPublishTime') ;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":440,"wires":[["3e40ccdb93825cd5","a9f29e53590f796f"]]},{"id":"6f6d4c66bbb39396","type":"mqtt out","z":"74c417894ebe4fde","name":"PM25","topic":"alex9ufo/PM25","qos":"1","retain":"true","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"603bb104.d6134","x":590,"y":520,"wires":[]},{"id":"7d6d280b84907c23","type":"mqtt in","z":"74c417894ebe4fde","name":"PM25","topic":"alex9ufo/PM25","qos":"1","datatype":"auto-detect","broker":"603bb104.d6134","nl":false,"rap":true,"rh":0,"inputs":0,"x":790,"y":120,"wires":[["305dfaa1dd1b079f","158f73016922f32e","e9fea79b02646127"]]},{"id":"490e6bf9455d882d","type":"function","z":"74c417894ebe4fde","name":"set  flow variable","func":"//var PM25,Status,PublishTime;\nvar flowPM25=flow.get('flowPM25') || 0;\nvar flowStatus=flow.get('flowStatus') ;\nvar flowPublishTime=flow.get('flowPublishTime') ;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":240,"y":380,"wires":[[]]},{"id":"9f1a67fe02033640","type":"inject","z":"74c417894ebe4fde","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":80,"y":380,"wires":[["490e6bf9455d882d"]]},{"id":"3490e14f9ff8fe75","type":"function","z":"74c417894ebe4fde","name":"function ","func":"var f1=flow.get('flowPM25') || 0;\nvar f2=flow.get('flowStatus') ;\nvar f3=flow.get('flowPublishTime') ;\n\nvar v4=\"MQTT發行訊息為 台中市大里區 , PM2.5=\";\nv4+=f1;\nv4+=\"天候狀況\";\nv4+=f2;\n\nv4+=\"發布時間\";\nv4+=f3;\n\nmsg.payload=v4;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":520,"wires":[["6f6d4c66bbb39396","d61fbc10f8fab89f"]]},{"id":"a9f29e53590f796f","type":"delay","z":"74c417894ebe4fde","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":260,"y":520,"wires":[["3490e14f9ff8fe75"]]},{"id":"305dfaa1dd1b079f","type":"debug","z":"74c417894ebe4fde","name":"debug 262","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":970,"y":60,"wires":[]},{"id":"d61fbc10f8fab89f","type":"ui_text","z":"74c417894ebe4fde","group":"551944bcad742ef1","order":1,"width":8,"height":3,"name":"","label":"MQTT發行訊息","format":"{{msg.payload}}","layout":"row-left","className":"","x":600,"y":600,"wires":[]},{"id":"158f73016922f32e","type":"ui_text","z":"74c417894ebe4fde","group":"551944bcad742ef1","order":3,"width":8,"height":3,"name":"","label":"MQTT接收訊息","format":"{{msg.payload}}","layout":"row-left","className":"","x":980,"y":120,"wires":[]},{"id":"e9fea79b02646127","type":"function","z":"74c417894ebe4fde","name":"function ","func":"msg.payload=\" --訂閱MQTT的訊息---\" +msg.payload;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":960,"y":180,"wires":[["8e5a33f864d47f7a","a65bd6bb901d2cd7"]]},{"id":"8e5a33f864d47f7a","type":"function","z":"74c417894ebe4fde","name":"Set Line API ","func":"msg.headers = {'content-type':'application/x-www-form-urlencoded','Authorization':'Bearer A4wwPNh2WqB7dlfeQyyIAwtggn1kfZSI5LkkCdia1gB'};\nmsg.payload = {\"message\":msg.payload};\nreturn msg;\n\n//oR7KdXvK1eobRr2sRRgsl4PMq23DjDlhfUs96SyUBZu","outputs":1,"noerr":0,"x":1130,"y":180,"wires":[["52591b7051c6ec4f"]]},{"id":"52591b7051c6ec4f","type":"http request","z":"74c417894ebe4fde","name":"","method":"POST","ret":"txt","paytoqs":false,"url":"https://notify-api.line.me/api/notify","tls":"","persist":false,"proxy":"","authType":"","x":1280,"y":180,"wires":[["648619e21e91f6e2"]]},{"id":"648619e21e91f6e2","type":"debug","z":"74c417894ebe4fde","name":"debug 263","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1430,"y":180,"wires":[]},{"id":"01e2331858995585","type":"comment","z":"74c417894ebe4fde","name":"Line Notify Message ","info":"","x":1210,"y":140,"wires":[]},{"id":"a65bd6bb901d2cd7","type":"delay","z":"74c417894ebe4fde","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":1100,"y":260,"wires":[["41cc1b799336aeb0"]]},{"id":"41cc1b799336aeb0","type":"ui_audio","z":"74c417894ebe4fde","name":"","group":"551944bcad742ef1","voice":"Microsoft Hanhan - Chinese (Traditional, Taiwan)","always":"","x":1260,"y":260,"wires":[]},{"id":"f64f0af7fe862c9e","type":"ui_group","name":"2024 大里","tab":"ba052423.3729f8","order":3,"disp":true,"width":8,"collapse":false,"className":""},{"id":"603bb104.d6134","type":"mqtt-broker","name":"","broker":"broker.mqtt-dashboard.com","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"15","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"551944bcad742ef1","type":"ui_group","name":"Line+MQTT ","tab":"ba052423.3729f8","order":4,"disp":true,"width":8,"collapse":false,"className":""},{"id":"ba052423.3729f8","type":"ui_tab","name":"AQI","icon":"dashboard","order":1}]

2024年4月24日 星期三 Node-Red Dashboard UI Template + AngularJS 參考 AngularJS教學 --2

 2024年4月24日 星期三 Node-Red Dashboard UI Template + AngularJS 參考 AngularJS教學 --2 AngularJS 實例 <!DOCTYPE html> <html> <head> &...