Posted on 2 Comments

Creating A Web IoT Javascript Package: NFC, Bluetooth, Web Serial, Web USB

HTML and IoT

IoT has been increasing in relevance over the past decade. The idea of connecting physical devices to a website has always intrigued me. Bringing the physical world into the digital in my opinion brings us closer together globally.

In this tutorial, I will go over how to build a WebIOT package so you can add IoT capabilities to your Javascript applications. Please like, comment, and share this article, it really helps.

Introducing the WebIoT NPM Package

Following my FOSS commitment for 2023 (check out SpeechKit and Laravel OpenAI API) my March package is called WebIOT it brings together a collection of Web APIs to easily give developers functions for interacting with IoT devices. It has classes for NFC, Bluetooth, Serial and USB. I am actively looking for PRs and constructive criticism.

Web NFC

The Web NFC API is a cool API that allows for interaction with NFC chips. It consists of a message, a reader and a record.

The Web NFC API allows exchanging data over NFC via light-weight NFC Data Exchange Format (NDEF) messages.

https://developer.mozilla.org/en-US/docs/Web/API/Web_NFC_API

You can get NFC chips really cheap on Amazon (use this link and I get a commission !) You can use NFC for all sorts of cool interactive things.

  • Extend offline activity: NFC is an offline technology, it doesn’t need to be connected to a network in order to exchange data, it gets its electricity from the close contact radio wave exchange hitting the wire (yay physics). A cool implementation is adding real-world nodes for your web game, when users tap it they get a special prize.
  • IoT device configurations: You can have users on your website get configuration data for your IoT devices without them having to download any additional software. This is extremely useful when paired with the Web Bluetooth API for GATT server configs.
  • Sending data to devices: NFC is a secure way to write data to your IoT devices and let those handle the processing

Web Bluetooth

The Web Bluetooth API provides the ability to connect and interact with Bluetooth Low Energy peripherals.

https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API

The Web Bluetooth API allows developers to connect to Bluetooth LE devices and read and write data. Some useful implementations of Web Bluetooth

  • Get local device updates
  • Run webpage functionality based on device state i.e. a heart monitor make an animation run on certain BPM

Web Serial

The Web Serial API provides a way for websites to read from and write to serial devices. These devices may be connected via a serial port, or be USB or Bluetooth devices that emulate a serial port.

https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API

Web Serial allows us to connect to generic serial ports and interact with our devices. This means we can do things like connect our webpages to embedded devices such as a Raspberry Pi.

Web USB

The WebUSB API provides a way to expose non-standard Universal Serial Bus (USB) compatible devices services to the web, to make USB safer and easier to use.

https://developer.mozilla.org/en-US/docs/Web/API/WebUSB_API

The Web USB API allows us to work directly with USB peripherals and by extension if those devices has programs, run them.

Current Browser Limitations

Most of these APIs cannot be used on iOS currently.

Listen To Some Hacker Music While You Code

Follow me on Spotify I make Tech Trap music

The Source Code

Init The Project

Creating a new project and running the npm create script

mkdir web-iot && cd web-iot
npm init

The package.json looks like this:

{
  "name": "@mastashake08/web-iot",
  "version": "1.0.0",
  "description": "Connect to your IoT devices via usb, serial, NFC or Bluetooth",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/mastashake08/web-iot.git"
  },
  "keywords": [
    "iot",
    "webserial",
    "bluetooth",
    "webusb",
    "nfc"
  ],
  "author": "Mastashake",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/mastashake08/web-iot/issues"
  },
  "homepage": "https://github.com/mastashake08/web-iot#readme"
}

The index.js file

This is the entry point for the package and it simply exports our classes

import { WebIOT } from './classes/WebIOT'
import { NFCManager } from './classes/NFCManager'
import { BluetoothManager } from './classes/BluetoothManager'
import { SerialManager } from './classes/SerialManager'
import { USBManager } from './classes/USBManager'
export {
  WebIOT,
  NFCManager,
  BluetoothManager,
  SerialManager,
  USBManager
}

The WebIOT Class

This is the base class for all our managers. It contains some functions for sending data to a remote server, a functionality that’s usually needed when dealing with IoT devices.

