Skip to main content

Unreal TV



Picture the scene.

You are a "successful" producer of a string of mind-numbingly boring reality TV shows that have seen ratings the height of Simon Cowell's belt. At your very best, you have provided an invaluable public service by dispatching yesteryear's celebrities to the remote wilderness (only for them to make their way back again, twice as famous). At your worst, one eternal optimist seeking stardom after another have braved your stage to sing yet another rendition of I Will Always Love You... and engaged in the occasional burst of energetic Greek-Cypriot dancing.

But you have come to learn a very important lesson of show-business. That the audience always want more.

...So other than (once again) live-streaming housemates sleeping soundly after a heated argument about the direction in which loo roll should dispensed, what other tricks do you have up your sleeve to hold the attention of the modern day audience who are increasingly distracted by Instagram and... Instagram? Could anything top the magical moment in which a contestant wins a coveted prenuptial agreement with a millionaire* through 6 weeks of televised bickering?

*Inevitable plot twist: the currency is in South Korean Won.

Sure! Why not live-stream the indoor air quality of the contestants' unkept dormitory in real time to see your ratings take off like Stelios's latest venture: easyGas.

It's day 123,198 in the Big Brother house, the earth has been hit by a stray asteroid, yet the housemates are still discussing the effects of poor indoor air quality on the health and well-being of an averagely-sized meerkat. As a result, Nasty Nick and Craig have become embroiled in a row about the benefits of monitoring Total Volatile Organic Compounds using a microprocessor purchased from AliExpress. Oh, what TV gold!

Welcome to this latest realty television series in which we - quite literally - measure quality of the inhabitants' household and broadcast it to the world...


^^No jacuzzis were involved in the making of this post.^^

Dancing on thin ice:

It's - allegedly - our first time ever in an ice rink (if you discount that time we spent 7 years starring in a West End spectacular once described by the Daily Mail as "Les Misérables, but without the heartache, the French, and on ice"). And we don't mind breaking a collar bone or two in an attempt to reinvigorate our telly career by landing a role in an ad about life insurance.

Here are all the props we decided to juggle while being launched at 50km/hour along the surface of 10 tonnes of ice, dressed in a diner apron, while pretending to mouth the words to Greased Lightnin'.

  • The revered ESP32 development board returns to this series once again like Darcey in 90 Days FiancĂ©. After all, we need a small, reliable (but slightly vocal) microprocessor to take indoor air quality readings using our attached sensor, and later, to dispatch them to the cloud like a true IoT superstar. We'll continue to run MicroPython firmware on our thing because that's just the way we strut around the dance floor for votes on elimination night (sequins and fake tan are strictly optional for this experiment).
  • We're using an I2C-based Sensirion SGP30 air quality sensor breakout board from Pimoroni which measures... wait for it... Total Volatile Organic Compounds (TVOC) and Equivalent Carbon Dioxide (CO2eq). We'll continue to mention them like we know what they are. Yet we now know enough to be alarmed if we see them listed as the main ingredients on our favourite energy drink  (you know, the one that stops us from sleeping for at least a week after the first sip).
  • We also have a Bosch BME280 sensor thrown into the mix for good measure. Not because we want to write yet another post featuring one. But because it will provide us with three more data points (temperature / pressure / humidity) that makes this experiment a little more exciting. Sure, we have a very low bar when it comes to excitement.
  • Our AWS IoT setup has featured a Greengrass Core device running on a Raspberry Pi for sometime now. So why not use one again? It'll continue to accept MQTT traffic from our ESP32 development board, and via a configured Greengrass Subscription, forward it to AWS IoT Core. It's edge-of-the seat entertainment. No really, that's where we actually last spotted our Raspberry Pi. Of course, Greengrass isn't mandatory. It's arguably simpler to send MQTT directly to AWS IoT Core.


But, really, this post isn't about the hardware, or the sensors. Or the inconspicuous product placements.

