65.9K
CodeProject 正在变化。 阅读更多。
Home

IoT 智能时钟(使用 Mega 2560+WiFi R3)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (7投票s)

2020 年 11 月 10 日

MIT

10分钟阅读

viewsIcon

10342

downloadIcon

321

一个先进的网络连接时钟, 该时钟充分发挥了 Arduino 的性能

smart clock

引言

有时,设备的用途远不止表面看上去那么简单。我们以时钟为例,探索物联网设备的一些高级技术,即使是这个不起眼的、讽刺地命名为 Mega 的设备。我们将基于一些教程组装一个时钟,然后编写一些“魔法”代码来赋予它魔力。我还会为你提供一些库,这些库可以简化你自己的应用程序中的自动网络配置部分。

必备组件

你需要一个 Arduino Mega 2560+WiFi R3。

你需要一个 DS1307 或类似的实时时钟。

你需要一个 DHT11 温湿度传感器。

你需要一个带日立接口的 16x2 LCD 显示屏。

你需要常用的电线和面包板。

你需要 Arduino IDE。你必须进入 文件|首选项 并将以下内容添加到 附加开发板管理器网址 文本框中:

如果已存在 URL,请用逗号分隔。

现在请点击此处获取最新的 ESP8266FS zip 文件。

如果你在 Linux 上,你需要解压 zip 文件中的 ESP8266FS 文件夹,并找到你的 Arduino 应用程序目录。它应该在你的主目录下。我的目录是 ~/arduino-1.8.13。不要与 ~/Arduino 混淆。在该目录下有一个 tools 文件夹,你就是要把你的 ESP8266FS 文件夹放到这里。

我从没在 Windows 上做过,但操作方法如下:你需要 zip 文件中的 ESP8266FS 文件夹。找到你的程序目录。它可能类似于 C:\Program Files (x86)\arduino-1.8.13。里面有一个 tools 文件夹。这就是 ESP8266FS 文件夹需要放置的位置。

无论哪种方式,你都需要重新启动 Arduino IDE。如果现在有了 工具|ESP8266 Sketch 数据上传 选项,就表示安装成功了。

你还需要安装 ESP8266AutoConfLibs.zip 中的所有库。解压后,你可以进入 项目|加载库|添加 .ZIP 库... 并选择每个 zip 文件进行安装。

概念化这个混乱的局面

连接和配置

这是项目中最复杂的部分,也是我创建此项目和文章的主要原因,所以我们将特别花一些时间在这一小节上。

我的物联网设备通常会自动配置其 WiFi 连接并支持 WPS。我的意思是,我的设备除了开机外,不需要进行任何操作。一旦路由器上的 WPS 按钮被按下,它们就会连接。这对于需要网络连接的设备来说相对简单,因为我们可以暂停应用程序的其余部分,直到连接和配置完成,但是对于像时钟这样的应用程序,它需要持续运行,同时理想地在后台寻找互联网连接或 WPS 信号,该怎么办呢?使用默认的 ESP8266WiFi 库,这几乎是不可能的。

以下是我们连接网络的步骤:

  1. 从配置中读取 SSID 和密码。
  2. 如果可用,使用它们尝试连接到该网络,否则跳过此步骤。
  3. 如果 #2 超时或失败,或者我们跳过了 #2,尝试扫描 WPS 信号,如果找到则连接。
  4. 如果我们超时/未能找到 WPS 信号,则返回 #2。
  5. 否则,将新的 SSID 和密码保存到配置中并继续。

正如我所说,在阻塞直到进程完成的前台,这相对简单。当它必须在后台完成时,则要复杂得多。ESP8266WiFi 库的 `beginWPSconfig()` 是同步的,所以我挣扎了一段时间,才制作了一个像 `begin()` 一样的异步版本。我的库叫做 ESP8266AsyncWiFi,前面提到的方法是我唯一改变的行为。当我第一次尝试这样做时,我试图不分叉 WiFi 库,但结果是随机崩溃,因为我无法调用一些使其工作的私有方法。

