element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
RFID or NFC
  • Challenges & Projects
  • Project14
  • RFID or NFC
  • More
  • Cancel
RFID or NFC
Project14: RFID or NFC Competition Using web-NFC to help catch BAT’s (i.e. Buses & Trains)
  • Blog
  • Forum
  • Documents
  • Events
  • Leaderboard
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join RFID or NFC to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: BigG
  • Date Created: 13 Dec 2022 4:21 PM Date Created
  • Views 3496 views
  • Likes 8 likes
  • Comments 9 comments
  • web-NFC
  • rfidornfcch
Related
Recommended

Using web-NFC to help catch BAT’s (i.e. Buses & Trains)

BigG
BigG
13 Dec 2022
Using web-NFC to help catch BAT’s (i.e. Buses & Trains)

Table of Contents

  • Introduction
  • Getting started with web-NFC
  • Developing a web-NFC app
  • Writing data to NFC tags
  • My web-NFC BAT’s app
  • Video Demo

Introduction

image

I decided to dust off the cobwebs covering my box of NFC tags/readers/antennas and give the new w3.org web-NFC API (draft specification - Sept 2022) a go, as it has been on my wish list ever since I saw this Google Chrome Developer's YouTube video over a year ago.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Basically, web-NFC is a JavaScript API for your webpage, which allows a limited number of Internet browsers to read from or write to nearby NFC cards/tags or multipurpose NFC controller devices using an NFC enabled Android OS mobile phone (IOS is not supported). The following Internet browsers (image source: https://developer.mozilla.org) can be used at the moment:

image

MDN Web Docs class web-NFC as experimental technology.

My idea for this Project14 competition was to use an NFC tag as a smart-location trigger to get the latest bus or train arrival data on my nfc-enabled phone without having to download an app beforehand to do so. I would simply open up a website, tap on the tag to read data and then use this data to request real-time arrival data. So in this case my NFC project does not involve any build using custom electronics (the hard bit), besides using a mobile phone and some NFC tags, and as I had already developed an application to grab train arrivals data (see blog here) I was almost halfway there on the software side.

So even though there's no Arduino or Raspberry Pi involved, I am still hopeful that others will find this web-NFC example useful when it comes to developing their own future project ideas.

Getting started with web-NFC

The API itself is quite simple, but as this low level API will not work on laptops or desktops the real challenge when developing your application is finding a way to test your app on your phone.

This presented me with a new challenge, as I never had this restriction before, but thankfully Github pages worked really well for this task as there is no need for backoffice server-side code for web-NFC and with GitHub you can also literally code online.

For those who don’t know what GitHub pages is, it is a (free-to-use) static-site hosting service that takes your HTML, CSS, and JavaScript files from a user defined (open) GitHub repository and publishes these files online as a website. To learn more about GitHub pages, click here.

Once you have updated your files, these are available almost immediately after a short validation build process. It’s worth noting though that page caching, especially with any javascript files, can often hinder matters by delaying the file update by a good couple of minutes (I found that this usually happens during peak Internet demand periods, despite using the no cache setting).

Then to debug your app you need to enable Google’s Android developer option on your phone to allow app debugging via USB.

Once this has been enabled and you have plugged your USB cable into your phone and your computer, you can then open up your new GitHub based webpage on your phone’s Chrome browser and then you can activate the really clever bit on your computer’s Chrome browser by entering this command:

chrome://inspect/#devices

This opens up a new screen on your computer:

image

Then there is a page inspect option, which opens up a new interface showing an emulation of the phone screen and the Chrome debug options such as the screen console and the source files (this screen is useful to check that the file version is the most up-to-date and not a cached version).

image

With these tools at your disposal, you’ll then be able to develop app code and test.

Developing a web-NFC app

As you are limited by hardware (i.e. phone only) and browser software, it is definitely worth starting with a "if ("NDEFReader" in window)" is true to make sure you are good to go, otherwise it’s a no-go and you'll have to get your code do something else.