It's actually about finding a gullible willing audience with a penchant for staring at very little happening on-screen, preferably, all day, everyday. Which is why the remainder of our time will be spent setting up a simple website that will broadcast sensor readings, delivered directly from the diary room of an unimportant house.

One Bourne every minute:

Like Cops, or its significantly politer British offshoot often dealing with minor scuffles caused by an ineligible discount coupon presented at a Burger King franchise at a M4 service station, this series keeps coming back at us with multiple felonies of an IoT kind.

Here are past episodes that the current series has given birth to live on air, while unappreciative late night viewers opted instead to sleep through yet another repeat of a Bourne film on Channel 4.

  1. Gold Filing
  2. Castle Track-a-lot
  3. Chariots of Wire
  4. Athlete's Foot
  5. SD:S3 The Untold Love Story
  6. Rage:MKR
  7. Battle of BLEtain
  8. Bruce's Site is Wise

The Great British make off:

Contrary to our initial expectations, this plot quickly thickens like Mary Berry's dough. So please bear with us while we frantically throw screenshots and code at you like a frenzied contestant fighting for survival in Gladiators.

According to a review found in the Radio Times, next to a sensationalist article about a cop who arrested himself while failing to steal his own car (a vehicle he would later go on to marry), the scandalous storyline will unfold a little like this...

  • The season opener features a dramatic love triangle between BME280 and SGP30 sensors, and an ESP32 development board, all against the romantic, picturesque backdrop of a Tuscan breadboard. We gather temperature, pressure and humidity readings from the BME280 sensor, and Total Volatile Organic Compounds (TVOC) and Equivalent Carbon Dioxide (CO2eq) values from the SGP30. Undoubtedly, further episodes should not have been commissioned based on audience feedback. But they were. So live with it. 
  • By now, you will know that we are able to configure AWS IoT Greengrass Core in such a way to receive JSON payloads from our ESP32 development board via MQTT. And, moreover, we can forward these to AWS IoT Core via a Subscription. The slight "twist" to this tale is that we are sending our payloads to the device's designated MQTT topic used to update its Shadow document. In this way, we'll have the latest reported measurements stored against the thing in AWS IoT Core. No long term storage. No time-series databases. Easy as banoffee pie, right?
  • Well, steady on. What's that we smell burning in Gordon Ramsey's kitchen (spoiler: it's not the contestants)?! Did we say something about creating a website to display our current readings? We sure did. You see, websites mainly consist of static files. HTML files. CSS files. JavaScript files. X-Files. Nail files. Yep. Quite often, a tonne of them. We're going to host these using a S3 Bucket. Not because we couldn't use any other website hosting solution, but because we can get this bit over and done with quickly using S3 and spend our remaining time whisking together other services.
  • We absolutely do not want anyone and everyone to be able to view our beautiful sensor readings. Not without being registered to do so, and even then, only after logging in. How else are we going to bombard them with adverts from our premier sponsor - the Advertising Standards Authority*. And where there is personal information involved, and credentials, we ought to secure the site. We will use a Content Delivery Network (CDN) - CloudFront - to front the static content stored in our S3 Bucket - not because we need all the security, availability and performance enhancing features of a CDN, but because we want to simply enable encryption (HTTPS). 

*To our surprise, it transpires that a) the Advertising Standards Authority do themselves bombard us with ads, and b) there was actually a Nintendo Wii game based on I'm a Celebrity Get Me Out of Here. We learn something useful everyday.


 
  • Lastly, we fiddle around with AWS Cognito to create our website's user database (repository), and give them permissions to access our device's Shadow document through APIs.
  • But for the true icing on the cake, the "signature challenge" if you will, by hacking together a website using the AWS JavaScript SDK, we can now come up with a crude login page to authenticate a user, and then afterwards, to let them view the amazing measurements ("probably the most amazing sensor readings they have ever seen") from the comfort of their own home, using a browser.

