2020年12月27日 星期日

ESP32 + PN532 NFC Reader I2C Mode

 ESP32 + PN532 NFC Reader  I2C Mode 

參考來源

http://www.elechouse.com/elechouse/images/product/PN532_module_V3/PN532_%20Manual_V3.pdf

https://www.taiwaniot.com.tw/product/pn532-nfc-rfid-v3-%E6%A8%A1%E7%B5%84-%E8%BF%91%E5%A0%B4%E9%80%9A%E8%A8%8A-%E6%94%AF%E6%8C%81-android-%E6%89%8B%E6%A9%9F%E9%80%9A%E8%A8%8A/

使用Library   https://github.com/elechouse/PN532

PN532 NFC RFID V3 模組 近場通訊 支持 Android 手機通訊

NFC又稱近距離無線通信,是一種短距離的高頻無線通信技術,允許電子設備之間進行非接觸式點對點數據傳輸(在十厘米內)交換數據。這個技術由免接觸式射頻識別(RFID)演變而來,並向下兼容RFID,最早由Sony和Philips各自開發成功,主要用於手機等手持設備中提供M2M(Machine to Machine)的通信。由於近場通訊具有天然的安全性,因此,NFC技術被認為在手機支付等領域具有很大的應用前景。

利用NXP532的幾乎所有管腳都,因此用戶可以輕鬆的調試和把玩。該進場通訊模組默認才用I2C作為數據接埠

  • 支援I2C、SPI、HSU(高速UART),可以很容易在這些通訊方式之間進行切換,通過板子左下角的撥動開關即可輕鬆實現通訊方式的切換
  • Work in NFC Mode or RFID reader/writer Mode(可工作在NFC狀態、RFID讀或者寫狀態)
  • RFID reader/writer supports: (RDID讀寫支援)
    Mifare 1k, 4k, Ultralight, 和DesFire 卡
    ISO/IEC 14443-4 cards,如CD97BX, CD light, Desfire, P5CN072 (SMX)
    Innovision Jewel cards,如IRT5001 card
    FeliCa cards ,如RCS_860 和RCS_854
  • 即插即用,相容Arduino
  • 板內天線, 支援 1cm-5ccm 通訊距離
  • On-board level shifter, 標準5V TTL for I2C and UART, 3.3V TTL for SPI
  • 可工作在 RFID 讀/寫模式
  • 可工作在 1443-A card 或者 虛擬card模式
  • 可與其他NFC設備交換數據,如Android手機
  • Interface 數據埠 I2C 是默認數據埠. 用戶也可根據自己需要利用引出的管腳改變數據傳輸方式,如串埠,SPI等

Connecting an I2C device to an ESP32 is normally as simple as connecting GND to GND, SDA to SDA, SCL to SCL and a positive power supply to a peripheral, usually 3.3V (but it depends on the module you’re using).

I2C DeviceESP32
SDASDA (default is GPIO 21)
SCLSCL (default is GPIO 22)
GNDGND
VCCusually 3.3V or 5V







使用Library   https://github.com/elechouse/PN532

解壓縮後存入 目錄 C:\Users\alex\Documents\Arduino\libraries







將其他MODE 刪除掉 保留I2C Mode

支持I2C .SPI  和HSU(高速UART)
I2C --> Wire,h
SPI --> SPI.h 
HSU (High Speed UART) -->PN532_HSU.h
 


// SDA  SDA (default is GPIO 21) ESP32
// SCL SCL (default is GPIO 22)
/* it will be switch to I2C Mode*/
#include <Wire.h>
  #include <PN532_I2C.h>
  #include <PN532.h>
  #include <NfcAdapter.h>
  
  PN532_I2C pn532i2c(Wire);
  PN532 nfc(pn532i2c);

void setup(void) {
  Serial.begin(115200);
  Serial.println("Hello!");
  Serial.println("SDA (default is GPIO 21) SCL (default is GPIO 22)");

  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // halt
  }
  
  // Got ok data, print it out!
  Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX); 
  Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); 
  Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
  
  // Set the max number of retry attempts to read from a card
  // This prevents us from waiting forever for a card, which is
  // the default behaviour of the PN532.
  nfc.setPassiveActivationRetries(0xFF);
  
  // configure board to read RFID tags
  nfc.SAMConfig();
    
  Serial.println("Waiting for an ISO14443A card");
}