Then the next step very much depends on the type of application you want to develop, as in, will it be read only, write only or both read & write. Also, will you want to restrict/prevent write access by making the tag read only etc. In my use-case, I only needed to read NFC cards/tags and I also planned to read NDEF messages. Thus my requirements fitted very well with web-NFC functionality.

To activate your webpage to read NFC cards, you first need to create a new NDEFreader() object.

const ndef = new NDEFReader();

Then you initiate the scan method, i.e. NDEFReader.scan()

This returns a “Promise”, which either resolves when an NFC tag is read or it rejects if a hardware or permission error is encountered. The online MDN documentation, provides this handy example to demonstrate the use of a promise:

const ndef = new NDEFReader();
ndef.scan().then(() => {
  console.log("Scan started successfully.");
  ndef.onreadingerror = (event) => {
    console.log("Error! Cannot read data from the NFC tag. Try a different one?");
  };
  ndef.onreading = (event) => {
    console.log("NDEF message read.");
  };
}).catch((error) => {
  console.log(`Error! Scan failed to start: ${error}.`);
});

However, I found the more common approach, based on online examples, was to use an “await ndef.scan();” method within an async function that is triggered by an event like a button press.

According to MDN documentation, the await expression never blocks the main thread and only defers execution of code that actually depends on the result. That’s pretty neat, in my opinion.

An example of this method can be found here (this website also include a demo option, if you open URL on a nfc-enabled Android mobile phone using a compatible browser): https://googlechrome.github.io/samples/web-nfc/

What is really really helpful from a development and user perspective, is that the scan method will also trigger a permission prompt if the "nfc" permission on your mobile phone has not been previously granted.

Finally, we add in some “Event Listeners” to capture either a read event or a read error event.

And that is basically it!

You have data. This can be found within a message.records data structure. You also have options to extract meta information about the data such as recordType and encoding (for text-based NDEF messages). It’s all quite handy.

Writing data to NFC tags

image

You can also use web-NFC to write to tags. Similar to the read() method, the write() method will attempt to write an NDEF message to a tag, which returns a “Promise” when either a message has been written to the tag or it has been rejected due to some hardware or permission error.

For my project there was no need for the end-user to write to the tag as it would be done by the product owner etc. So, I simply used a mobile phone app called NFC TagWriter from NXP to write to my tags.

image

For my demo project I decided to use a Geo-location NDEF message, which includes data about the stop and its latitude and longitude coordinates for the bus stop or train station location. This also allowed me to include a text description for additional information.

My web-NFC BAT’s app

image

For my webpage I used the almost ubiquitous Bootstrap front-end toolkit.