Phew, that was rather frantic! Let's get moving before Paul Hollywood hovers over our breadboard and starts to prod our wobbly JavaScript code.

How clean is your house? (not very)

Indoor Air Quality (IAQ) sensors aim to measure (you guessed it!) air quality of an indoor environment. A Sensirion SGP30 is one such device, and as its datasheet makes clear, it primarily reports back two measurements: Total Volatile Organic Compounds (TVOC) measured in Parts per Billion (ppb) and Equivalent Carbon Dioxide (CO2eq) measured in Parts per Million (ppm). And it goes without saying, we feel highly qualified just uttering these words like some concerned scientist at a UN conference. Yet we aren't going to delve into the science behind these readings, nor whether their values signify that the air quality is "good" or "bad" - primarily because we don't want to get sued by anyone for giving out duff medical advice.

OK, we get it. We have some new data points. And we could be doing many other things with the data.

Let's play Whitney's 1992 tearjerker and roll the soppy montage of memorable moments from previous series:


Here, we are simply using the sensor readings to update the thing's Shadow. What is a Shadow? A device's Shadow is a JSON document that is used to store and retrieve current state information for a device. How do we know this? Because that exact sentence was shamelessly plagiarised in full from the Developer Guide.


So our intention is for our device to - in real time - update its digital twin in the cloud by updating this JSON document. And by using a service that can interact with AWS IoT - such as the AWS JavaScript SDK - we can query this data and present it back in any way we see fit. For example, as a webpage in a browser. Or a Super Bowl ad (funding permitting).

We don't profess to knowing much about web development. But we know that by using HTML, JavaScript, CSS files and some pretty images, we can create content that can be presented in a browser. But where do we host them? After all, they need to be accessible by anyone, from anywhere - over the information superhighway some (actually, most) call the INTEEERNEEEET.

S3 is an AWS object storage solution, and is an ideal dumping ground for static files that need to be accessible to the masses. Moreover, using CloudFront, we can apply a little control to the way in which the Bucket is accessed, for example by adding encryption in transit (HTTPS), and making use of other typical CDN capabilities such as caching of static content and delivery from edge locations closer to the end user. This way, if the checking of a random ppm measurement becomes viral overnight, because the Kardashians Tweeted about it, we are prepared.

Lastly, most websites have the ability to authenticate users. AWS Cognito gives our website the ability store and manage external users, along with the ability for them to access our AWS services in a controlled fashion. We'll create User Pools and Identity Pools that fulfil this requirement, and embed these into our JavaScript code, to allow users to authenticate first before being authorised to query our sensor data.

Eventually, the result looks like this. If the selected icons look a little odd, and the page looks familiar, it is because we recycled majority of HTML and JavaScript code we put together back in Taking a Peak: Xtreme2 Edition.


Let's get clicking!

Let's say yes to the mess:

Meet our new pal.

This little nifty device is our SGP30 indoor air quality monitor breakout board from Pimoroni. It requires power to function (obviously), and it can be reached from a microprocessor using the venerable I2C protocol (less obviously).


We have also decided to daisy-chain another clever device - the Bosch BME280 temperature / pressure / humidity sensor - to the identical I2C bus because we can reuse the same SCL (GPIO 18) and SDA (GPIO 19) pins this way.

Let's see if we can instantiate a MicroPython i2c object and detect the I2C addresses of our devices.

from machine import I2C, Pin

I2C_SCL_GPIO = const(18)
I2C_SDA_GPIO = const(19)

i2c = I2C(scl=Pin(I2C_SCL_GPIO, Pin.OUT), sda=Pin(I2C_SDA_GPIO, Pin.OUT), freq=400000)
i2c.scan()


Yep, there they are. Their bare I2C addresses displayed in all their natural glory (where 88 is the SGP30 address, 118 the BME280).

Just like week 1 at the Big Brother house. Unashamedly, everything is on show.


We've interacted with a BME280 sensor a gazillion times before. Mostly, using this handy little MicroPython library. But a simple refresher isn't going to harm the environment.