然而,麻烦并没有就此结束。为了在不使用 `delay()` 的情况下使所有超时都起作用,并管理上述步骤之间的不同转换,我构建了一个从 `loop()` 中调用的状态机。状态机在连接和 WPS 协商过程导航时从一个状态移动到下一个状态。涉及两个超时——一个用于连接,一个用于 WPS 搜索。基本上,我们只是在尝试连接和尝试寻找 WPS 信号之间来回切换,但我们以一种不阻塞的方式进行。它并不美观——状态机很少美观,但至少它没有比它需要的更复杂。

我将所有的自动配置功能都变成了几个库,并将其包含在此分发包中。你可以通过进入 项目|加载库|添加 .ZIP 库... 来使用 Arduino IDE 安装每个 zip 文件。

  1. ESP8266AsyncWifi 是随附的两个异步自动配置库(见下文)所必需的。
  2. ESP8266AsyncAutoConf 是一个后台自动 WiFi 配置库,使用原始闪存存储网络凭据。
  3. ESP8266AsyncAutoConfFS 是一个后台自动 WiFi 配置库,使用 SPIFFS 闪存文件系统存储网络凭据。如果你已经打算使用文件系统来提供网页等功能,这很有用。
  4. ESP8266AutoConf 是一个前台自动 WiFi 配置库,使用原始闪存存储网络凭据。
  5. ESP8266AutoConfFS 是一个前台自动 WiFi 配置库,使用 SPIFFS 闪存文件系统存储网络凭据。

如何为你的项目选择合适的 AutoConf 库

  • 如果你的设备不需要网络即可运行,你应该使用上面的 #2 或 #3。
  • 如果你的设备需要网络才能运行,你应该使用上面的 #4 或 #5。
  • 如果你的设备需要文件系统,请使用上面的 #3 或 #5。
  • 如果你的设备不需要文件系统,请使用上面的 #2 或 #4。

这只是一个使用它们的通用指南。我们将为我们的项目选择 #3,因为我们不需要网络连接即可运行,并且我们需要一个文件系统。这样,我们的时钟可以在后台自动搜索 WPS 信号或可用的 WiFi 连接,同时提供一个小型网站和网络服务。

时钟硬件

我基本上是从几个示例项目拼凑起来的硬件,这里这里这里。我希望你能按照它们,并将它们组合到一个原型板上。请记住,在 Mega 上连接时钟时,你将要将其设置为第二个 I2C 接口(SDA20/SCL21),而不是第一组。你还需要将 DHT 传感器的 S 线插入 A0,并且相应的代码需要更新以将代码中的引脚更改为 A0。如果你的时钟不是 DS1307,你需要相应地调整你的代码和接线。

一旦你接好线并用一些废弃代码进行测试,我们就可以开始核心部分了。这是一些用于测试的废弃代码:

#include <LiquidCrystal.h>
#include <dht.h>
#include <RTClib.h>
RTC_DS1307 RTC;
dht DHT;
float _temperature;
float _humidity;

#define DHT11_PIN A0

// initialize the library by providing the nuber of pins to it
LiquidCrystal LCD(8, 9, 4, 5, 6, 7);

void setup() {
  Serial.begin(115200);
  LCD.begin(16, 2);
  pinMode(DHT11_PIN, INPUT);
  if (! RTC.begin()) {
    Serial.println(F("Couldn't find RTC"));
    while (true);
  }
   RTC.adjust(DateTime(__DATE__, __TIME__));
}
void loop()
{
  int chk = DHT.read11(DHT11_PIN);
  float f = DHT.temperature;
  if (-278 < f) {
    _temperature = f;
    _humidity = DHT.humidity;
  }

  DateTime now = RTC.now();
  char sz[16];
  memset(sz,' ',16);
  sprintf(sz, "%d:%02d:%02d", 
    now.hour(), 
    now.minute(), 
    now.second());
  int c = strlen(sz);
    if(c<16)
      sz[c]=' ';
  LCD.setCursor(0,0);
  LCD.print(sz);

  LCD.setCursor(0, 1);
  memset(sz,' ',16);
  if (1==(millis() / 1000L) % 2) {
    sprintf(sz,"Temp: %dC/%dF",(int)_temperature,(int)((int)_temperature * 1.8) + 32);
    int c = strlen(sz);
    if(c<16)
      sz[c]=' ';
  } else {
    sprintf(sz,"Humidity: %d",(int)_humidity);
    int c = strlen(sz);
    if(c<16)
      sz[c]=' ';
  }
  LCD.print(sz);
}

