Close

NFCWifiManager.h

A project log for e-ink clock with html + css

bonus: using NFC to configure the WiFi

nazNaz 06/11/2024 at 01:450 Comments

Have you noticed that WiFi signal icon seems to go up but NFC signals go sideways. Hmmmm.

In this project, I brought over a NFC chip that I was using on another project and have had a working antenna and ST25DV onboard for quite some time. It was after the hardware was in place for a few boards that I ended up programming it. I struggled to find a use for the NFC when I initially placed it other than I had the schematic and it seemed like fun. NFC is a cool technology, I'd love to find other uses for it! Energy harvesting!

Initially I programmed this with a react-native app, but I realized that there are a few apps out there that implement writing WiFi records to the app (because it's a standard! hooray! standards working!). I have not look into the Play Store, but I assume there should be a few of those apps out there.

I used the ST NFC Tap app, and something is wrong with the way it writes WiFi records, so the NFC Tools app on iOS is the one of choice for me.

https://apps.apple.com/us/app/nfc-tools/id1252962749

Below is the source code for a part of the eink screen that I call the NFCWifiManager. The way it works is that it in your Arduino code, you delegate responsibility of the WiFi to the NFCWifiManager. From here, it exposes begin() that should be called from setup() and loop() which should be called from within your loop.

The loop will check the NDEF records on the ST25 and see if there is a WiFi record. If it detects there is a WiFi record, it takes the SSID and password and starts connecting as well as storing the values to Preferences. It then replaces the NDEF record with a URL showing that it is connecting.

The isWifiConnected() tells you if there is an active connection, and getMessage() gives you way to show to the user what the status is (it gets displayed on the e-ink screen when wifi is not connected).

To configure the device, you open up NFC Tools and add a new record of WiFi, set the SSID and Password, and write it to the device. That's it! The next loop, the WiFi record will be picked up and connection will be attempted. If the connection fails to connect the device resets and forgets the WiFi password. You can comment that out (look for preferences.clear())

Beyond that, there is also this concept of a "serverAddress" which is where the e-ink screen queries to get the display data, and so to configure that you can set a URL through the NFC device, and that will set the server address. So for example, when I'm giving these devices out, I can include a sticker QR code that has the address of the default server and then when you receive it, you open up NFC Tools and import the URL from the QR code. Or just type it in like http://192.168.1.71/ the NFCWifiManager respects protocol, because sometimes on our local network, we don't go through the hoops for https.

The getServerAddress() returns the processed address and if you hover over the NFC antenna with your phone and you aren't transmitting, it reads a URL which is the server address + display.html with the device ID. This can be used as a way for the user to edit their content on the screen.

So to close up, the workflow for a person connecting their device would look like:

1> Plug in the device to power

2> The screen says "Tap To Configure" so you install a NFC Tool app

3> You write your WiFi Record from the NFC app to the device and the screen connects

4> You change the default server to your home server by writing http://192.168.1.71/ to the device and now the screen displays your local server (e.g. https://codeberg.org/wsqnyc/eink_web_server)

#include <Preferences.h>
#include <ST25DVSensor.h>

enum NFCWifiStatus {
  NFC_WIFI_STATUS_NOT_CONFIGURED,
  NFC_WIFI_STATUS_WIFI_FAILED,
  NFC_WIFI_STATUS_INVALID_SSID,
  NFC_WIFI_STATUS_WIFI_CONNECTING,
  NFC_WIFI_STATUS_WIFI_CONNECT_SUCCESS
};

class NFCWifiManager {
 public:
  NFCWifiManager();
  ST25DV st25dv;
  Preferences preferences;
  bool initialized = false;
  void begin();
  bool isWifiConnected();
  String getMessage();
  void loop();
  String getServerAddress();

 private:
  bool hasWifiCredentials();
  void setURL(NFCWifiStatus url);
  void startConnect(String ssid, String password);
  String buildURL(NFCWifiStatus status);
};
#include "NFCWifiManager.h"

#include <Arduino.h>
#include <ST25DVSensor.h>
#include <WiFi.h>

NFCWifiManager::NFCWifiManager() : st25dv(-1, -1, &Wire) {}

String removeTrailingSlash(String serverAddress) {
  if(serverAddress[serverAddress.length() - 1] == '/') {
    return serverAddress.substring(0, serverAddress.length() - 1);
  }

  return serverAddress;
}

String ensureTrailingSlash(String serverAddress) {
  if(serverAddress[serverAddress.length() - 1] != '/') {
    return serverAddress + "/";
  }

  return serverAddress;
}

String NFCWifiManager::getServerAddress() {
  String serverAddress = preferences.getString("serverAddress", "");

  if(serverAddress.length() == 0) {
    serverAddress = "https://www.example.com/";
  }

  return removeTrailingSlash(serverAddress);
}

String removeProtocol(String url) {
  if(url.startsWith("http://")) {
    url = url.substring(7);
  }

  if(url.startsWith("https://")) {
    url = url.substring(8);
  }

  return url;
}

String NFCWifiManager::buildURL(NFCWifiStatus status) {
  String writeValue = ensureTrailingSlash(removeProtocol(getServerAddress()));

  if(status == NFC_WIFI_STATUS_NOT_CONFIGURED) {
    writeValue += "wifi.html";
  } else if(status == NFC_WIFI_STATUS_WIFI_FAILED) {
    writeValue += "wifi-failed.html";
  } else if(status == NFC_WIFI_STATUS_INVALID_SSID) {
    writeValue += "wifi-invalid-ssid.html";
  } else if(status == NFC_WIFI_STATUS_WIFI_CONNECTING) {
    writeValue += "wifi-connecting.html";
  } else if(status == NFC_WIFI_STATUS_WIFI_CONNECT_SUCCESS) {
    String deviceId = WiFi.macAddress();
    deviceId.replace(":", "");

    writeValue += "display.html?deviceId=" + deviceId;
  }

  return writeValue;
}

void NFCWifiManager::setURL(NFCWifiStatus status) {
  Serial.println("setURL: " + buildURL(status));

  st25dv.writeURI(URI_ID_0x04_STRING, buildURL(status), "");
}

void NFCWifiManager::startConnect(String ssid, String password) {
  preferences.putString("ssid", ssid);
  preferences.putString("password", password);

  if(password.length() > 0) {
    Serial.println("Connecting to " + ssid + " with password " + password);
    WiFi.begin(ssid.c_str(), password.c_str());
  } else {
    Serial.println("Connecting to " + ssid + ".");
    WiFi.begin(ssid.c_str());
  }

  setURL(NFC_WIFI_STATUS_WIFI_CONNECTING);
}

void NFCWifiManager::begin() {
  Serial.print("NFCWifiManager::begin");

  Serial.println("ST25DV begin");

  if(st25dv.begin() != NFCTAG_OK) {
    Serial.println("ST25DV not found");
    return;
  }

  initialized = true;

  preferences.begin("wifi", false);

  String ssid = preferences.getString("ssid", "");
  String password = preferences.getString("password", "");

  if(ssid.length() > 0) {
    startConnect(ssid, password);
  } else {
    setURL(NFC_WIFI_STATUS_NOT_CONFIGURED);
  }

  // WiFi.disconnect(true, true);
  // preferences.clear();
}

bool NFCWifiManager::isWifiConnected() { return WiFi.isConnected(); }

bool NFCWifiManager::hasWifiCredentials() {
  String ssid = preferences.getString("ssid", "");
  String password = preferences.getString("password", "");

  if(ssid.length() == 0) {
    return false;
  }

  return true;
}

String NFCWifiManager::getMessage() {
  if(!initialized) {
    return "ST25DV\nnot\nfound";
  }

  sRecordInfo_t recordInfo;
  uint16_t ret = st25dv.getNDEF()->NDEF_IdentifyNDEF(&recordInfo);
  if(ret) {
    return ("NDEF_IdentifyNDEF\nfailed:\n" + String(ret));
  }

  sURI_Info uriInfo;
  ret = st25dv.getNDEF()->NDEF_ReadURI(&recordInfo, &uriInfo);
  if(ret) {
    return ("NDEF_ReadURI\nfailed\n" + String(ret));
  }

  Serial.println("Existing NDEF\nvalue:\n" + String(uriInfo.URI_Message));

  if(String(uriInfo.URI_Message) == buildURL(NFC_WIFI_STATUS_NOT_CONFIGURED)) {
    return "Tap to\nconfigure\nWiFi";
  }

  if(String(uriInfo.URI_Message) == buildURL(NFC_WIFI_STATUS_WIFI_FAILED)) {
    return "WiFi\nconnection\nfailed";
  }

  if(String(uriInfo.URI_Message) == buildURL(NFC_WIFI_STATUS_INVALID_SSID)) {
    return "Invalid\nWiFi\nSSID";
  }

  if(String(uriInfo.URI_Message) == buildURL(NFC_WIFI_STATUS_WIFI_CONNECTING)) {
    return "Establishing\nWiFi\nconnection";
  }

  if(String(uriInfo.URI_Message) ==
     buildURL(NFC_WIFI_STATUS_WIFI_CONNECT_SUCCESS)) {
    return "Connected\nto\nWiFi";
  }

  return "Unknown status:\n" + String(uriInfo.URI_Message);
}

int wifiDownCounter = 0;

void NFCWifiManager::loop() {
  if(!initialized) {
    return;
  }

  if(hasWifiCredentials() && !isWifiConnected()) {
    wifiDownCounter++;

    Serial.println("WiFi down: " + String(wifiDownCounter));

    if(wifiDownCounter > 10) {
      setURL(NFC_WIFI_STATUS_WIFI_FAILED);

      preferences.clear();
      WiFi.disconnect(true, true);
      delay(10);

      ESP.restart();
    }
  } else {
    wifiDownCounter = 0;
  }

  NDEF_TypeDef type = st25dv.readNDEFType();

  if(type == URI_WIFITOKEN_TYPE) {
    Serial.println("URI_WIFITOKEN_TYPE");

    sRecordInfo_t recordInfo;
    uint16_t ret = st25dv.getNDEF()->NDEF_IdentifyNDEF(&recordInfo);
    if(ret) {
      Serial.print("NDEF_IdentifyNDEF failed: " + String(ret));
      return;
    }

    sWifiTokenInfo wifiToken;
    st25dv.getNDEF()->NDEF_ReadWifiToken(&recordInfo, &wifiToken);

    String ssid = String(wifiToken.NetworkSSID);

    if(ssid.length() == 0) {
      setURL(NFC_WIFI_STATUS_INVALID_SSID);
      return;
    }

    String password = String(wifiToken.NetworkKey);

    preferences.putString("ssid", ssid);
    preferences.putString("password", password);

    startConnect(ssid, password);
  } else if(type == WELL_KNOWN_ABRIDGED_URI_TYPE) {
    sRecordInfo_t recordInfo;
    uint16_t ret = st25dv.getNDEF()->NDEF_IdentifyNDEF(&recordInfo);
    if(ret) {
      Serial.println("NDEF_IdentifyNDEF failed: " + String(ret));
      return;
    }

    sURI_Info uriInfo;
    ret = st25dv.getNDEF()->NDEF_ReadURI(&recordInfo, &uriInfo);
    if(ret) {
      Serial.println("NDEF_ReadURI failed: " + String(ret));
      return;
    }

    if(String(uriInfo.URI_Message) ==
       buildURL(NFC_WIFI_STATUS_WIFI_CONNECTING)) {
      if(isWifiConnected()) {
        setURL(NFC_WIFI_STATUS_WIFI_CONNECT_SUCCESS);
      }
    } else if(String(uriInfo.URI_Message) !=
              buildURL(NFC_WIFI_STATUS_WIFI_CONNECT_SUCCESS)) {
      String oldServerAddress = getServerAddress();

      String newServerAddress = "";
      newServerAddress += String(uriInfo.protocol);
      newServerAddress += String(uriInfo.URI_Message);

      newServerAddress = removeTrailingSlash(newServerAddress);

      if(oldServerAddress != newServerAddress) {
        preferences.putString("serverAddress", newServerAddress);
      }
    }
  }
}

Discussions