In fact I simply modified the getbootstrap “products” example to suit my purposes, as those mobile phones on the homepage looked like buses (top down view) to me. The mods also included references to the relevant online CDN links.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="A real-time public transport arrivals demo using web nfc (proof of concept - alpha)">
    <meta name="keywords" content="Internet of Things, IoT, NFC, Geolocation, RTPI, Proof of Concept Design" />
    <meta name="author" content="C Gerrish">
    <meta name="robots" content="noodp"/>
    <title>Super Bus::Train (NFC Web Demo)</title>

    <link rel="canonical" href="https://getbootstrap.com/docs/5.2/examples/product/">

     <!-- CSS only -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">

    <style>
      .bd-placeholder-img {
        font-size: 1.125rem;
        text-anchor: middle;
        -webkit-user-select: none;
        -moz-user-select: none;
        user-select: none;
      }

      @media (min-width: 768px) {
        .bd-placeholder-img-lg {
          font-size: 3.5rem;
        }
      }

      .b-example-divider {
        height: 3rem;
        background-color: rgba(0, 0, 0, .1);
        border: solid rgba(0, 0, 0, .15);
        border-width: 1px 0;
        box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
      }

      .b-example-vr {
        flex-shrink: 0;
        width: 1.5rem;
        height: 100vh;
      }

      .bi {
        vertical-align: -.125em;
        fill: currentColor;
      }

      .nav-scroller {
        position: relative;
        z-index: 2;
        height: 2.75rem;
        overflow-y: hidden;
      }

      .nav-scroller .nav {
        display: flex;
        flex-wrap: nowrap;
        padding-bottom: 1rem;
        margin-top: -1px;
        overflow-x: auto;
        text-align: center;
        white-space: nowrap;
        -webkit-overflow-scrolling: touch;
      }
    </style>

    
    <!-- Custom styles for this template -->
    <link href="product.css" rel="stylesheet">
  </head>
  <body>
    <header class="site-header sticky-top py-1">
      <nav class="container d-flex flex-column flex-md-row justify-content-between">
        <a class="py-2" href="#" aria-label="Product">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="d-block mx-auto" role="img" viewBox="0 0 24 24"><title>Super Bus</title><circle cx="12" cy="12" r="10"/><path d="M14.31 8l5.74 9.94M9.69 8h11.48M7.38 12l5.74-9.94M9.69 16L3.95 6.06M14.31 16H2.83m13.79-4l-5.74 9.94"/></svg>
        </a>
        <a class="py-2 d-none d-md-inline-block" href="#">Fares and Tickets</a>
        <a class="py-2 d-none d-md-inline-block" href="#">Routes and Timetables</a>
        <a class="py-2 d-none d-md-inline-block" href="#">About Us</a>
        <a class="py-2 d-none d-md-inline-block" href="#">News Centre</a>
        <a class="py-2 d-none d-md-inline-block" href="#">Contact Us</a>
      </nav>
    </header>

    <main>
      <div class="position-relative overflow-hidden p-3 p-md-5 m-md-3 text-center bg-light">
        <div id="bus-header" class="col-md-5 p-lg-5 mx-auto my-5">
          <h1 class="display-4 fw-normal">It's Super Bus!</h1>
          <h3 class="display-5 fw-normal">(and train)</h3>
          <p class="lead fw-normal">Offering you a frequent reliable service and super comfy seating for your posterior.</p>
          <a class="btn btn-light" href="#">Just wait for it...</a>
        </div>
        <div class="product-device shadow-sm d-none d-lg-block"></div>
        <div class="product-device product-device-2 shadow-sm d-none d-lg-block"></div>
      </div>

      <div class="d-md-flex flex-md-equal w-100 my-md-3 ps-md-3">
        <div class="text-bg-dark me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden">
          <p id="nfc-error" class="d-none lead">Web NFC is not available. Use Chrome on Android.</p>
          <div id="nfc-pass" class="d-none">
            <div class="my-3 py-3">
              <img src="bus-stop.svg" style="height: 100%; width: auto;" alt="...">
              <h2 class="display-5">RTPI</h2>
              <p class="lead">Real Time Passenger Information.</p>
              <a id="scan_btn" class="btn btn-lg btn-outline-light" onClick="startScanning()">Where's my transport?</a>
            </div>
            <div id="arrivals_canvas" class="d-none bg-light shadow-sm mx-auto" style="width: 94%; height: auto; border-radius: 18px 18px 0 0;">
              <div class="row align-items-start">
                <div class="col col-md-12">
                  <div class="card" style="width: 100%;">
                    <div class="card-body text-dark text-start">
                      <h5 id="arrivals_header" class="card-title"></h5>
                      <p id="arrivals_data" class="card-text"></p>
                    </div>
                  </div>
                </div>
              </div>   
              <div class="row align-items-start">
                <div id="bus_btn" class="d-none my-2 py-2">
                  <a class="btn btn-success" onClick="getBusData()">Get Bus Data...</a>
                </div>
                <div id="train_btn" class="d-none my-2 py-2">
                  <a class="btn btn-primary" onClick="getTrainData()">Get Train Data...</a>
                </div>
                <div id="map_btn" class="d-none">
                  <a class="btn btn-secondary" onClick="getMapData()">Map of Location</a>
                </div>
                <div id="stop_btn" class="d-none my-2 py-2">
                  <a class="btn btn-danger" onClick="stopScan()">Stop Scanning</a>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div class="bg-light me-md-3 pt-3 px-3 pt-md-5 px-md-5 text-center overflow-hidden">
          <div class="my-3 p-3">
            <h2 class="display-5">Travel Updates</h2>
            <p class="lead">Latest news about service.</p>
            <a class="btn btn-lg btn-outline-dark" href="#">Subscribe to RSS</a>
          </div>
          <div class="bg-dark shadow-sm mx-auto" style="width: 94%; height: auto; border-radius: 18px 18px 0 0;">
          </div>
        </div>
      </div>
    </main>

  <footer class="container py-5">
    <div class="row">
      <div class="col-12 col-md">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="d-block mb-2" role="img" viewBox="0 0 24 24"><title>Product</title><circle cx="12" cy="12" r="10"/><path d="M14.31 8l5.74 9.94M9.69 8h11.48M7.38 12l5.74-9.94M9.69 16L3.95 6.06M14.31 16H2.83m13.79-4l-5.74 9.94"/></svg>
        <small class="d-block mb-3 text-muted">© Gerrikoio 2022</small>
      </div>
      <div class="col-6 col-md">
        <h5>Features</h5>
        <ul class="list-unstyled text-small">
          <li><a class="link-secondary" href="#">Cool stuff</a></li>
          <li><a class="link-secondary" href="#">Random feature</a></li>
          <li><a class="link-secondary" href="#">Team feature</a></li>
          <li><a class="link-secondary" href="#">Stuff for developers</a></li>
          <li><a class="link-secondary" href="#">Another one</a></li>
          <li><a class="link-secondary" href="#">Last time</a></li>
        </ul>
      </div>
      <div class="col-6 col-md">
        <h5>About</h5>
        <ul class="list-unstyled text-small">
          <li><a class="link-secondary" href="#">Team</a></li>
          <li><a class="link-secondary" href="#">Locations</a></li>
          <li><a class="link-secondary" href="#">Privacy</a></li>
          <li><a class="link-secondary" href="#">Terms</a></li>
        </ul>
      </div>
    </div>
  </footer>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js" integrity="sha384-IDwe1+LCz02ROU9k972gdyvl+AESN10+x7tBKgc9I5HFtuNz0wWnPclzo6p9vxnk" crossorigin="anonymous"></script>
    <script src="arrivals.js"></script>  
  </body>