如果一切正常,这应该在显示屏上显示时间、温度和湿度。

ATMEGA2560

这个处理器将负责管理 LCD 输出,并与 WiFi 模块上的 XDS 160Micro 处理器通信时钟和传感器读数。我们将使用该处理器来完成几乎所有繁重的工作,因为它功能更强大,并且内置了 WiFi,而无需通过串口访问。我们确实会使用一个串口,但只是为了来回获取传感器数据和时钟信息。大部分情况下,除了更新时钟显示外,它只是在串口上监听传入的 255 值。如果它收到该值,它会接下来读取一个操作码,然后是取决于操作码的命令字节。任何其他值都将转发到通过 USB 暴露的串口。

XDS 160Micro

该处理器将负责协商 WiFi 网络、运行 Web 服务器、与 NTP 服务器通信并在必要时设置时钟。每当它需要传感器或时钟信息时,它必须通过串行线查询 ATMega2560。它通过发送字节 255,然后是 0,然后接收所有时钟和传感器数据来完成此操作。如果它发送 255,然后是 1,然后是一个表示 Unix 时间的 32 位数字,它将设置时钟。它在 http://clock.local 运行一个 Web 服务器,该服务器将显示时间、温度和湿度。它在 http://clock.local/time.json 运行一个 JSON 服务。

编写这个混乱的程序

ESPAsyncAutoConf 功能

此功能可自动扫描 WiFi 和 WPS,并管理设备中存储的 SSID 和密码。

如何使用它

在我们深入探讨它是如何工作的之前,让我们先看看如何使用它

#include <ESP8266AsyncAutoConfFS.h> // header chosen from #3 above

在我们的 `setup()` 方法中

ESPAsyncAutoConf.begin();

如果你不使用异步版本,你将调用 `ESPAutoConf.begin()`。

在 `loop()` 方法中

ESPAsyncAutoConf.update();
// your loop code here.

与上面类似,如果你不使用异步版本,你将改为引用 `ESPAutoConf`。

你可以像平常一样查看是否连接到网络

if(WL_CONNECTED == WiFi.status()) Serial.println("Connected!");

对于同步版本,在调用 `update()` 之后,你将始终处于连接状态。对于异步版本,在调用 `update()` 之后,它们很可能不会连接。

记住,在使用异步库时,在执行网络操作之前,务必检查是否已连接!

关于使用它就到这里。让我们深入了解它是如何制作的。

构建它

我已经在此处介绍了这些库的同步版本的机制。自该文章发布以来,库中的代码有所更新,但概念没有改变。本文将重点介绍异步配置过程。

这些异步库的大部分核心代码都在 `update()` 中,它通常从 `loop()` 中调用。

#include "ESP8266AsyncAutoConfFS.h"
_ESPAsyncAutoConf ESPAsyncAutoConf;
void _ESPAsyncAutoConf::begin() {
  SPIFFS.begin();
  int i = 0;
  // Read the settings
  _cfgssid[0] = 0;
  _cfgpassword[0] = 0;
  File file = SPIFFS.open("/wifi_settings", "r");
  if (file) {
    if (file.available()) {
      int l = file.readBytesUntil('\n', _cfgssid, 255);
      _cfgssid[l] = 0;
      if (file.available()) {
        l = file.readBytesUntil('\n', _cfgpassword, 255);
        _cfgpassword[l] = 0;
      }
    }
    file.close();
  }
 
  // Initialize the WiFi
  WiFi.mode(WIFI_STA);
  _wifi_timestamp = 0;
  _wifi_conn_state = 0;
}