void loop(void) {
  boolean success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;                        // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
  
  // Wait for an ISO14443A type cards (Mifare, etc.).  When one is found
  // 'uid' will be populated with the UID, and uidLength will indicate
  // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, &uid[0], &uidLength);
  
  if (success) {
    Serial.println("Found a card!");
    Serial.print("UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes");
    Serial.print("UID Value: ");
    for (uint8_t i=0; i < uidLength; i++) 
    {
      Serial.print(" 0x");Serial.print(uid[i], HEX); 
    }
    Serial.println("");
    // Wait 1 second before continuing
    delay(1000);
  }
  else
  {
    // PN532 probably timed out waiting for a card
    Serial.println("Timed out waiting for a card");
  }
}

2020年12月26日 星期六

Use Different I2C Pins with ESP32 (change default I2C pins 變更內定I2C的腳位)

Use Different I2C Pins with ESP32 (change default I2C pins)

源自於 https://randomnerdtutorials.com/esp32-i2c-communication-arduino-ide/

With the ESP32 you can set almost any pin to have I2C capabilities, you just need to set that in your code.

When using the ESP32 with the Arduino IDE, use the Wire.h library to communicate with devices using I2C. With this library, you initialize the I2C as follows:

Wire.begin(I2C_SDA, I2C_SCL);

So, you just need to set your desired SDA and SCL GPIOs on the I2C_SDA and I2C_SCL variables.

However, if you’re using libraries to communicate with those sensors, this might not work and it might be a bit tricky to select other pins. That happens because those libraries might overwrite your pins if you don’t pass your own Wire instance when initializing the library.

In those cases, you need to take a closer look at the .cpp library files and see how to pass your own TwoWire parameters.

For example, if you take a closer look at the Adafruit BME280 library, you’ll find out that you can pass your own TwoWire to the begin() method.

Adafruit_BME280 library using different I2C pins

So, the example sketch to read from the BME280 using other pins, for example GPIO 33 as SDA and and GPIO 32 as SCL is as follows.

/*
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-i2c-communication-arduino-ide/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define I2C_SDA 33
#define I2C_SCL 32

#define SEALEVELPRESSURE_HPA (1013.25)

TwoWire I2CBME = TwoWire(0);
Adafruit_BME280 bme;

unsigned long delayTime;

void setup() {
  Serial.begin(115200);
  Serial.println(F("BME280 test"));
  I2CBME.begin(I2C_SDA, I2C_SCL, 100000);

  bool status;

  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  status = bme.begin(0x76, &I2CBME);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  Serial.println("-- Default Test --");
  delayTime = 1000;

  Serial.println();
}

void loop() { 
  printValues();
  delay(delayTime);
}

void printValues() {
  Serial.print("Temperature = ");
  Serial.print(bme.readTemperature());
  Serial.println(" *C");
  
  // Convert temperature to Fahrenheit
  /*Serial.print("Temperature = ");
  Serial.print(1.8 * bme.readTemperature() + 32);
  Serial.println(" *F");*/
  
  Serial.print("Pressure = ");
  Serial.print(bme.readPressure() / 100.0F);
  Serial.println(" hPa");

  Serial.print("Approx. Altitude = ");
  Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
  Serial.println(" m");

  Serial.print("Humidity = ");
  Serial.print(bme.readHumidity());
  Serial.println(" %");

  Serial.println();
}

View raw code

ESP32 with BME280 on Different I2C Pins Schematic Diagram Wiring
Click image to enlarge

Let’s take a look at the relevant parts to use other I2C pins.

First, define your new I2C pins on the I2C_SDA and I2C_SCL variables. In this case, we’re using GPIO 33 and GPIO 32.

#define I2C_SDA 33
#define I2C_SCL 32

Create a new TwoWire instance. In this case, it’s called I2CBME. This simply creates an I2C bus.

TwoWire I2CBME = TwoWire(0);

In the setup(), initialize the I2C communication with the pins you’ve defined earlier. The third parameter is the clock frequency.

I2CBME.begin(I2C_SDA, I2C_SCL, 400000);

Finally, initialize a BME280 object with your sensor address and your TwoWire object.

status = bme.begin(0x76, &I2CBME);

After this, you can use use the usual methods on your bme object to request temperature, humidity and pressure.

Note: if the library you’re using uses a statement like wire.begin() in its file, you may need to comment that line, so that you can use your own pins.

2020年12月25日 星期五

Node-Red DS1820 & Servo Motor

   Node-Red DS1820 & Servo Motor


Node-Red程式1