</html>

All I then had to do was add in some buttons and some custom JavaScript. For my JavaScript code I decided to use JQuery as it offers some useful RESTful http(s) GET/POST functions, some helpful event handling functions and some neat animation features.

let ndef;
let AbortCtrlr;
let geoLoc = "";
let placeID = "";
let stnName = "";
let trainLink = "";
let busLink = "";

$(document).ready(async function() {
  if (!("NDEFReader" in window)) {
    $('#nfc-error').hide().removeClass('d-none').fadeIn();
  }
  else {
    $('#nfc-pass').hide().removeClass('d-none').fadeIn();
    try {
			await getGoogs();
    }
    catch (error) {
      console.log("Argh fetch! " + error);
    }
  }
});

async function getGoogs() {
	let response = await fetch("googs.abc");
	if(response.status != 200) {
		throw new Error("Server Error");
	}
	// read response stream as text
	let text_data = await response.text();
	var textArr = text_data.split(',');
	if (textArr.length == 2) {
		trainLink = textArr[0].substring(3);
		busLink = textArr[1].substring(3);
	}
}

async function startScanning() {
  let URLfind;
  let TXTfind;
  $('#arrivals_canvas').hide().removeClass('d-none').fadeIn();
  $('html, body').animate({ scrollTop: $("#nfc-pass").offset().top }, 300);
  try {
    ndef = new NDEFReader();
    AbortCtrlr = new AbortController();
    const signal = AbortCtrlr.signal;
    await ndef.scan({ signal });
    $('#scan_btn').text("NFC Scan Active...");
    $('#scan_btn').removeClass('btn-outline-light');
    $('#scan_btn').addClass('btn-warning disabled');
		$('#bus_btn').hide().addClass('d-none');
		$('#train_btn').hide().addClass('d-none');
		$('#map_btn').hide().addClass('d-none');
		$('#stop_btn').hide().addClass('d-none');
		$('#arrivals_header').text("Waiting for tag data...");
		$('#arrivals_data').text("");

    ndef.addEventListener("readingerror", () => {
			$('#arrivals_header').text("Read Error:");
      $('#arrivals_data').text("Argh! Cannot read data from the NFC tag. Try another one?");
    });

    ndef.addEventListener("reading", ({ message, serialNumber }) => {
			$('#arrivals_header').text("Your NFC Tag Data:");
      $('#arrivals_data').text(`Tag Serial Number: ${serialNumber}`);
      $('#arrivals_data').append(`<br/>NDEF Records: (${message.records.length})`);
      
      if (message.records.length > 0 && message.records[0].recordType != "empty") {
        const decoder = new TextDecoder();
        for (const record of message.records) {
          $('#arrivals_data').append(`<br/>NDEF Record Type: (${record.recordType})`);
          $('#arrivals_data').append(`<br/>NDEF Data: (${decoder.decode(record.data)})`);
          $('#bus_btn').hide().addClass('d-none');
          $('#train_btn').hide().addClass('d-none');
           $('#map_btn').hide().addClass('d-none');
          switch (record.recordType) {
            case "text":
              const textDecoder = new TextDecoder(record.encoding);
              $('#arrivals_data').append(`<br/>Text: ${textDecoder.decode(record.data)} (${record.lang})`);
              break;
            case "url":
              break;
            case "mime":
              if (record.mediaType === "application/json") {
                $('#arrivals_data').append(`<br/>Mime JSON: ${JSON.parse(decoder.decode(record.data))}`);
              } else {
                //const textDecoder = new TextDecoder();
                //$('#arrivals_data').append(`<br/>Text: ${textDecoder.decode(record.data)}`);
              }
              break;
            case "smart-poster":
              URLfind = false;
              TXTfind = 0;
              for (const sprecord of record.toRecords()) {
                const spData = decoder.decode(sprecord.data);
                $('#arrivals_data').append(`<br/>- SP Type: ${sprecord.recordType} | Data: ${spData}`);
                if (sprecord.recordType == "url" && spData.includes("geo:53.")) {
                  const GEOlat = spData.indexOf("geo:")+4;
                  const GEOlong = spData.indexOf(",");
                  geoLoc = "query="+spData.substring(GEOlat, GEOlong)+"%2C"+spData.substring(GEOlong+1);
                  //console.log("geoLoc: "+geoLoc);
                  URLfind = true;
                }
                else if (sprecord.recordType == "text") {
                  if (spData.includes(", stop")) {
										const StnStart = spData.indexOf(", stop")+2;
										const StnEnd = spData.indexOf(", Place");
										stnName = spData.substring(StnStart, StnEnd);
										TXTfind = 1;
									}
                  else if (spData.includes(", StationCode")) {
										const StnStart = spData.indexOf("ode=")+4;
										const StnEnd = spData.indexOf(", Place");
										stnName = spData.substring(StnStart, StnEnd);
										TXTfind = 2;
									}
                  if (spData.includes("ID: ")) {
                    const placeIDstart = spData.indexOf("ID: ")+4;
                    placeID = "query_place_id="+spData.substring(placeIDstart);
                    //console.log("placeID: "+placeID);
                  }
                }
              }
              
              if (URLfind == true && TXTfind == 1) {
                $('#bus_btn').hide().removeClass('d-none').fadeIn();
                $('#map_btn').hide().removeClass('d-none').fadeIn();
              }
              if (URLfind == true && TXTfind == 2) {
                $('#train_btn').hide().removeClass('d-none').fadeIn();
                $('#map_btn').hide().removeClass('d-none').fadeIn();
              }
                
              break;
            default:
          }
        }
        $('#stop_btn').hide().removeClass('d-none').fadeIn();
      }
    });
  } catch (error) {
    $('#arrivals_data').text("Argh! " + error);
  }
}