export class WebIOT {
  debug = false
  constructor(debug = false) {
    this.debug = debug
  }
  sendData (url, options = {}, type='fetch') {
      try {
        switch (type) {
          case 'fetch':
            return this.sendFetch(url, options)
            break;
          case 'beacon':
            return this.sendBeacon(url, options)
            break;
          default:
            return this.sendFetch(url, options)
        }
      } catch (e) {
        this.handleError(e)
      }
  }

  sendBeacon (url, data) {
    try {
      navigator.sendBeacon(url, data)
    } catch (e) {
      this.handleError(e)
    }
  }

  async sendFetch(url, options) {
    try {
      const res = await fetch(url, options)
      if (res.status != 200) {
        throw new Error(`HTTP error! Status: ${res.status}`)
      } else {
        return res
      }
    } catch (e) {
      this.handleError(e)
    }
  }

  handleError (e) {
    if(this.debug) {
      alert(e.message)
      console.log(e.message)
    } else {
      throw e
    }
  }
}

The USBManager

The USBManager is responsible for working with USB devices.

import { WebIOT } from './WebIOT'
export class USBManager extends WebIOT{
  #devices = {}
  #selectedDevice = {}
  constructor (debug = false) {
    super(debug)
  }

  async getDevices () {
    this.devices = await navigator.usb.getDevices()
    return this.devices
  }

  async requestDevice(options = {}) {
    this.selectedDevice = this.selectedDevice = await navigator.usb.requestDevice(options)
    return this.selectedDevice
  }

  async openDevice() {
    await this.connectDevice()
  }

  async closeDevice(options) {
    await this.selectedDevice.close()
  }

  async connectDevice() {
    await this.selectedDevice.open();
    if (this.selectedDevice.configuration === null)
      await this.selectedDevice.selectConfiguration(1);
    await this.selectedDevice.claimInterface(0);
    return this.selectedDevice

  }

  async writeData(endpointNumber, data) {
    return await this.selectedDevice.transferOut(endpointNumber, data)
  }
  async readData(endpointNumber, data) {
    return await this.selectedDevice.transferIn(endpointNumber, data)
  }
}

}

The SerialManager

The SerialManager class manages serial connections.

import { WebIOT } from './WebIOT'
export class SerialManager extends WebIOT {
  #ports = {}
  #selectedPort = {}
  constructor (debug = false) {
    super(debug)
  }

  async getPorts () {
    this.ports = await navigator.serial.getPorts()
    return this.ports
  }

  async requestPort(options = {}) {
    this.selectedPort = this.selectedPort = await navigator.serial.requestPort(options)
    return this.selectedPort
  }

  async openPort(options) {
    await this.selectedPort.open(options)
  }

  async closePort(options) {
    await this.selectedPort.close()
  }

  async getInfo() {
    await this.selectedPort.getInfo()
  }

  async setSignals(options) {
    await this.selectedPort.setSignals(options)
  }

  async getSignals() {
    return await this.selectedPort.getSignals()
  }

  async readData () {
    const reader = this.selectedPort.readable.getReader();

    // Listen to data coming from the serial device.
    while (true) {
      const { value, done } = await reader.read();
      if (done) {
        // Allow the serial port to be closed later.
        reader.releaseLock();
        break;
      }
      // value is a Uint8Array.
      return value
    }
  }

  async writeData(data) {
    const writer = port.writable.getWriter();

    await writer.write(data);
    // Allow the serial port to be closed later.
    writer.releaseLock();
  }
}

The BluetoothManager

The BluetoothManager is responsible for managing Bluetooth devices

import { WebIOT } from './WebIOT'
export class BluetoothManager extends WebIOT {
  #bluetooth = {}
  #device = {}
  #server = {}
  #selectedService = {}
  #services = {}
  #characteristic = {}
  #currentValue = null
  constructor (debug = false) {
    super(debug)
    navigator.bluetooth.getAvailability().then((available) => {
      if (available) {
        this.bluetooth = navigator.bluetooth
      } else {
        alert("Doh! Bluetooth is not supported");
      }
    });

  }

  async getDevices (options = {acceptAllDevices: true}) {
    return await this.requestDevice(options)
  }

  async requestDevice (options) {
    try {
      this.device = await navigator.bluetooth.requestDevice(options)
      return this.device
    } catch(e) {
      alert(e.message)
    }
  }

  async connectToServer () {
    this.server = await this.device.gatt.connect()
  }

  async getService (service) {
    this.selectedService = await this.server.getPrimaryService(service)
    return this.selectedService
  }

  async getServices () {
    this.services = await this.server.getPrimaryServices()
    return this.services
  }

  async getCharacteristic (char) {
    this.characteristic = await this.selectedService.getCharacteristic(char)
    return this.characteristic
  }

  async getCharacteristics () {
    return await this.selectedService.getCharacteristics()
  }

  async getValue () {
    this.currentValue = await this.characteristic.readValue()
    return this.currentValue
  }

  async writeValue(data) {
    await this.characteristic.writeValue(data)
  }
}

The NFCManager

Work with NFC tags with the NFCManager

import { WebIOT } from './WebIOT'
export class NFCManager extends WebIOT {
  #nfc = {}
  constructor (debug = false) {
    super(debug)
    if ('NDEFReader' in window) { /* Scan and write NFC tags */
      this.nfc = new NDEFReader()
    } else {
      alert('NFC is not supported in your browser')
    }

  }

  startNFC () {

    this.nfc = new NDEFReader()
  }
  async readNFCData (readCb, errorCb = (event) => console.log(event)) {
    this.nfc.onreading = readCb()
    await this.nfc.scan()
  }
  async writeNFCData (records, errorCb = (event) => console.log(event)) {
    try {
      await this.nfc.write(records)
    } catch (e) {
      errorCb(e)
    }
  }
  async lockNFCTag(errorCb = (event) => console.log(event)) {
    try {
      await this.nfc.makeReadOnly()
    } catch(e) {
      errorCb(e)
    }
  }
  static generateNFC () {
    return new NDEFReader()
  }
}

Using It In Action

USB Example

Select a device and read/write data to pin 4

import { USBManager } from '@mastashake08/web-iot'

....
const usb = new USBManager()

// get devices
const devices = usb.getDevices()

// request a single device
const device = usb.requestDevice()

// open device after connecting to it
device = usb.openDevice()

// read 64 bytes of data from pin 4 on device
const readData = usb.readData(4, 64)

// write 64 bytes of data to pin 4
usb.writeData(4, new Uint8Array(64))

Serial Example

Have a user select a serial device and write 64 bytes of data to it

import { SerialManager } from '@mastashake08/web-iot'

....

const serial = new SerialManager()

// get a port
port = serial.requestPort()

// read data
const data = serial.readData()

// write 64 bytes data
serial.writeData(new Uint8Array(64))

Bluetooth Example

Let a user select a Bluetooth device and get the battery level

import { BluetoothManager } from '@mastashake08/web-iot'

....

const bt = new BluetoothManager()

// get a device
const device = bt.requestDevice(options)

// get services
const services = bt.getServices()

// get battery service
const service = bt.getService('battery_service')

// get batter level
const char = bt.getCharacteristic('battery_level')

// get battery value
const battery = bt.getValue()

//write value

bt.writeValue(new Uint8Array(64))

NFC Example

Read, Write, and lock tags

import { NFCManager } from '@mastashake08/web-iot'

....
// start NFC
const nfc = new NFCManager()
nfc.startNFC()

//Read a tag
const data = nfc.readNFCData(successCb, errorCb)
const writeData = "Hello World"

// Write to a tag
nfc.writeNFCData(writeData)

// Lock tag
nfc.lockNFCTag(errorCb)


Did You Enjoy This Tutorial?

If so please leave a comment and like this article and please share it on social media! I post weekly so please come back for more content!

Follow Me On Social Media

Follow Me On Youtube!

Follow my YouTube account

Get Your Next Domain Cheap & Support The Channel

I use Namecheap for all of my domains! Whenever I need a cheap solution for a proof-of-concept project I grab a domain name for as little as $1! When you sign up and buy your first domain with Namecheap I get a commission, it’s a great way to get a quality service and support this platform!

Get Your Next Domain Cheap
CLICK HERE

Become A Sponsor

Open-source work is free to use but it is not free to develop. If you enjoy my content and would like to see more please consider becoming a sponsor on Github or Patreon! Not only do you support me but you are funding tech programs for at risk youth in Louisville, Kentucky.

Join The Newsletter

By joining the newsletter, you get first access to all of my blogs, events, and other brand-related content delivered directly to your inbox. It’s 100% free and you can opt out at any time!

Check The Shop

You can also consider visiting the official #CodeLife shop! I have my own clothing/accessory line for techies as well as courses designed by me covering a range of software engineering topics.