[{"id":"fcedd20e.08ac6","type":"mqtt out","z":"cb74fe7b.b6c12","name":"","topic":"alex9ufo/inTopic/servo","qos":"1","retain":"false","broker":"e4d9b72d.d14398","x":640,"y":140,"wires":[]},{"id":"a10598db.465ae8","type":"ui_button","z":"cb74fe7b.b6c12","name":"","group":"309b9c97.50a4c4","order":1,"width":0,"height":0,"label":"Min  0%","color":"black","icon":"","payload":"0","payloadType":"num","topic":"","x":217.00003051757812,"y":141.66653442382812,"wires":[["4af995b4.bbdc1c"]]},{"id":"694c6869.decc58","type":"ui_slider","z":"cb74fe7b.b6c12","name":"","label":"Servo","group":"309b9c97.50a4c4","order":4,"width":0,"height":0,"passthru":true,"topic":"","min":0,"max":"100","step":1,"x":214.00003051757812,"y":289.66650390625,"wires":[["4af995b4.bbdc1c"]]},{"id":"bd4806ea.45dcb8","type":"ui_gauge","z":"cb74fe7b.b6c12","name":"","group":"309b9c97.50a4c4","order":5,"width":0,"height":0,"gtype":"donut","title":"Servo position ","label":"position ","format":"{{value}}","min":0,"max":"100","colors":["#00b500","#e6e600","#ca3838"],"x":600,"y":200,"wires":[]},{"id":"2db2b1f4.3fb7fe","type":"ui_button","z":"cb74fe7b.b6c12","name":"","group":"309b9c97.50a4c4","order":3,"width":0,"height":0,"label":"Max 100%","color":"black","icon":"","payload":"100","payloadType":"num","topic":"","x":219.00003051757812,"y":230.33319091796875,"wires":[["4af995b4.bbdc1c"]]},{"id":"4af995b4.bbdc1c","type":"function","z":"cb74fe7b.b6c12","name":"node","func":"\nreturn msg;","outputs":1,"noerr":0,"x":405.00006103515625,"y":175.33316040039062,"wires":[["fcedd20e.08ac6","bd4806ea.45dcb8","ce6c6d8.4ebb79"]]},{"id":"ce6c6d8.4ebb79","type":"link out","z":"cb74fe7b.b6c12","name":"slider","links":["192588bf.5ccc17"],"x":561,"y":291.3331298828125,"wires":[]},{"id":"192588bf.5ccc17","type":"link in","z":"cb74fe7b.b6c12","name":"","links":["ce6c6d8.4ebb79"],"x":295,"y":340,"wires":[["694c6869.decc58"]]},{"id":"fabe3fdf.00386","type":"ui_button","z":"cb74fe7b.b6c12","name":"","group":"309b9c97.50a4c4","order":2,"width":0,"height":0,"passthru":false,"label":"50%","tooltip":"","color":"black","bgcolor":"","icon":"","payload":"50","payloadType":"num","topic":"","x":208.00003051757812,"y":183.66650390625,"wires":[["4af995b4.bbdc1c"]]},{"id":"ef26e619.8b1a28","type":"comment","z":"cb74fe7b.b6c12","name":"ESP8266 MQTT Control Servo","info":"","x":282.0000305175781,"y":70.66650390625,"wires":[]},{"id":"e4d9b72d.d14398","type":"mqtt-broker","name":"","broker":"broker.mqtt-dashboard.com","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"15","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"309b9c97.50a4c4","type":"ui_group","name":"Servo Control","tab":"e0a394f8.695aa8","order":2,"disp":true,"width":"6"},{"id":"e0a394f8.695aa8","type":"ui_tab","name":"ESP8266","icon":"dashboard","order":2}]

Node-Red程式2