void _ESPAsyncAutoConf::update() {
  // connect, reconnect or discover the WiFi
  switch (_wifi_conn_state) {
    case 0: // connect
      if (WL_CONNECTED != WiFi.status())
      {
        if (0 < strlen(_cfgssid)) {
          Serial.print(F("Connecting to "));
          Serial.println(_cfgssid);
          if (WiFi.begin(_cfgssid, _cfgpassword)) {
            // set the state to connect
            // in progress and reset
            // the timestamp
            _wifi_conn_state = 1;
            _wifi_timestamp = 0;
          }
        } else {
          // set the state to begin
          // WPS and reset the 
          // timestamp
          _wifi_conn_state = 2;
          _wifi_timestamp = 0;
        }
      }
      break;
    case 1: // connect in progress
      if (WL_CONNECTED != WiFi.status()) {
        if (!_wifi_timestamp)
          _wifi_timestamp = millis();
        else if (20000 <= (millis() - _wifi_timestamp)) {
          Serial.println(F("Connect attempt timed out"));
          // set the state to begin
          // WPS and reset the 
          // timestamp
          _wifi_conn_state = 2;
          _wifi_timestamp = 0;
        }
      } else {
        Serial.print(F("Connected to "));
        // store the WiFi configuration
        Serial.println(WiFi.SSID());
        strcpy(_cfgssid,WiFi.SSID().c_str());
        strcpy(_cfgpassword,WiFi.psk().c_str());
    File file = SPIFFS.open("/wifi_settings", "w");
        if (file) {
            file.print(_cfgssid);
            file.print("\n");
            file.print(_cfgpassword);
            file.print("\n");
            file.close();
        }
        // set the state to connected
        _wifi_conn_state = 4;
        _wifi_timestamp = 0;
      }
      break;
    case 2: // begin wps
      Serial.println(F("Begin WPS search"));
      if (WL_CONNECTED != WiFi.status()) {
        WiFi.beginWPSConfig();
        // set the state to WPS in
        // progress
        _wifi_conn_state = 3;
        _wifi_timestamp = 0;      
      }
      break;
    case 3: // wps in progress
      if (WL_CONNECTED != WiFi.status()) {
        if (!_wifi_timestamp)
          _wifi_timestamp = millis();
        else if (30000 <= (millis() - _wifi_timestamp)) {
          Serial.println(F("WPS search timed out"));
          // set the state to connecting
          _wifi_conn_state = 0;
          _wifi_timestamp=0;
        }

      } else {
        // eventually goes to 4:
        _wifi_conn_state = 1; 
        _wifi_timestamp = 0;
      }
      break;
    case 4:
      if (WL_CONNECTED != WiFi.status()) {
        // set the state to connecting
        _wifi_conn_state = 0;
        _wifi_timestamp = 0;
      }
      break;
  }
}

你可能会注意到这是一个状态机。我们之前在概念化部分已经介绍过它的作用。逻辑有点混乱,但为了处理所有情况,这是必要的。我喜欢状态机的概念,但在实践中却不那么喜欢,因为它们可能难以阅读。然而,有时它们正是适合这项工作的工具。

这个程序的整个想法是将连接、扫描 WPS、连接、扫描 WPS 等过程分解成一个协程——一个“可重新启动的方法”——这就是状态机的用武之地。每次调用 `loop()` 时,我们都会从上次离开的地方继续,因为我们正在用 `_wifi_conn_state` 跟踪状态。它并不是最不言自明的代码,但如果你仔细观察,你就能理解它。

ATmega2560

这是 ATmega2560 CPU 的处理代码,我们之前已经介绍过,所以让我们直接看代码吧。

#include <dht.h>
#include <RTClib.h>
#include <LiquidCrystal.h>

// make sure S is on analog 0
#define DHT11_PIN A0

// these unions make it
// easy to convert
// numbers to bytes
typedef union {
 float fp;
 byte bin[4];
} binaryFloat;
typedef union {
  uint32_t ui;
  byte bin[4];
} binaryUInt;
float _temperature;
float _humidity;
dht DHT;
RTC_DS1307 RTC;
LiquidCrystal LCD(8, 9, 4, 5, 6, 7);
void setup() {
  // initialize everything
  Serial.begin(115200);
  Serial3.begin(115200);
  pinMode(DHT11_PIN,INPUT);
  if (! RTC.begin()) {
    Serial.println(F("Couldn't find the clock hardware"));
    while (1);
  }
  if (! RTC.isrunning())
    Serial.println(F("The clock is not running!"));
  
  LCD.begin(16, 2);
}