async function stopScan() {
  await AbortCtrlr.abort();
  $('#arrivals_data').text("");
  $('#bus_btn').hide().addClass('d-none');
  $('#train_btn').hide().addClass('d-none');
  $('#map_btn').hide().addClass('d-none');
  $('#stop_btn').hide().addClass('d-none');
  $('#arrivals_canvas').addClass('d-none').fadeOut();
  $('#scan_btn').removeClass('btn-warning disabled');
  $('#scan_btn').addClass('btn-outline-light');
  $('#scan_btn').text("Where's my transport?");
  $('html, body').animate({ scrollTop: $("#bus-header").offset().top }, 500);
  
}

function getMapData() {
  if (geoLoc.length > 5 && placeID.length > 5) {
    window.open('https://www.google.com/maps/search/?api=1&'+geoLoc+'&'+placeID);
  }
}

async function getTrainData() {
	await AbortCtrlr.abort();
  $('#scan_btn').removeClass('btn-warning disabled');
  $('#scan_btn').addClass('btn-outline-light');
  $('#scan_btn').text("Where's my transport?");
	$('#stop_btn').hide().addClass('d-none');
	if (trainLink.length > 32 && stnName.length > 1) {
		const trainULS = "https://script.google.com/macros/s/" + trainLink + "/exec?station=" + stnName;
		//console.log(trainULS);
		$.getJSON(trainULS, function(data, status) {
			console.log("Status (" + status + ")");
			var items = [];
			$('#arrivals_header').text("Your " + stnName + " Train Arrivals:");
			$('#arrivals_data').text("");
			$.each( data, function( key, val ) {
				console.log("key:" + key + " | val:" + val);
				if (key == "T1" || key == "T2" || key == "T3" || key == "T4") {
					var textArr = val.toString().split(',');
					$('#arrivals_data').append(key.toString() + ": "+ textArr[0] + " due in " + textArr[1] + "<br/>");
				}
			});
			$('#arrivals_data').append("<br/>Click \"Get Train Data...\" to update.<br/>");
		});
	}
}