[{"id":"7e25c2d5.f0476c","type":"comment","z":"4e792ace.0c0c04","name":"ESP8266 MQTT Temperature DS18B20 ","info":"","x":310,"y":40,"wires":[]},{"id":"866663df.03348","type":"mqtt in","z":"4e792ace.0c0c04","name":"DS1820 Temp Sensor","topic":"alex9ufo/outTopic/temp/DS1820sensor","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":260,"y":160,"wires":[["cce3ab83.b54588","73fc7aab.c2cd94","559ff7f.63d5408","59fa23b0.f46e5c"]]},{"id":"cce3ab83.b54588","type":"debug","z":"4e792ace.0c0c04","name":"","active":true,"console":"false","complete":"payload","x":530,"y":100,"wires":[]},{"id":"73fc7aab.c2cd94","type":"ui_gauge","z":"4e792ace.0c0c04","name":"","group":"4e2af753.086688","order":2,"width":0,"height":0,"gtype":"gage","title":"Temperature","label":"ºC","format":"{{value}}","min":"10","max":"50","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":530,"y":160,"wires":[]},{"id":"559ff7f.63d5408","type":"ui_chart","z":"4e792ace.0c0c04","name":"5  Min","group":"4e2af753.086688","order":0,"width":0,"height":0,"label":"5  Min","chartType":"line","legend":"false","xformat":"H:M:S","interpolate":"linear","nodata":"5  Min","dot":false,"ymin":"10","ymax":"50","removeOlder":"1","removeOlderPoints":"","removeOlderUnit":"60","cutout":"","useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":510,"y":220,"wires":[[]]},{"id":"59fa23b0.f46e5c","type":"ui_chart","z":"4e792ace.0c0c04","name":"1  Hour","group":"4e2af753.086688","order":0,"width":0,"height":0,"label":"1  Hour","chartType":"line","legend":"false","xformat":"H:M:S","interpolate":"linear","nodata":"","dot":false,"ymin":"10","ymax":"50","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":"","useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":510,"y":280,"wires":[[]]},{"id":"28b0dd1b.b3d632","type":"function","z":"4e792ace.0c0c04","name":"","func":"var max=50;\nvar min=10;\nmsg.payload= Math.floor(Math.random()*(max-min+1))+min;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":310,"y":380,"wires":[["beb35e5b.181b6"]]},{"id":"beb35e5b.181b6","type":"mqtt out","z":"4e792ace.0c0c04","name":"DS1820 Temp Sensor","topic":"alex9ufo/outTopic/temp/DS1820sensor","qos":"2","retain":"","broker":"e4d9b72d.d14398","x":520,"y":380,"wires":[]},{"id":"69aed158.bec98","type":"inject","z":"4e792ace.0c0c04","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"4","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":380,"wires":[["28b0dd1b.b3d632"]]},{"id":"e4d9b72d.d14398","type":"mqtt-broker","name":"","broker":"broker.mqtt-dashboard.com","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"15","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"4e2af753.086688","type":"ui_group","name":"ESP8266 DS18B20 MQTT","tab":"e0a394f8.695aa8","order":1,"disp":true,"width":"6"},{"id":"e0a394f8.695aa8","type":"ui_tab","name":"ESP8266","icon":"dashboard","order":2}]

ESP32 MQTT – Publish BME280 Sensor Readings (Arduino IDE)

 ESP32 MQTT – Publish BME280 Sensor Readings (Arduino IDE)

源自於https://randomnerdtutorials.com/esp32-mqtt-publish-bme280-arduino/




















Arduino程式

#include <Wire.h>             // include Wire library, required for I2C devices

#include <Adafruit_Sensor.h>  // include Adafruit sensor library

#include <Adafruit_BMP280.h>  // include adafruit library for BMP280 sensor

//=========================================

#include <WiFi.h>

#include <PubSubClient.h>

#include <AutoConnect.h>

#include <WebServer.h>

WebServer server;

AutoConnect  Portal(server);

//=========================================

 

// define device I2C address: 0x76 or 0x77 (0x77 is library default address)

#define BMP280_I2C_ADDRESS  0x76

//=========================================

#define MQTTid              ""                           //id of this mqtt client

#define MQTTip              "broker.mqtt-dashboard.com"  //ip address or hostname of the mqtt broker

#define MQTTport            1883                         //port of the mqtt broker

#define MQTTuser            "alex9ufo"                   //username of this mqtt client

#define MQTTpsw             "alex1234"                   //password of this mqtt client

//#define MQTTuser          "your_username"              //username of this mqtt client

//#define MQTTpsw           "your_password"              //password of this mqtt client

#define MQTTpubQos          2                            //qos of publish (see README)

#define MQTTsubQos          1                            //qos of subscribe 

Adafruit_BMP280 bmp280;


//Variables

long lastMsg = 0;

String json = "";   

char jsonChar1[50];

char jsonChar2[50];

char jsonChar3[50];

char jsonChar4[50];

//=============================================================================

boolean pendingDisconnect = false;

void mqttConnectedCb(); // on connect callback

void mqttDisconnectedCb(); // on disconnect callback

void mqttDataCb(char* topic, byte* payload, unsigned int length); // on new message callback


WiFiClient wclient;

PubSubClient client(MQTTip, MQTTport, mqttDataCb, wclient);

//======================================================

void rootPage() {

  char content[] = "Hello, world";

  server.send(200, "text/plain", content);

}  

//======================================================= 

void mqttConnectedCb() {

  Serial.println("connected");

  

  // Once connected, publish an announcement...

  client.publish("alex9ufo/BMP280/TempC", jsonChar1, MQTTpubQos, true); // true means retain

  // Once connected, publish an announcement...

  client.publish("alex9ufo/BMP280/TempF", jsonChar2, MQTTpubQos, true); // true means retain

  // Once connected, publish an announcement...

  client.publish("alex9ufo/BMP280/Pressure", jsonChar3, MQTTpubQos, true); // true means retain

  // Once connected, publish an announcement...

  client.publish("alex9ufo/BMP280/FAltitude", jsonChar4, MQTTpubQos, true); // true means retain


 // ... and resubscribe


}

//======================================================= 

void mqttDisconnectedCb() {

  Serial.println("disconnected");

}

//======================================================= 

void mqttDataCb(char* topic, byte* payload, unsigned int length) {

  Serial.println("mqttDataCb --> subscribe 無訂閱"); 

}

//======================================================

void process_mqtt() {

  if (WiFi.status() == WL_CONNECTED) {

    if (client.connected()) {

      client.loop();

    } else {

    // client id, client username, client password, last will topic, last will qos, last will retain, last will message

      if (client.connect(MQTTid, MQTTuser, MQTTpsw, MQTTid "/status", 2, true, "0")) {

          pendingDisconnect = false;

          mqttConnectedCb();

      }

    }

  } else {

    if (client.connected())

      client.disconnect();

  }

  if (!client.connected() && !pendingDisconnect) {

    pendingDisconnect = true;

    mqttDisconnectedCb();

  }

}

//======================================================

void setup() {

  Serial.begin(115200);

  Serial.println("Configuring ESP32...");

  Serial.println(F("Arduino + BMP280"));

    //=================================================

  Serial.println("Configuring your Mobile WiFi to esp32ap...");

  Serial.println("Configuring another WiFi SSID,PWD...");

  

  server.on("/", rootPage);

  if (Portal.begin()) {

    Serial.println("HTTP server:" + WiFi.localIP().toString());

  }

    else {

    Serial.println("Connection failed");

    while(true) {yield();}

  }

  

  if (!bmp280.begin(BMP280_I2C_ADDRESS))

  {  

    Serial.println("Could not find a valid BMP280 sensor, check wiring!");

    while (1);

  }

}

//====================================================== 

// main loop

void loop()

{

  process_mqtt();

  long now = millis();

  if (now - lastMsg > 3000) {  //等3秒

    lastMsg = now; 

    // get temperature, pressure and altitude from library

    float temperature = bmp280.readTemperature();  // get temperature

    float pressure    = bmp280.readPressure();     // get pressure

    float altitude_   = bmp280.readAltitude(1013.25); // get altitude (this should be adjusted to your local forecast)

    //1013.15需修正

    //該1013.25值應為數百Pa處的海平面局部壓力。

    //如果要顯示高於地面的高度,則需要知道該位置,並編寫代碼以計算高度偏移。

    //海平面:泛指我們所處的高度,即1大氣壓(1atm或1013.25hPa 或 1013.25mb)

    //That 1013.25 value should be the local pressure at sea level in hundreds of Pa. 

    //If want to show altitude above ground level, you will need to know that for your location and write your code to calculate the altitude offset.


    // print data on the serial monitor software

    // 1: print temperature

    Serial.print("Temperature CELSIUS 攝氏溫度= ");

    Serial.print(temperature);

    Serial.println(" °C");

  

    float TempF = (temperature*1.8)+32;

    Serial.print("Temperature FAHRENHEIT 華氏溫度= ");

    Serial.print(TempF);

    Serial.println(" °F");

  

    // 2: print pressure

    Serial.print("Pressure 大氣壓力百帕(hPa)= ");

    Serial.print(pressure/100);

    Serial.println(" hPa");

    // 3: print altitude

    Serial.print("Approx Altitude 大概海拔高度= ");

    Serial.print(altitude_);

    Serial.println(" m");

    

    Serial.println();  // start a new line

    String json1 = String(temperature);

    String json2 = String(TempF);

    String json3 = String(pressure/100);

    String json4 = String(altitude_);

    json1.toCharArray(jsonChar1, json1.length()+1);

    json2.toCharArray(jsonChar2, json2.length()+1);

    json3.toCharArray(jsonChar3, json3.length()+1);

    json4.toCharArray(jsonChar4, json4.length()+1);

    

    if  (client.connected()) {

         Serial.println("Publish message:攝氏溫度 華氏溫度 大氣壓力百帕 海拔高度 ");

         Serial.println(json1+','+json2+','+json3+','+json4);

         

         // Publish JSON character array to MQTT topic

         client.publish("alex9ufo/BMP280/TempC",jsonChar1);

         client.publish("alex9ufo/BMP280/TempF",jsonChar2);

         client.publish("alex9ufo/BMP280/Pressure",jsonChar3);

         client.publish("alex9ufo/BMP280/FAltitude",jsonChar4);

    } 

  }// if (now - lastMsg > 3000)  等3秒

}

// end of code.

Node-Red程式

[{"id":"cf1bcee.62c9b3","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempC","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":40,"wires":[["dc6fdc66.f71fb","8754c0da.c5f9c","2bf8d29e.8bf13e"]]},{"id":"7ef09837.92cbe8","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempF","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":160,"wires":[["2914dd13.33f262","67fdbfea.b0ff1","682d2ef2.23766"]]},{"id":"ebbe39a4.ec43c8","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/Pressure","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":240,"wires":[["9c7a94d2.676388","91c23ee3.44a0f","5e43499b.2cba48"]]},{"id":"ffd75aa6.973ad8","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/FAltitude","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":360,"wires":[["7c55d2fe.0c519c","73f96b.3b607694","e4970484.1a0838"]]},{"id":"8754c0da.c5f9c","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"699c61b6.51c8e","order":2,"width":0,"height":0,"label":"C Temp","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"0","ymax":"50","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff8040","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":420,"y":80,"wires":[[]]},{"id":"67fdbfea.b0ff1","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"699c61b6.51c8e","order":1,"width":0,"height":0,"label":"F temp","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"32","ymax":"122","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#a61e1e","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":420,"y":160,"wires":[[]]},{"id":"91c23ee3.44a0f","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"6d81d54e.2e9c4c","order":1,"width":0,"height":0,"label":"Pressure","chartType":"bar","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"0","ymax":"1500","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":380,"y":280,"wires":[[]]},{"id":"7c55d2fe.0c519c","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"6d81d54e.2e9c4c","order":2,"width":0,"height":0,"label":"Altitude","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"0","ymax":"500","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":380,"y":360,"wires":[[]]},{"id":"dc6fdc66.f71fb","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":430,"y":40,"wires":[]},{"id":"2914dd13.33f262","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":430,"y":120,"wires":[]},{"id":"9c7a94d2.676388","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":390,"y":240,"wires":[]},{"id":"73f96b.3b607694","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":390,"y":400,"wires":[]},{"id":"9284055c.420db8","type":"join","z":"6e2fed4b.325c24","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"","joinerType":"str","accumulate":true,"timeout":"4","count":"4","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":170,"y":500,"wires":[["a2ed8c13.c2f8c"]]},{"id":"d2abecc.983171","type":"function","z":"6e2fed4b.325c24","name":"","func":"p = JSON.parse(msg.payload);\nnode.log(typeof p);\nmsg.payload = p;\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":500,"wires":[["c665b986.34e148"]]},{"id":"a2ed8c13.c2f8c","type":"json","z":"6e2fed4b.325c24","name":"","property":"payload","action":"","pretty":false,"x":310,"y":500,"wires":[["d2abecc.983171"]]},{"id":"c665b986.34e148","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":500,"wires":[]},{"id":"2bf8d29e.8bf13e","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":295,"y":100,"wires":[]},{"id":"aa673acc.cb8478","type":"link in","z":"6e2fed4b.325c24","name":"","links":["2bf8d29e.8bf13e","682d2ef2.23766","5e43499b.2cba48","e4970484.1a0838"],"x":55,"y":500,"wires":[["9284055c.420db8"]]},{"id":"682d2ef2.23766","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":295,"y":180,"wires":[]},{"id":"5e43499b.2cba48","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":295,"y":320,"wires":[]},{"id":"e4970484.1a0838","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":315,"y":440,"wires":[]},{"id":"b0978186.a37a8","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempC","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":950,"y":80,"wires":[]},{"id":"aef92e29.341c8","type":"inject","z":"6e2fed4b.325c24","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":570,"y":200,"wires":[["e82a3e.0c2ce5c","b6cc6afe.dfe0a8","60219d14.f7d0a4"]]},{"id":"e82a3e.0c2ce5c","type":"function","z":"6e2fed4b.325c24","name":"","func":"var max=50;\nvar min=0;\nmsg.payload= Math.floor(Math.random()*(max-min+1))+min;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":730,"y":80,"wires":[["b0978186.a37a8","95412341.d816c"]]},{"id":"ff05f33a.8976","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempF","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":950,"y":160,"wires":[]},{"id":"c9433d5.14408c","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/Pressure","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":940,"y":240,"wires":[]},{"id":"3963a196.aacdbe","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/FAltitude","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":940,"y":300,"wires":[]},{"id":"95412341.d816c","type":"function","z":"6e2fed4b.325c24","name":"","func":"//華氏= (攝氏乘9/5) + 32 \nvar c1= Number(msg.payload);\nvar f1= Number((c1 * 9) / 5) +32 ;\nmsg.payload=f1;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":750,"y":160,"wires":[["ff05f33a.8976"]]},{"id":"b6cc6afe.dfe0a8","type":"function","z":"6e2fed4b.325c24","name":"","func":"var max=1500;\nvar min=300;\nmsg.payload= Math.floor(Math.random()*(max-min+1))+min;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":750,"y":240,"wires":[["c9433d5.14408c"]]},{"id":"60219d14.f7d0a4","type":"function","z":"6e2fed4b.325c24","name":"","func":"var max=500;\nvar min=10;\nmsg.payload= Math.floor(Math.random()*(max-min+1))+min;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":750,"y":300,"wires":[["3963a196.aacdbe"]]},{"id":"e4d9b72d.d14398","type":"mqtt-broker","name":"","broker":"broker.mqtt-dashboard.com","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"15","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"699c61b6.51c8e","type":"ui_group","name":"Group 1","tab":"f4a73006.d45dc","order":1,"disp":true,"width":6},{"id":"6d81d54e.2e9c4c","type":"ui_group","name":"Group 2","tab":"f4a73006.d45dc","order":2,"disp":true,"width":6},{"id":"f4a73006.d45dc","type":"ui_tab","name":"Altitude","icon":"dashboard","order":3}]





[{"id":"cf1bcee.62c9b3","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempC","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":40,"wires":[["dc6fdc66.f71fb","2bf8d29e.8bf13e","78abba0b.3cc7b4","8754c0da.c5f9c"]]},{"id":"7ef09837.92cbe8","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempF","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":180,"wires":[["2914dd13.33f262","682d2ef2.23766","6be0c47.0f5783c","67fdbfea.b0ff1"]]},{"id":"ebbe39a4.ec43c8","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/Pressure","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":320,"wires":[["9c7a94d2.676388","5e43499b.2cba48","897d4d98.ec45f","91c23ee3.44a0f"]]},{"id":"ffd75aa6.973ad8","type":"mqtt in","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/FAltitude","qos":"2","datatype":"auto","broker":"e4d9b72d.d14398","x":130,"y":420,"wires":[["7c55d2fe.0c519c","73f96b.3b607694","e4970484.1a0838","83bdbf19.8dd02"]]},{"id":"8754c0da.c5f9c","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"699c61b6.51c8e","order":2,"width":0,"height":0,"label":"C Temp","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"0","ymax":"50","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff8040","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":420,"y":80,"wires":[[]]},{"id":"67fdbfea.b0ff1","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"699c61b6.51c8e","order":1,"width":0,"height":0,"label":"F temp","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"32","ymax":"122","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#a61e1e","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":420,"y":200,"wires":[[]]},{"id":"91c23ee3.44a0f","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"699c61b6.51c8e","order":1,"width":0,"height":0,"label":"Pressure","chartType":"bar","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"0","ymax":"1500","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":400,"y":320,"wires":[[]]},{"id":"7c55d2fe.0c519c","type":"ui_chart","z":"6e2fed4b.325c24","name":"","group":"699c61b6.51c8e","order":2,"width":0,"height":0,"label":"Altitude","chartType":"line","legend":"false","xformat":"HH:mm","interpolate":"linear","nodata":"","dot":false,"ymin":"0","ymax":"500","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"x":400,"y":420,"wires":[[]]},{"id":"dc6fdc66.f71fb","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":430,"y":40,"wires":[]},{"id":"2914dd13.33f262","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":430,"y":160,"wires":[]},{"id":"9c7a94d2.676388","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":410,"y":280,"wires":[]},{"id":"73f96b.3b607694","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":410,"y":500,"wires":[]},{"id":"9284055c.420db8","type":"join","z":"6e2fed4b.325c24","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"","joinerType":"str","accumulate":true,"timeout":"4","count":"4","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":170,"y":560,"wires":[["a2ed8c13.c2f8c"]]},{"id":"d2abecc.983171","type":"function","z":"6e2fed4b.325c24","name":"","func":"p = JSON.parse(msg.payload);\nnode.log(typeof p);\nmsg.payload = p;\nreturn msg;","outputs":1,"noerr":0,"x":430,"y":560,"wires":[["c665b986.34e148"]]},{"id":"a2ed8c13.c2f8c","type":"json","z":"6e2fed4b.325c24","name":"","property":"payload","action":"","pretty":false,"x":310,"y":560,"wires":[["d2abecc.983171"]]},{"id":"c665b986.34e148","type":"debug","z":"6e2fed4b.325c24","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":570,"y":560,"wires":[]},{"id":"2bf8d29e.8bf13e","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":295,"y":100,"wires":[]},{"id":"aa673acc.cb8478","type":"link in","z":"6e2fed4b.325c24","name":"","links":["2bf8d29e.8bf13e","682d2ef2.23766","5e43499b.2cba48","e4970484.1a0838"],"x":55,"y":560,"wires":[["9284055c.420db8"]]},{"id":"682d2ef2.23766","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":275,"y":220,"wires":[]},{"id":"5e43499b.2cba48","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":275,"y":360,"wires":[]},{"id":"e4970484.1a0838","type":"link out","z":"6e2fed4b.325c24","name":"","links":["aa673acc.cb8478"],"x":295,"y":500,"wires":[]},{"id":"b0978186.a37a8","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempC","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":1050,"y":80,"wires":[]},{"id":"aef92e29.341c8","type":"inject","z":"6e2fed4b.325c24","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":670,"y":180,"wires":[["e82a3e.0c2ce5c","b6cc6afe.dfe0a8","60219d14.f7d0a4"]]},{"id":"e82a3e.0c2ce5c","type":"function","z":"6e2fed4b.325c24","name":"","func":"var max=50;\nvar min=0;\nmsg.payload= Math.floor(Math.random()*(max-min+1))+min;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":830,"y":80,"wires":[["b0978186.a37a8","95412341.d816c"]]},{"id":"ff05f33a.8976","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/TempF","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":1050,"y":160,"wires":[]},{"id":"c9433d5.14408c","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/Pressure","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":1060,"y":220,"wires":[]},{"id":"3963a196.aacdbe","type":"mqtt out","z":"6e2fed4b.325c24","name":"","topic":"alex9ufo/BMP280/FAltitude","qos":"1","retain":"","broker":"e4d9b72d.d14398","x":1060,"y":300,"wires":[]},{"id":"95412341.d816c","type":"function","z":"6e2fed4b.325c24","name":"","func":"//華氏= (攝氏乘9/5) + 32 \nvar c1= Number(msg.payload);\nvar f1= Number((c1 * 9) / 5) +32 ;\nmsg.payload=f1;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":850,"y":160,"wires":[["ff05f33a.8976"]]},{"id":"b6cc6afe.dfe0a8","type":"function","z":"6e2fed4b.325c24","name":"","func":"var max=1500;\nvar min=300;\nmsg.payload= Math.floor(Math.random()*(max-min+1))+min;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":850,"y":220,"wires":[["c9433d5.14408c"]]},{"id":"60219d14.f7d0a4","type":"function","z":"6e2fed4b.325c24","name":"","func":"var max=500;\nvar min=10;\nmsg.payload= Math.floor(Math.random()*(max-min+1))+min;\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":850,"y":300,"wires":[["3963a196.aacdbe"]]},{"id":"78abba0b.3cc7b4","type":"ui_gauge","z":"6e2fed4b.325c24","name":"","group":"6d81d54e.2e9c4c","order":2,"width":0,"height":0,"gtype":"gage","title":"攝氏溫度","label":"units","format":"{{value}}","min":0,"max":"50","colors":["#00b500","#e6e600","#ca3838"],"seg1":"25","seg2":"35","x":420,"y":120,"wires":[]},{"id":"6be0c47.0f5783c","type":"ui_gauge","z":"6e2fed4b.325c24","name":"","group":"6d81d54e.2e9c4c","order":2,"width":0,"height":0,"gtype":"gage","title":"華氏溫度","label":"units","format":"{{value}}","min":"32","max":"122","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":420,"y":240,"wires":[]},{"id":"897d4d98.ec45f","type":"ui_gauge","z":"6e2fed4b.325c24","name":"","group":"6d81d54e.2e9c4c","order":2,"width":0,"height":0,"gtype":"gage","title":"大氣壓力","label":"units","format":"{{value}}","min":"0","max":"1500","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":400,"y":360,"wires":[]},{"id":"83bdbf19.8dd02","type":"ui_gauge","z":"6e2fed4b.325c24","name":"","group":"6d81d54e.2e9c4c","order":2,"width":0,"height":0,"gtype":"gage","title":"海拔高度","label":"units","format":"{{value}}","min":"0","max":"500","colors":["#00b500","#e6e600","#ca3838"],"seg1":"","seg2":"","x":400,"y":460,"wires":[]},{"id":"e4d9b72d.d14398","type":"mqtt-broker","name":"","broker":"broker.mqtt-dashboard.com","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"15","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"699c61b6.51c8e","type":"ui_group","name":"Group 1","tab":"f4a73006.d45dc","order":1,"disp":true,"width":6},{"id":"6d81d54e.2e9c4c","type":"ui_group","name":"Group 2","tab":"f4a73006.d45dc","order":2,"disp":true,"width":6},{"id":"f4a73006.d45dc","type":"ui_tab","name":"Altitude","icon":"dashboard","order":3}]

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> &...