void loop() {
  // update the temp and humidity
  // note that sometimes the DHT11
  // will return -999s for the 
  // values. We check for that.
  int chk = DHT.read11(DHT11_PIN);
  float tmp = DHT.temperature;
  if (-278 < tmp) {
    _temperature = tmp;
    _humidity = DHT.humidity;
  }
  DateTime now = RTC.now();
  // format our time and other
  // info to send to the LCD
  char sz[16];
  memset(sz,' ',16);
  sprintf(sz, "%d:%02d:%02d", 
    now.hour(), 
    now.minute(), 
    now.second());
  int c = strlen(sz);
    if(c<16)
      sz[c]=' ';
  LCD.setCursor(0,0);
  LCD.print(sz);

  LCD.setCursor(0, 1);
  memset(sz,' ',16);
  if (1==(millis() / 2000L) % 2) {
    sprintf(sz,"Temp: %dC/%dF",(int)_temperature,(int)((int)_temperature * 1.8) + 32);
    int c = strlen(sz);
    if(c<16)
      sz[c]=' ';
  } else {
    sprintf(sz,"Humidity: %d",(int)_humidity);
    int c = strlen(sz);
    if(c<16)
      sz[c]=' ';
  }
  LCD.print(sz);
  // now wait for incoming serial data
  if (Serial3.available()) {
    byte b = Serial3.read();
    // if it's not our escape byte
    // of 255, just forward it
    if (255 != b)
    {
      Serial.write(b);
      return;
    }
    b = Serial3.read();
    switch (b) {
      case 0: // get unixtime, temp and humidity
      // read one int32 and two floats from the
      // serial line
        binaryUInt data;
        data.ui = RTC.now().unixtime();
        Serial3.write(data.bin, 4);
        binaryFloat data2;
        data2.fp = _temperature;
        Serial3.write(data2.bin, 4);
        data2.fp = _humidity;
        Serial3.write(data2.bin, 4);
        break;
      case 1: // set clock
      // read an int32 and set the
      // clock
        Serial3.readBytes((char*)data.bin,4);
        RTC.adjust(DateTime(data.ui));
        break;
    }
  }
}

请记住将你的 DIP 开关设置为 1-4 ON 和 5-8 OFF。在烧录上述代码之前,还要从板卡菜单中选择“Arduino Mega or Mega 2560”。

XDS Micro160

这是 XDS Micro160 的代码。如你所见,这要复杂得多。这主要是由于时钟的所有功能。

#include <ESP8266AsyncAutoConfFS.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
// make it easier to convert
// between numbers and bytes:
typedef union {
  float fp;
  byte bin[4];
} binaryFloat;
typedef union {
  uint32_t ui;
  byte bin[4];
} binaryUInt;
// the host name for the webserver
#define HOSTNAME "clock"
// local port to listen for UDP packets
unsigned int localPort = 2390;      

// time.nist.gov NTP server address
IPAddress timeServerIP; 
const char* ntpServerName = "time.nist.gov";

// NTP time stamp is in the first 48 bytes of the message
const int NTP_PACKET_SIZE = 48; 
//buffer to hold incoming and outgoing packets
byte packetBuffer[ NTP_PACKET_SIZE]; 

// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;
unsigned long _ntp_timestamp;
unsigned long _mdns_timestamp;
bool _net_begin;
AsyncWebServer server(80);
void sendNTPpacket(IPAddress& address);
String process(const String& arg);
void setup() {
  _ntp_timestamp = 0;
  _mdns_timestamp = 0;
  _net_begin = false;
  Serial.begin(115200);
  ESPAsyncAutoConf.begin();
  // web handlers
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, F("/index.html"), String(), false, process);
  });
  server.on("/time.json", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, F("/time.json"), String(), false, process);
  });

}
void loop() {
  if (WL_CONNECTED != WiFi.status()) {
    _mdns_timestamp = 0;
    _net_begin = false;
  }
  // give the clock a chance to connect
  // to the network:
  ESPAsyncAutoConf.update();
  // check if we're connected
  if (WL_CONNECTED == WiFi.status()) {
    // the first time we connect, 
    // start the services
    if (!_net_begin) {
      _net_begin = true;
      MDNS.begin(F(HOSTNAME));
      server.begin();
      udp.begin(localPort);
      Serial.print(F("Started http://"));
      Serial.print(F(HOSTNAME));
      Serial.println(F(".local"));

    }
    // now send an NTP packet every 5 minutes:
    if (!_ntp_timestamp)
      _ntp_timestamp = millis();
    else if (300000 <= millis() - _ntp_timestamp) {
      WiFi.hostByName(ntpServerName, timeServerIP);
      sendNTPpacket(timeServerIP); // send an NTP packet to a time server
      _ntp_timestamp = 0;
    }
    // update the MDNS responder every second
    if (!_mdns_timestamp)
      _mdns_timestamp = millis();
    else if (1000 <= millis() - _mdns_timestamp) {
      MDNS.update();
      _mdns_timestamp = 0;
    }
    // if we got a packet from NTP, read it
    if (0 < udp.parsePacket()) {
      udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

      //the timestamp starts at byte 40 of the received packet and is four bytes,
      // or two words, long. First, esxtract the two words:

      unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
      unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
      // combine the four bytes (two words) into a long integer
      // this is NTP time (seconds since Jan 1 1900):
      unsigned long secsSince1900 = highWord << 16 | lowWord;
      // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
      const unsigned long seventyYears = 2208988800UL;
      // subtract seventy years:
      unsigned long epoch = secsSince1900 - seventyYears;

      // use the data to set the clock
      Serial.write((byte)255);
      Serial.write((byte)1);
      binaryUInt data;
      data.ui = epoch;
      Serial.write((char*)data.bin, 4);
    }
  }
}
String process(const String& arg)
{
  // replace template parameters in
  // the web page with actual values
  Serial.write((byte)255);
  Serial.write((byte)0);
  binaryUInt data;
  Serial.read((char*)data.bin, 4);
  unsigned long epoch = data.ui;
  binaryFloat dataf;
  Serial.read((char*)dataf.bin, 4);
  float tmp = dataf.fp;
  Serial.read((char*)dataf.bin, 4);
  float hum = dataf.fp;
  if (arg == "TIMESTAMP") {
    char sz[256];
    // print the hour, minute and second:
    sprintf(sz, "%d:%02d:%02d", (epoch  % 86400L) / 3600,
            (epoch % 3600) / 60,
            epoch % 60
           );
    return String(sz);
  } else if (arg == "TEMPERATURE") {
    char sz[256];
    sprintf(sz, "%f",tmp);
    return String(sz);
  } else if(arg=="HUMIDITY") {
    char sz[256];
    sprintf(sz, "%f",hum);
    return String(sz);
  }

  return String();
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress& address) {
  Serial.println("sending NTP packet...");
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}

请记住将你的 DIP 开关设置为 1-4 OFF,5-7 ON,8 OFF。在烧录代码之前,从板卡菜单中选择“Generic ESP8266”。烧录代码后,请确保也将数据目录上传到你的闪存中。烧录完成后,将 DIP 开关设置回 1-4 ON,5-8 OFF。

请注意,我们上面的“网络服务”只是一个模板化的 JSON 文件

{
  "time": "%TIMESTAMP%",
  "temperature": %TEMPERATURE%,
  "humidity": %HUMIDITY%
}

这些以 % 分隔的值通过上面提到的 `process()` 例程解析。这是一种实现动态网络服务的无耻方式,但它有效,并使我们可怜的小 CPU 不必进行大量的 JSON 字符串拼接。

这些通过以下 HTML 和 JavaScript 获取。请原谅我的混乱。

<!DOCTYPE html>
<html>
    <head>
        <title>Clock</title>
    </head>
    <body>
        <span>Time:</span><span id="TIME">Loading</span><br />
        <span>Temp:</span><span id="TEMPERATURE">Loading</span><br />
        <span>Humidity:</span><span id="HUMIDITY">Loading</span>
<script>
    function askTime() {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        
      if (this.readyState == 4 && (this.status==0 || this.status == 200)) {    
        var obj = JSON.parse(this.responseText);

        document.getElementById("TIME").innerHTML = obj['time'];
            var far = (obj['temperature'] * 1.8) + 32;
            document.getElementById("TEMPERATURE").innerHTML = 
                Math.round(obj['temperature']) +
                'C/'+
                Math.round(far)+
                'F';
            document.getElementById("HUMIDITY").innerHTML = 
                Math.round(obj['humidity']);
      }
    };
    xmlhttp.open("GET", "time.json", true);
    xmlhttp.send();
    setTimeout(function() {askTime();},1000);
    }   
    askTime();      
</script>
 
    </body>
</html>

没什么可看的,这只是经典的基于 JSON 的 AJAX 技术,剥离了所有花哨的 JS 框架,直接接触底层。

历史

  • 2020 年 11 月 10 日 - 初次提交
© . All rights reserved.