Here's how it's used.

import bme280_float

bme = bme280_float.BME280(i2c=i2c)
temp_c, pressure_pa, humidity_perc = bme.read_compensated_data()
print(
    "Temperature (c): " + str(temp_c) + "\n" +
    "Pressure (Pa): " + str(pressure_pa) + "\n" +
    "Humidity (%): " + str( humidity_perc)
)

I'm an amateur meteorologist (armed with some semi-believable sensor readings), get me out of here!


But with these shows, nothing is ever plain sailing though, is it? There's always unwarranted drama artificially injected in that makes 100-minute of already questionable entertainment last for well over 100 days. Like that ex that mysteriously washes up onshore in an inflatable banana boat, and starts to serenade the group with an ukulele.

We started off using this SGP30 MicroPython library from Adafruit, but it didn't work for us. It appeared to error on the line checking the feature set (perhaps there have been updates made to the SGP30 hardware recently), and more inconveniently for us, the library also relies on having some other Adafruit modules. So we forked it and created our very own.

It works in a near identical way, except we've attempted to include a larger number of commands supported by the SGP30 for testing purposes.


Here's how we use it to obtain the co2eq_ppm and tvoc_ppb values.

import time
import uSGP30

sgp30 = uSGP30.SGP30(i2c)
while True:
    co2eq_ppm, tvoc_ppb = sgp30.measure_iaq()
    print(
        "Carbon Dioxide Equivalent (ppm): " + str(co2eq_ppm) + "\n" +
        "Total Volatile Organic Compound (ppb): " + str(tvoc_ppb)
    )
    time.sleep(5)

Let's give this a quick go in our room.


Nothing much happening here. Our air still tastes like air. And is still breathable. Phil Collins most definitely cannot yet feel it in the air tonight. It's safe to carry on with our work.

But when we breath on the sensor, or light a flame near it, we start to see a significant movement in the readings - which is a positive sign. Because it appears to be monitoring something.

In the words of our Phil, I've been waiting for this moment, for all my life.

Oh Lord. Oh Lord.


Here's a word of caution about the SGP30 indoor air quality sensor - it appears to require some calibration before it starts to register readings. Take a look, for example, at the SGP30 application note. It states that the sensor takes around 15 seconds to initialise, and has to run for a minimum of 12 hours until a valid baseline is stored (unless one was stored previously). If you would like to learn more, we recommend reading this helpful Adafruit document. Interestingly, the SGP30 also allows for the readings to be compensated for humidity / temperature. As we have a humidity / temperature sensor in the form of the BME280, we could also feed the relative humidity reading back to the SGP30 using the equation provided in the datasheet.

As is always the case, before we dispatch these readings to our Greengrass Core device using MQTT, we're going to package the data up into a JSON payload.

import ujson

shadow_data = {"state": {"reported": {}}}
data = {}
data["temp_c"] = temp_c
data["pressure_pa"] = pressure_pa
data["humidity_perc"] = humidity_perc
data["co2eq_ppm"] = co2eq_ppm
data["tvoc_ppb"] = tvoc_ppb
shadow_data["state"]["reported"] = data
ujson.dumps(shadow_data)

You may ask: why are we organising the data under the state and reported keys?

Because that's exactly how AWS IoT expects it if we are to use this data to directly update the device's Shadow document as explained here. We can have other states such as desired and delta, but we don't intend to use those.


The MicroPython code running on our ESP32 development board can be found at the end of this post.

That's it for our sensor readings. Please return after the ad break to see us battle a hyena with a soldering iron.

...

OK. Where were we? In some sort of remote island prison colony, ready to dispatch sensor readings to AWS IoT Core, via our (optional) local Greengrass Core device, using the MicroPython umqtt.simple library. And when we do, we ensure that we're publishing to the very specific MQTT topic that automatically updates the thing's Shadow document. Our device is registered in AWS IoT as this-iaq-thing so our exact MQTT topic looks like this:

$aws/things/this-iaq-thing/shadow/update

Once all this is done, we should see our messages arriving at AWS IoT like extortionate SMSs from the viewers on results night. But not only that, they are updating the device shadow's reported state in real-time... all without the voice-over of Ant and Dec. Magic!



Now, we are ready to move onto the stitching together of a simple website which we'll use to display our results to the enthralled users.

Websites generally require static content, such as HTML and JavaScript files, to be stored somewhere. That somewhere has to be accessible over the internet, to everyone. And so that somewhere might as well be a S3 Bucket.

We create a S3 Bucket, creatively named this.iaq.thing, and have it opened up for "read only" access by the general public.


The precise permissions granted in the Bucket Policy will be GetObject.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::this.iaq.thing/*"
        }
    ]
}


Temporarily, for testing purposes, we will enable Static Website Hosting for this Bucket as this gives us the easiest route for users to access our files over the internet using a browser.


Upload a simple index.html file to the root of the Bucket, and by navigating to its URL, it will be possible to view the unimpressive webpage in our favourite browser.


There we go. What an astonishing work of art. We're off to the Dragon's Den to pitch for a 1% stake in our £200 million business providing therapeutic index.html files to the busy UK public.


But hang on a second. Deborah Meaden is not impressed!

None of this feels very secure. As diligently reminded by our browser, we haven't used HTTPS so the web traffic to our site (Bucket) isn't encrypted. Which is a bad place to be when we are about to go and configure a login page that processes a user's credentials.

So we're going to quickly address this by disabling the Static Website Hosting feature on our Bucket.

Yes, we're going to present our S3 Bucket to the world, slightly differently.


This CDN isn't CloudFlare (but we would be forgiven for mixing the names up). We'll front our S3 Bucket using CloudFRONT.**

**Feeble excuse stated upfront for any occurrences of "CloudFlare" in the remainder of this post.

Firstly, we create a Distribution. Here, we configure our S3 Bucket to be the Origin to tell CloudFront that this Bucket is exactly where traffic from users ultimately land.


And since much of this is being done to enable encryption (HTTPS), we'll ensure we have a default CloudFront SSL certificate securing our service as well.


There. That's done. We have a new URL (a CloudFront one) through which we can access our S3 Bucket.


And since it has a shiny new, trustworthy certificate installed, it happily accepts our traffic using HTTPS.


If we want to doubly make sure that people are unable to access the S3 Bucket directly unless they come through CloudFront there is a guide here on how to restrict access in this way. We can re-enable "Block All Public Access" on the Bucket, and configure the CloudFront to Restrict Bucket Access.


Then, we configure a Bucket Policy to only allow access via our CloudFront Distribution's Origin Access Identity (instead of everyone).


Exhausted? Sort of? Well, just wait until you see the finale to this debacle.

We're now going to create ourselves a website that users can authenticate themselves with, and access our sensor readings in real-time. And since we want a solution that allows us to manage user identities, and provide them with access to AWS services, we're going to turn our attention to AWS Cognito.

Just one more Cognito, give it to meeeeee



We soon learn that there are two distinct parts to Cognito: the User Pool, which stores actual user objects and policies related to them, and Federated Identities, which we will use to provide these users with access to AWS services (in our case, the simple ability to query our thing's Shadow document in AWS IoT).

Let's then start with the User Pool.

We create a User Pool called this-iaq-thing-pool, mostly with what are default parameters. For example, we require users to store their email addresses and names in the database, and it has a password complexity setting of minimum 8 characters. There are countless more options here, but nothing that screams out at us as being essential to securing our investment from Deborah.


Notice how we also create an App Client - this-iaq-thing-app - and link this to this User Pool.


Here is what our this-iaq-thing-app looks like when we are creating it.


Once our User Pool is created, we can retrieve its Pool ID and Pool ARN. If the below looks like the Russian flag badly designed in CSS, it is because we have blanked out our very own IDs but have colour coded them to show where we are using them later.


Additionally, we can obtain the App Client ID as well.


These values turn out to be quite important, as they are later used in the AWS JavaScript SDK to identity our User Pool and authenticate our users.

It's time to create our IoT aficionado, the superfan - i_love_iaq_things.


There. Done.

At this point, unless we already have a login portal up and running (or use a default "Hosted UI" that Cognito can provide for you), this user cannot reset his / her own password. Let's do this manually for now, using the AWS CLI.

aws cognito-idp admin-set-user-password --user-pool-id=eu-west-1_secret --username=i_love_iaq_things --permanent --password=wouldnt_tell_ya



This looks to have worked... as the Account Status now states "Confirmed".


Besides the usual user attributes, such as name and email, we can also store additional attributes for a user. In our case, we want to store our AWS IoT Endpoint for the application, as this is later required by the JavaScript SDK to query our thing's Shadow document stored in AWS IoT.


We're storing our AWS IoT Endpoint here purely to avoid storing it elsewhere, for example, in our static JavaScript files as a string, visible to everyone. Instead, this custom attribute can be retrieved by each user after they authenticate themselves.

We'll use the AWS CLI to add our particular AWS IoT Endpoint to our user.

aws cognito-idp admin-update-user-attributes --user-pool-id=secret --username=i_love_iaq_things --user-attributes Name="custom:awsiotendpoint",Value="our-secret-ats.iot.eu-west-1.amazonaws.com"


We can now make our custom attribute available to our App Client as a read only value.


Next, we dive into the Federated Identities section of Cognito.

We create a new Identity Pool - this_iaq_thing_idp.


And here, we'll link it to the User Pool we configured earlier, and the App Client.


We need to determine what privileges an authenticated user will have, and what AWS services they will have access to.


We have simply added two IoT permission sets: iot:Connect and iot:GetThingShadow.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*",
        "cognito-identity:*",
        "iot:Connect",
        "iot:GetThingShadow"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Would we want to restrict this further to a specific resource in real life? We probably would. But we have the opportunity to assign some more permissions later to the individual users.

Oh, great. Another ID to remember - this time the Identity Pool ID.


When we navigate to the Identity Browser, we will notice that there are no identities in there yet. Where's our user?


Well, it transpires that the Cognito user has to authenticate first for it to be registered in the Identity Browser.  Again, Cognito's built-in "Hosted UI" could be an answer.

We're no experts in JavaScript, so we simply followed these handy resources to create our login page which uses the AWS JavaScript SDK to allow users to enter their username and password.


The SDK for Cognito requires a few parameters, which we recognise from our previous configuration.


Once our user has been able to login, they appear in the Identity Browser.


Lastly, an AWS IoT policy is linked to this Identity ID. This gives this particular user more granular permissions to perform certain actions against the AWS service, in our case, AWS IoT.

aws iot attach-principal-policy --policy-name cognito-this-iaq-thing --principal eu-west-1:secret_id


Interestingly, the certificate for this policy has automatically been created in AWS IoT, and can have a detailed policy attached to it. Once again, we only require it to have iot:Connect and iot:GetThingShadow privelages, that that's what the policy will get.



Here is the policy in AWS IoT. This user will only have the ability to connect to and query this specific device's Shadow document.



{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect"
      ],
      "Resource": [
        "arn:aws:iot:eu-west-1:blahblahblah:*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:GetThingShadow"
      ],
      "Resource": [
        "arn:aws:iot:eu-west-1:blahblahblah:thing/this-iaq-thing"
      ]
    }
  ]
}

With a bit of JavaScript wizardry, we can now authenticated ourselves, and log in using our custom login page.


...And query the data contained in the device's Shadow and present them back using a Bootstrap-based landing page.


The HTML / JavaScript code required isn't the subject of this post, but it revolves around the use of the AWS JavScript SDK, and specifically, interactions with the AmazonCognitoIdentity (for Cognito) and IotData (AWS IoT) modules. For example, providing that you have been able to successfully authenticate against Cognito and have a valid JWT Token, retrieving the Shadow document is as easy as using the iotdata.getThingShadow() method.

This is explained fully (and better!) in the following tutorials:


Here's what this all looks like end to end.


Everyone is clapping and smiling with excitement (we don't quite know why). There's confetti and fireworks, and Whitney has just hit the dramatic key change. We believe this all signals the climatic finale of the show, culminating in some random celebrity we've never heard of being crowned the winner of a contest that has been going on for almost half a decade (perhaps, even longer).

Thanks for showing up, but it's time to declare that you're all free to leave this particular boredroom... because you're fired!

Below is the MicroPython code running on the ESP32 development board:


Rantiques roadshow:

Here is the Bosch BME280 datasheet:
We are using this SGP30 air quality sensor breakout board from Pimoroni:
The official Sensirion SGP30 sensor datasheet can be found here:
This Adafruit document brilliantly describes how a SGP30 sensor works:
We use this BME280 MicroPython library:
We've forked the Adafruit SGP30 library so that it can run on native ESP32 MicroPython firmware without reliance on other Adafruit libraries. This is available here:
We followed these two AWS posts to get our AWS JavaScript SDK up and running.

Comments

MOST VISITED (APPARENTLY)

LoRa-Wan Kenobi

In the regurgitated words of Michael BublĂ©: It's a new dawn .  It's a new day .  It's a new Star Wars film .  For me .  And I'm (George Lucas, and I'm) feeling good .  Unfortunately for Canadian Mike, the Grammy that year was won by the novelty disco classic with the famous refrain: We love IoT, even in Planet Tatooine * . *Not true. Clearly, the Star Wars producers didn't sincerely mean the last Jedi the previous time around.  Return of the Jedi, released during the decade that spearheaded cultural renaissance 2.0 with the mullet and hair-metal , was less economic with the truth.  Either way, we're going to take inspiration from the impressive longevity of the money-spinning space-opera and reboot our franchise with some Jedi mind tricks.  Except this particular flick doesn't require an ever-growing cast of unrecognisable characters, unless ASCII or UTF counts.  In place of an ensemble gathering of Hollywood stars and starlets, we will b

Battle of BLEtain

The trolling . The doxing . An army of perplexing emojis. And endless links to the same - supposedly funny - viral video of a cat confusing a reflection from a dangling key for a golden hamster, while taking part in the mice bucket challenge. Has social media really been this immense force for good? Has it actually contributed significantly to the continued enlightenment of the human (or feline) race? In order to answer these poignant existential questions about the role of prominent platforms such as Critter, StinkedIn and Binterest, employing exceptional scientific rigour equal to that demonstrated by Theranos , we're going to set up a ground-breaking experiment using the Bluetooth Low Energy feature of MicroPython v1.12, and two ESP32 development boards with inexplicable hatred for one another.  And let them hurl quintessentially British expressions (others call them abuse) at each other like two Wiltshire residents who have had their internet access curbed by the co

Hard grapht

You would all be forgiven for assuming that bar , pie and queue line are favourite pastimes of the British .  Yet, in fact – yes, we did learn this back in GCSE maths – they are also mechanisms through which meaningless, mundane data of suspect origin can be given a Gok Wan -grade makeover, with the prime objective of padding out biblical 187-page PowerPoint presentations and 871-page Word reports (*other Microsoft productivity tools are available).  In other words, documents that nobody has the intention of ever reading.  But it becomes apparent over the years; this is perhaps the one skill which serves you well for a lifetime in certain careers.  In sales.  Consultancy.  Politics.  Or any other profession in which the only known entry requirement is the ability to chat loudly over a whizzy graph of dubious quality and value, preferably while frantically waving your arms around. Nevertheless, we are acutely conscious of the fact that we have spent an inordinate amount