async function getBusData() {
	await AbortCtrlr.abort();
  $('#scan_btn').removeClass('btn-warning disabled');
  $('#scan_btn').addClass('btn-outline-light');
  $('#scan_btn').text("Where's my transport?");
	$('#stop_btn').hide().addClass('d-none');
	if (busLink.length > 32 && stnName.length > 1) {
		const busULS = "https://script.google.com/macros/s/" + busLink + "/exec?station=" + stnName;
		//console.log(trainULS);
		$.getJSON(busULS, function(data, status) {
			console.log("Status (" + status + ")");
			var items = [];
			$('#arrivals_header').text("Your " + stnName + " Bus Arrivals:");
			$('#arrivals_data').text("");
			$.each( data, function( key, val ) {
				console.log("key:" + key + " | val:" + val);
				if (key == "B1" || key == "B2" || key == "B3" || key == "B4") {
					var textArr = val.toString().split(',');
					if(textArr[1].length > 3) {
						if (textArr[1].includes("Now") == false)
							$('#arrivals_data').append(key.toString() + ": No."+ textArr[0] + " due in " + textArr[1] + "<br/>");
						else
							$('#arrivals_data').append(key.toString() + ": No."+ textArr[0] + " " + textArr[1] + "<br/>");
					}
					else {
						$('#arrivals_data').append(key.toString() + ": n/a<br/>");
					}
				}
			});
			$('#arrivals_data').append("<br/>Click \"Get Bus Data...\" to update.<br/>");
		});
	}
}

As Github hosting does not provide any back-end functionality, I decided to use Google Apps Script again as this also uses a JavaScript code base. So the reason for choosing Google Apps Script was purely for convenience and not speed as it is rather slow for this purpose but it’s just about acceptable for a proof of concept.

As before (see my previous Pico-W blog), I simply used the URL Fetch Service to retrieve the data from the real-time bus/train GTFS API.

As I was planning to grab bus arrival data, I also had to manipulate some static GTFS files for the relevant bus network. This link provides a comprehensive list of all the static files.

In my case I had to use four static files:

  1. Stops.txt
  2. Routes.txt
  3. Trips.txt
  4. Stop_times.txt

There is also a fifth file, which is relevant if you have multiple bus operators, namely Agency.txt.

Note that for most city-wide bus networks using GTFS you’ll find that these files are quite large. For example, the size of just Stop_times.txt for my regional bus network is 500MBytes, Shapes.txt (not used) is 120MBytes and Trips.txt 16MBytes.

So, to reduce the size I manually extracted out the relevant details and stored this information in Google sheets, which was attached to the Google Apps script. I won’t go into the details of all this, as it is outside the scope of this NFC project. Here is the pseudo script code for those who are interested.

image

So, as you can see it is not too complex.

Video Demo

And here is a demonstration of the system in action.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

  • Sign in to reply
  • BigG
    BigG over 2 years ago in reply to Fred27

    Yes, I don't see why not.

    One thing I really like about QR codes is that you can scan them at a distance... they even put them on billboards these days: www.researchgate.net/.../Example-of-billboard-designed-with-QR-code-QR-code-applications-are-also-used-when_fig4_357573384

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Fred27
    Fred27 over 2 years ago in reply to BigG

    That makes sense. I didn't realise you had so much data in the NFC tag. I'd assumed a QR code like https://superbus.e14/bus/12345 would be enough to give you the times you'd want if you're at bus stop no. 12345.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • BigG
    BigG over 2 years ago in reply to Fred27

    Thanks.

    Yes, your comment is perfectly valid and it was something I looked into but I couldn't find a definitive answer. Beside, the one reason I chose to use NFC was the obvious... i.e. deciding to try out something new for the sake of this project14 competition.

    This is what I did discover.

    Firstly, how data could you store in a QR code. When searching online, I found comments saying about 2kbytes but when trying out a couple of apps and online websites, the biggest QR Code I could get was about 460 bytes of text.

    So on that basis, one could argue that NFC tags can hold more data, when compared against say a 4kbyte NFC tag or the one offered by STMicroelectronics, which is a 64-Kbit ST25 tag.

    Of course, I thought to check again while writing this comment and I have now found an example in Wikipedia which has 1852 chars: https://en.wikipedia.org/wiki/QR_code#/media/File:Qr-code-ver-40.svg

    This Wikipedia page (section 5.1) also has a table which suggests that QR Codes can actually store similar amounts of data, but is dependent on type of data stored: https://en.wikipedia.org/wiki/QR_code#Storage

    Then there's data encoding.

    I'm more familiar with the NFC NDEF messaging formats, so I was not sure if QR codes could provide more complex data structures other than URL's and large alphanumeric text strings. Once again, I was being influenced by current QR code readers tested.

    So on that basis NFC may be better as it offers a mechanism to generate more elaborate data structures, which can be interpreted universally. Current QR code readers on phones limit the options... but I may be wrong here as I did not fully evaluate.

    There's also data encryption/protection of memory sectors/blocks/registers in NFC, but this is not applicable to web-NFC which can only read and write NDEF messages. So you cannot realistically use web-NFC for banking applications.

    Finally there's roll-out.

    Here, I would assume that it's easier to digitally write custom data to a NFC sticker than it is to print out custom QR stickers. Of course, I have not this at scale.

    In my demonstration I used the NDEF geolocation option, which allowed me to store geo location and a text field, which I used to store a unique identifier for the bus stop or train station together with other validation identifiers. You can go to town here as you have kbytes of data to play with (depending tag size of course).

    Hope this answers the question.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • javagoza
    javagoza over 2 years ago in reply to Fred27

    I asked myself the same question until I tried the application. In order for a web page to read the NFC tag, permissions must be given to that application. To be able to access by directly reading the URL of the tag you would need another application with permissions to access the NFC. It's a compromise and I think BigG  has solved it in a very elegant way, without having to install anything in your mobile phone. It works whenever your browser supports WebNFC.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Fred27
    Fred27 over 2 years ago

    Some awesome info. I had never heard of web-NFC but it looks really useful. You've also created a really nice example application for it.

    I have only one question about the idea behind it. I hope it doesn't sound too critical.

    I assume the NFC tag repsents the bus stop or train station. if you need to scan a QR code to open the web page, why not just add a location Id to the QR code? If the answer is "I could, but I just wanted to learn about web-NFC" then that's absolutely a good enough reason.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
>
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube