Jun 202021
 

So, today on the radio I heard that from this Friday, our state government was “expanding” the use of their Check-in Queensland program. Now, since my last post on the topic, I have since procured a new tablet. The tablet was purchased for completely unrelated reasons, namely:

  1. to provide navigation assistance, current speed monitoring and positional logging whilst on the bicycle (basically, what my Garmin Rino-650 does)
  2. to act as a media player (basically what my little AGPTek R2 is doing — a device I’ve now outgrown)
  3. to provide a front-end for a SDR receiver I’m working on
  4. run Slack for monitoring operations at work

Since it’s a modern Android device, it happens to be able to run the COVID-19 check-in programs. So I have COVIDSafe and Check-in Queensland installed. For those to work though, I have to run my existing phone’s WiFi hotspot. A little cumbersome, but it works, and I get the best of both worlds: modern Android + my phone’s excellent cell tower reception capability.

The snag though comes when these programs need to access the Internet at times when using my phone is illegal. Queensland laws around mobile phone use changed a while back, long before COVID-19. The upshot was that, while people who hold “open” driver’s licenses may “use” a mobile phone (provided that they do not need to handle it to do so), anybody else may not “use” a phone for “any purpose”. So…

  • using it for talking to people? Banned. Even using “hands-free”? Yep, still banned.
  • using it for GPS navigation? Banned.
  • using it for playing music? Banned.

It’s a $1000 fine if you’re caught. I’m glad I don’t use a wheelchair: such mobility aids are classed as a “vehicle” under the Queensland traffic act, and you can be fined for “drink driving” if you operate one whilst drunk. So traffic laws that apply to “motor vehicles” also apply to non-“motor vehicles”.

I don’t have a driver’s license of any kind, and have no interest in getting one, my primary mode of private transport is by bicycle. I can’t see how I’d be granted permission to do something that someone on a learner’s permit or P1 provisional license is forbidden from doing. The fact that I’m not operating a “motor vehicle” does not save me, the drink-driving in a wheelchair example above tells me that I too, would be fined for riding my bicycle whilst drunk. Likely, the mobile phones apply to me too. Given this, I made the decision to not “use” a mobile phone on the bicycle “for any purpose”. “For any purpose” being anything that requires the device to be powered on.

If I’m going to be spending a few hours at the destination, and in a situation that may permit me to use the phone, I might carry it in the top-box turned off (not certain if this is permitted, but kinda hard to police), but if it’s a quick trip to the shops, I leave the mobile phone at home.

What’s this got to do with the Check-in Queensland application or my new shiny-shiny you ask? Glad you did.

The new tablet is a WiFi-only device… specifically because of the above restrictions on using a “mobile phone”. The day those restrictions get expanded to include the tablet, you can bet the tablet will be ditched when travelling as well. Thus, it receives its Internet connection via a WiFi access point. At home, that’s one of two Cisco APs that provide my home Internet service. No issue there.

If I’m travelling on foot, or as a passenger on someone else’s vehicle, I use the WiFi hot-spot function on my phone to provide this Internet service… but this obviously won’t work if I just ducked up the road on my bike to go get some grocery shopping done, as I leave the phone at home for legal reasons.

Now, the Check-in Queensland application does not work without an Internet connection, and bringing my own in this situation is legally problematic.

I can also think of situations where an Internet connection is likely to be problematic.

  • If your phone doesn’t have a reliable cell tower link, it won’t reliably connect to the Internet, Check-in Queensland will fail.
  • If your phone is on a pre-paid service and you run out of credit, your carrier will deny you an Internet service, Check-in Queensland will fail.
  • If your carrier has a nation-wide whoopsie (Telstra had one a couple of years back, Optus and Vodafone have had them too), you can find yourself with a very pretty but very useless brick in your hand. Check-in Queensland will fail.

What can be done about this?

  1. The venues could provide a WiFi service so people can log in to that, and be provided with limited Internet access to allow the check-in program to work whilst at the venue. I do not see this happening for most places.
  2. The Check-in Queensland application could simply record the QR code it saw, date/time, co-visitors, and simply store it on the device to be uploaded later when the device has a reliable Internet link.
  3. For those who have older phones (and can legally carry them), the requirement of an “application” seems completely unnecessary:
    1. Most devices made post-2010 can run a web browser capable of running an in-browser QR code scanner, and storage of the customer’s details can be achieved either through using window.localStorage or through RFC-6265 HTTP cookies. In the latter case, you’d store the details server-side, and generate an “opaque” token which would be stored on the device as a cookie. A dedicated program is not required to do the function that Check-in Queensland is performing.
    2. For older devices, pretty much anything that can access the 3G network can send and receive SMS messages. (Indeed, most 2G devices can… the only exception I know to this would be the Motorola MicroTAC 5200 which could receive but not send SMSes. The lack of a 2G network will stop you though.) Telephone carriers are required to capture and verify contact details when provisioning pre-paid and post-paid cellular services, so already have a record of “who” has been assigned which telephone number. So why not get people to text the 6-digit code that Check-In Queensland uses, to a dedicated telephone number? If there’s an outbreak, they simply contact the carrier (or the spooks in Canberra) to get the contact details.
  4. The Check-in Queensland application has a “business profile” which can be used for manual entry of a visitor’s details… hypothetically, why not turn this around? Scan a QR code that the visitor carries and provides. Such QR codes could be generated by the Check-in Queensland website, printed out on paper, then cut out to make a business-card sized code which visitors can simply carry in their wallets and present as needed. No mobile phone required! For the record, the Electoral Commission of Queensland has been doing this for our state and council elections for years.

It seems the Queensland Government is doing this fancy “app” thing “because we can”. Whilst I respect the need to effectively contact-trace, the truth is there’s no technical reason why “this” must be the implementation. We just seem to be playing a game of “follow the shepherd”. They keep trying to advertise how “smart” we are, why not prove it?

Jun 092021
 

So, I finally had enough with the Epson WF7510 we have which is getting on in years, occasionally miss-picks pages, won’t duplex, and has a rather curious staircase problem when printing. We’ll keep it for A3 scanning and printing (the fax feature is now useless), but for a daily driver, I decided to make an end-of-financial-year purchase. I wanted something that met this criteria:

  • A4 paper size
  • Automatic duplex printing
  • Networked
  • Laser/LED (for water-resistant prints)
  • Colour is a “nice to have”

I looked at the mono options, but when I looked at the driver options for Linux, things were looking dire with binary blobs everywhere. Removed the restriction on it being mono, and suddenly this option appeared that was cheaper, and more open. I didn’t need a scanner (the WF7510’s scanner works fine with xsane, plus I bought a separate Canon LiDE 300 which is pretty much plug-and-play with xsane), a built-in fax is useless since we can achieve the same using hylafax+t38modem (a TO-DO item well down in my list of priorities).

The Kyocera P5021cdn allegedly isn’t the cheapest to run, but it promised a fairly pain-free experience on Linux and Unix. I figured I’d give it a shot. These are some notes I used to set the thing up. I want to move it to a different part of the network ultimately, but we’ll see what the cretinous Windows laptop my father users will let us do, for now it shares that Ethernet VLAN with the WF7510 and his laptop, and I’ll just hop over the network to access it.

Getting the printer’s IP and MAC address

The menu on the printer does not tell you this information. There is however, a Printer Status menu item in the top-panel menu. Tell it to print the status page, you’ll get a nice colour page with lots of information about the printer including its IPv4 and IPv6 addresses.

Web interface

If you want to configure the thing further, you need a web browser. Visit the printer’s IP address in your browser and you’re greeted with Command Centre RX. Out of the box, the username and password were Admin and Admin (capitalised A).

Setting up CUPS

The printer “driver” off the Kyocera website is a massive 400MB zip file, because they bundled up .deb and .rpm packages for every distribution they officially support together in one file. Someone needs to introduce them to reprepro and its dnf-equivalent. That said, you have a choice… if you pick a random .deb out of that blob, and manually unpack it somewhere (use ar x on it, you’ll see data.tar.xz or something, unpack that and you’ve got your package files), you’ll find a .ppd file you’ll need.

Or, you can do a search and realise that the Arch Linux guys have done the hard work for you. Many thanks guys (and girls… et all)!

Next puzzle is figuring out the printer URI. Turns out the printer calls itself lp1… so the IPP URI you should use is http://<IP>:631/ipp/lp1.

I haven’t put the thing fully through its paces, and I note the cartridges are down about 4% from those two prints (the status page and the CUPS test print), but often the initial cartridges are just “starter” cartridges and that the replacements often have a lot more toner in them. I guess time will tell on their longevity (and that of the imaging drum).

May 262021
 

So, recently I misplaced the headset adaptor I use for my aging ZTE T83… which is getting on nearly 6 years old now. I had the parts on hand to make a new adaptor, so whipped up a new one, but found the 3.5mm plug would not stay in the socket.

Evidently, this socket has reached the number of insert/remove cycles, and will no longer function reliably. I do like music on the go, and while I’m no fan of Bluetooth, it is a feature my phone supports. I’ve also been hacking an older Logtech headset I have so that I can re-purpose it for use at work, but so far, it’s been about 15 months since I did any real work in the office. Thanks to China’s little gift, I’ve been working at home.

At work, I was using the Logitech H800 which did both USB and Bluetooth. Handy… but one downside it had is it didn’t do both, you selected the mode via a slider switch on the back of one of the ear cups. The other downside is that being an “open ear” design, it tended to leak sound, so my colleagues got treated to the sound track of my daily work.

My father now uses that headset since he needed one for video conferencing (again, thank-you China) and it was the best-condition headset I had on hand. I switched to using a now rather tatty-looking G930 before later getting a ATH-G1WL which is doing the task at home nicely. The ATH-G1WL is better all-round for a wireless USB headset, but it’s a one-trick pony: it just does USB audio. It does it well, better than anything else I’ve used, but that’s all it does. Great for home, where I may want better fidelity and for applications that don’t like asymmetric sample rates, but useless with my now Bluetooth-only phone.

I had a look around, and found the Zone Wireless headset… I wondered how it stacked up against the H800. Double the cost, is it worth it?

Firstly, my environment: I’m mostly running Linux, but I will probably use this headset with Android a lot… maybe OpenBSD. The primary use case will be mobile devices, so my existing Android phone, and a Samsung Active3 8″ tablet I have coming. The fact this unit like the H800 does both Bluetooth and USB is an attractive feature. Another interesting advertised feature is that it can be simultaneously connected to both, unlike the H800 which is exclusively one or the other.

First impressions

So, it turned up today. In the box was a USB-C cable (probably the first real use I have for such a cable), a USB-A to USB-C adaptor (for all you young whipper-snappers with USB-C ports exclusively), the headset itself, the USB dongle, and a bag to stow everything in.

Interestingly, each of these has a set-up guide. Ohh, and at the time of writing, yes, there are 6 links titled “Setup Guide (PDF)”… the bottom-right one is the one for the headset (guess how I discovered that?). Amongst those is a set-up guide for the bag. (Who’d have thought some fabric bag with a draw-string closure needed a set-up guide?) I guess they’re aiming this at the Pointy Haired Boss.

Many functions are controlled using an “app” installed on a mobile device. I haven’t tried this as I suspect Android 4.1 is too old. Maybe I can look at that when the tablet turns up, as it should be recent enough. It’d be nice to duplicate this functionality on Linux, but ehh… enough of it works without.

Also unlike the H800… there’s nowhere on the headset to stash the dongle when not in use. This is a bit of a nuisance, but they do provide the little bag to stow it in. The assumption is I guess that it’ll permanently occupy a USB port, since the same dongle also talks to their range of keyboards and mice.

USB audio functionality

I had the Raspberry Pi 3 running as a DAB+ receiver, Triple M Classic Rock had The Beatles Seargeant Pepper’s Lonely Hearts Club Band album on… so I plugged the dongle in to see how they compared with my desktop speakers (plugged in to the “headphone” jack). Now this isn’t the best test for sound quality for two reasons: (1) this DAB+ station is broadcasting 64kbps HE-AAC, and (2) the “headphone” jack on the Pi is hardly known as high fidelity, but it gave me a rough idea.

Audio quality was definitely reasonable. No better or worse than the H800. I haven’t tried the microphone yet, but it looks as if it’s on par with the H800 as well. Like every other Logitech headset I’ve owned to date, it too forces asymmetric sample rates, if you’re looking at using JACK, consider something else:

stuartl@vk4msl-pi3:~ $ cat /proc/asound/Wireless/stream0
Logitech Zone Wireless at usb-3f980000.usb-1.4, full speed : USB Audio

Playback:
  Status: Running
    Interface = 2
    Altset = 1
    Packet Size = 192
    Momentary freq = 48000 Hz (0x30.0000)
  Interface 2
    Altset 1
    Format: S16_LE
    Channels: 2
    Endpoint: 3 OUT (NONE)
    Rates: 48000
    Bits: 16

Capture:
  Status: Stop
  Interface 1
    Altset 1
    Format: S16_LE
    Channels: 1
    Endpoint: 3 IN (NONE)
    Rates: 16000
    Bits: 16

The control buttons seem to work, and there’s even a HID device appearing under Linux, but testing with xev reveals no reaction when I press the up/down/MFB buttons.

Bluetooth functionality

With the dongle plugged in, I reached for my phone, turned on its Bluetooth radio, pressed the power button on the headset for a couple of seconds, then told my phone to go look for the device. It detected the headset and paired straight away. Fairly painless, as you’d expect, even given the ancient Android device it was paired with. (Bluetooth 5 headset… meet Bluetooth 3 host!)

I then tried pulling up some music… the headset immediately switched streams, I was now hearing Albert Hammond – Free Electric Band. Hit pause, and I was back to DAB+ on the Raspberry Pi.

Yes, it was connected to both the USB dongle and the phone, which was fantastic. One thing it does NOT do though, at least out-of-the-box, is “mix” the two streams. Great for telephone calls I suppose, but forget listening to something in the background via your computer whilst you converse with somebody on the phone.

The audio quality was good though. Some cheaper Bluetooth headsets often sound “watery” to my hearing (probably the audio CODEC), which is why I avoided them prior to buying the H800, the H800 was the first that sounded “normal”, and this carries that on.

I’m not sure what the microphone sounds like in this mode. I suspect with my old phone, it’ll drop back to the HSP profile, which has an 8kHz sample rate, no wideband audio. I’ll know more when the tablet turns up as it should be able to better put the headset through its paces.

Noise cancellation

One nice feature of this headset is that unlike the H800, this is a closed-ear design which does a reasonable amount of passive noise suppression. So likely will leak sound less than the H800. Press the ANC button, and an announcement reports that noise cancellation is now ON… and there’s a subtle further muffling of outside noise.

It won’t pass AS/NZS:1270, but it will at least mean I’m not turning the volume up quite so loud, which is better for my hearing anyway. Doing this is at the cost of about an hour’s battery life apparently.

Left/right channel swapping

Another nice feature, this is the first headset I’ve owned where you can put the microphone on either side you like. Put the headset on either way around, flip the microphone to where your mouth is: as you pass roughly the 15° point on the boom, the channels switch so left/right channels are the correct way around for the way you’re wearing it.

This isn’t a difficult thing to achieve, but I have no idea why more companies don’t do it. There seems to be this defacto standard of microphone/controls on the left, which is annoying, as I prefer it and controls on the right. Some headsets (Logitech wired USB) were right-hand side only, but this puts the choice in the user’s hands. This should be encouraged.

Verdict

Well, it’s pricey, but it gets the job done and has a few nice features to boot. I’ll be doing some more testing when more equipment turns up, but it seems to play nice with what I have now.

The ability to switch between USB and Bluetooth sources is welcome, it’d be nice to have some software mixing possible, but that’s not the end of the world, it’s an improvement nonetheless.

Audio quality seems decent enough, with playback sample rates able to match DVD audio sample rates (at 16-bits linear PCM). Microphone sample rate should be sufficient for wideband voice (but not ultra-wideband or fullband).

It’s nice to be able to put the microphone on the side of my choosing rather than having that dictated to me.

The audio cancellation is a nice feature, and one I expect to make more use of in an open-plan environment.

The asymmetric record/playback sample rates might be a bit of a nuisance if you use software that expects these to be symmetric.

Somewhere to stash the dongle on the headset would’ve been nicer than having a carry bag.

It’d be nice if there was some sort of protocol spec for the functions offered in the “app” so that those who cannot or choose not to run it, can still access those functions.

Apr 262021
 

Recently, I discovered that in spite of my attempts to ensure my Internet connection would remain reliable throughout adverse conditions, I discovered a simple power outage basically left the ohh-so-wonderful HFC NBN NTD blinking and boot-looping helplessly.

In the last major storm event, the PSTN land-line was the only way we got a phone service. Sadly I was not geared up to test whether ADSL worked at that time, but the PSTN did, which was good because Telstra’s mobile network didn’t!

Armed with this knowledge, I decided to protect myself. My choices for an Internet link here are 4G and NBN. That does not give me much hope in a major calamity, but you know, do the best you can. At least in simple black-outs, 4G should stay up. 4G exclusively is too expensive, especially for a connection comparable to the NBN link I have, so the next best thing is to set up a back-up link using 4G. Since local towers may be down-and-out, best hope I have is to put the 4G antennas up as high as I possibly can. I looked at possible options, and one locally-produced option I stumbled on is the Telco Electronics T1. This is an outdoor rated 4G router, powered using PoE. The PoE scheme i simple: 24V DC nominal voltage, with the blue pair (pins 4 & 5) carrying the positive leg, and the brown pair (7 & 8) carrying the negative.

Talking with the vendor, I discovered that while these things can run down to 12V, they don’t recommend it. I guess I²R losses are a big factor there: CAT5e isn’t known for its power carrying capability. My thinking since my system is all 12V, is to simply run a 12V cable using 15A-rated DC cable alongside the Ethernet cable up to my bedroom, then from there I can split off a few 12V feeds: one for my 8-port switch, one for my access point, and one going to the 4G router.

Since the router expects 24V, I’ll use a boost converter so that the “PoE” run is as short as practical. I found an inexpensive 24V boost converter which could tolerate input voltages as low as 3.3V and input currents up to 5A. Mount this into a little wall-plate box with a couple of RJ-45 jacks and a barrel jack for the DC input, and we’d have a quick and easy boost converter.

I won’t put the wiring diagram up because honestly, it’s pretty straightforward! I haven’t tried running an Ethernet signal through this, but I’m confident it’ll work just fine. It does however power the T1 beautifully… the T1 drawing about 150mA when running at 14.4V (which is what my bench supply was set to). Some things I should possibly add would be fuses on the input and output: 1A on the input, 500mA on the output. For now I’ll just wing it. I’ll probably put the fuse at the socket in my room. There’s plenty of room to add this to the enclosure as it is now.

The business end of the PoE injector
Wiring job inside the enclosure.

Apr 232021
 

So, about 10 years ago, I started out as a contractor with a local industrial automation company, helping them integrate energy meters into various energy management systems.

Back then, they had an in-house self-managed corporate email system built on Microsoft Small Business Server. It worked, mostly, but had the annoyance of being a pariah regarding Internet standards… begrudgingly speaking SMTP to the outside world and mangling RFC822 messaging left-right and centre any chance it got. Ohh, and if you didn’t use its sister product, Microsoft Outlook, you weren’t invited!

Thankfully, as a contractor, I was largely insulated from that horror of a mail system… I had my own, running postfix + dovecot. That worked. Flawlessly for my needs. Emails were stored in the Maildir format, so back-ups were easy, if I couldn’t find something over IMAP, a ssh into the server was all I needed to unleash grep on the mailstore. Prior to this, I’ve used various combinations of Sendmail, Qmail, qpsmtpd for MTA and uw-imapd, Binc IMAP and finally dovecot. I used SpamAssassin for mail filtering, configured the server with a variety of RBLs, and generally enjoyed a largely spam-free and easy life.

A year or two into this arrangement, my workplace’s server had a major meltdown… they apparently had hit some internal limit on the Microsoft server, and on receipt of a few messages, it just crashed. Restore from back-up, all good, then some more incoming emails, down she went. In a hurry for an alternative, they grabbed an old box, loaded it up with an Ubuntu server fork and configured Zarafa groupware which sat atop the postfix MTA.

It was chosen because it was feature-wise, similar, to the Microsoft option. Unfortunately, it was also architecturally similar, with the mailstore being stored in MySQL using a bizzare schema that tried to replicate how Microsoft Exchange stored emails… meaning any header that Zarafa didn’t understand, got stripped… and any character that didn’t fit in the mailstore’s LATIN1 table character set got replaced with ?. Yes Mr. ????????? we’ll be onto that support request right away! One thing that I will say in Zarafa’s defence though, is that they at least supported IMAP (even if their implementation was primitive, it mostly “worked”), and calendaring was accessible using CalDAV.

That was the server I inherited as mail server administrator. We kept it going like that for a couple of years, but over time, the growing pains became evident… we had to move… again. By this stage, we were using Thunderbird as our standard email client, the Lightning extension for calendaring. On the fateful weekend of the 13-14th February, 2016, after a few weeks of research and testing, we moved again; to a combination of postfix, dovecot and SoGO providing calendaring/webmail. Like the server I had at home, email was stored in Maildir mail stores, which meant back-ups were as simple as rsync, selective restoring of a mail folder was easy, we could do public folders. People could use any IMAP compatible mail client: Thunderbird, Outlook, mutt, Apple Mail… whatever floated their boat.

I was quite proactive about the spam/malware situation… there was an extensive blacklist I maintained on that server to keep repeat offenders out. If you used a server at OVH or DigitalOcean for example, your email was not welcome, connections to port 25/tcp were rejected. Anything that did get through brought to my attention, I would pass the email through Spamcop for analysis and reporting, and any repeat offenders got added to the blacklist. I’d have liked to improve on the malware scanning… there are virus scanners that will integrate into Postfix and I was willing to set something up, but obviously needed management to purchase something suitable to do that.

Calendaring worked too… about the only thing that was missing was free-busy information, which definitely has its value, but it was workable. Worst case in my opinion is maybe replace SoGO with something else, but for now, it worked.

Fast forward to March 29th this year. New company has bought up my humble abode… and the big wigs have selected… Microsoft! No consultation. No discussion. The first note I got regarding this was a company-wide email stating we’d be migrating over the Easter long week-end.

I emailed back, pointing out a few concerns. I was willing to give Microsoft a second chance. For my end as a end user, I really only care about one thing: that the server communicates with the software on my computer with agreed “standard” protocols. For email that is IMAP and SMTP. For calendaring that is CalDAV. I really don’t care how it’s implemented, so long as it implements it properly. They do their end of the bargain by speaking an agreed protocol correctly… I’ll do my end by selecting a standards-compliant email/calendar client. All good.

I was assured that yes, it would do this. Specifically, I was shown this page as evidence. Okay, I thought, lets see how it goes. Small Business Server was from 2003… surely Microsoft has learned something in 18 years. They’ve been a lot more open about things, adopting support for OpenDocument in Office, working with Novell on .NET, ditching Visual Source Safe and embracing git so much so they acquired Github… surely things have improved.

Tuesday, 6th April, we entered a new world. A world were public folders were gone. A world with no calendaring. I’m guessing the powers at be have decided I do not need to see public folders, after all, RFC2342 has been around since the 90s… and even has people from Microsoft working on it! It’s possible they’re still migrating them from the old server, but 3 weeks seems a stretch.

Fine, I can live without public folders for now. Gone are the days where I interacted with customers on a regular basis and thus needed to file correspondence. The only mail folder I had much to do with of late was a public folder called Junk Mail which I used to monitor for spam to report and train the spam filter with.

Calendaring, I’ll admit I don’t use much… but to date, I have no CalDAV URI to configure my client with. I did some digging this morning. Initial investigations suggest that Microsoft still lives in the past. Best they can offer is a “look-but-not-touch” export. Useless.

But wait, there’s a web client! Yeah great… let’s cram it all in a web browser. I have to deal with Slack and its ugly bloat because voice chat doesn’t work in anything else. Then there’s the thorny of web-based email and why I think that is a bad idea. No, just because a web client works for you, or a particular brand desktop client works for you, does not mean it will work for everybody.

The frustration from this end right now is that I’m trapped with nowhere to go. I’m locked in to supporting myself and Sam (I made a commitment to my dying grandmother that he’d be cared for) for another 10 years at least (who knows how long he’ll live for, he’s 7 now and Emma lived to nearly 18), so suicide isn’t an option right now, nor is simply quitting and living on the savings I have.

Most workplaces seem to be infected with this groupware-malware, so switching isn’t a viable option either. Office365 apparently has a REST API, so maybe that’s the next point of call: see if I can write a proxy to bolt-on such an interface.

Apr 112021
 

So, for the past 12 months we’ve basically had a whirlwind of different “solutions” to the problem of contact tracing. The common theme amongst them seems to be they’re all technical-based, and they all assume people carry a smartphone, registered with one of the two major app stores, and made in the last few years.

Quite simply, if you’re carrying an old 3G brick from 2010, you don’t exist to these “apps”. Our own federal government tried its hand in this space by taking OpenTrace (developed by the Singapore Government and released as GPLv3 open-source) and rebadging that (and re-licensing it!) as COVIDSafe.

This had very mild success to say the least, with contact tracers telling us that this fancy “app” wasn’t telling them anything new. So much focus has been put on signing into and out of venues.

To be honest, I’m fine with this until such time as we get this gift from China under control. The concept is not what irks me, it’s its implementation.

At first, it was done on paper. Good old fashioned pen and paper. Simple, nearly foolproof, didn’t crash, didn’t need credit, didn’t need recharging, didn’t need network coverage… except for two problems:

  1. people who can’t successfully operate a pen (Hmm, what went wrong, Education Queensland?)
  2. people who can’t take the process seriously (and an app solves this how?)

So they demanded that all venues use an electronic system. Fine, so we had a myriad of different electronic web-based systems, a little messy, but it worked, and for the most part, the venue’s system didn’t care what your phone was.

A couple, even could take check-in by SMS. Still rocking a Nokia 3210 from 1998? Assuming you’ve found a 2G cell tower in range, you can still check in. Anything that can do at least 3G will be fine.

An advantage of this solution is that they have your correct mobile phone number then and it’s a simple matter for Queensland Health to talk to Telstra/Optus/Vodaphone/whoever to get your name and address from that… as a bonus, the cell sites may even have logs of your device’s IMEI roaming, so there’s more for the contact tracing kitty.

I only struck one venue out of dozens, whose system would not talk to my phone. Basically some JavaScript library didn’t load, and so it fell in a heap.

Until yesterday.

The Queensland Government has decided to foist its latest effort on everybody, the “Check-in Queensland” app. It is available on Google Play Store and Apple App Store, and their QR codes are useless without it. I can’t speak about the Apple version of the software, but for the Android one, it requires Android 5.0 or above.

Got an old reliable clunker that you keep using because it pulls the weakest signals and has a stand-by time that can be measured in days? Too bad. For me, my Android 4.1 device is not welcome. There are people out there for whom, even that, is a modern device.

Why not buy a newer phone? Well, when I bought this particular phone, back in 2015… I was looking for 3 key features:

  1. Make and receive (voice) telephone calls
  2. Send and receive short text messages
  3. Provide a Internet link for my laptop via USB/WiFi

Anything else is a bonus. It has a passable camera. It can (and does) play music. There’s a functional web browser (Firefox). There’s a selection of software I can download (via F-Droid). It Does What I Need It To Do. The battery still lasts 2-3 days between charges on stand-by. I’ve seen it outperform nearly every contemporary device on the market in areas with weak mobile coverage, and I can connect an external antenna to boost that if needed.

About the only thing I could wish for is open-source firmware and a replaceable battery. (Well, it sort-of is replaceable. Just a lot of frigging around to get at it. I managed to replace a GPS battery, so this should be doable.)

So, given this new check-in requirement, what is someone like me to do? Whilst the Queensland Government is urging people to install their application, they recognise that there are those of us who cannot because we lack anything that will run it. So they ask that venues have a device on hand that can be used to check visitors in if this situation arises.

My little “hack” simply exploits this:

# This file is part of pylabels, a Python library to create PDFs for printing
# labels.
# Copyright (C) 2012, 2013, 2014 Blair Bonnett
#
# pylabels is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# pylabels is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# pylabels.  If not, see <http://www.gnu.org/licenses/>.

import argparse
import labels
import time
from reportlab.lib.units import mm
from reportlab.graphics import shapes
from reportlab.lib import colors
from reportlab.graphics.barcode import qr

rows = 4
cols = 2
# Specifications for Avery C32028 2×4 85×54mm
specs = labels.Specification(210, 297, cols, rows, 85, 54, corner_radius=0,
        left_margin=17, right_margin=17, top_margin=31, bottom_margin=32)

def draw_label(label, width, height, checkin_id):
    label.add(shapes.String(
        42.5*mm, 50*mm,
        'COVID-19 Check-in Card',
        fontName="Helvetica", fontSize=12, textAnchor='middle'
    ))
    label.add(shapes.String(
        42.5*mm, 46*mm,
        'The Queensland Government has chosen to make the',
        fontName="Helvetica", fontSize=8, textAnchor='middle'
    ))
    label.add(shapes.String(
        42.5*mm, 43*mm,
        'CheckIn QLD application incompatible with my device.',
        fontName="Helvetica", fontSize=8, textAnchor='middle'
    ))
    label.add(shapes.String(
        42.5*mm, 40*mm,
        'Please enter my contact details into your system',
        fontName="Helvetica", fontSize=8, textAnchor='middle'
    ))
    label.add(shapes.String(
        42.5*mm, 37*mm,
        'at your convenience.',
        fontName="Helvetica", fontSize=8, textAnchor='middle'
    ))

    label.add(shapes.String(
        5*mm, 32*mm,
        'Name: Joe Citizen',
        fontName="Helvetica", fontSize=12
    ))
    label.add(shapes.String(
        5*mm, 28*mm,
        'Phone: 0432 109 876',
        fontName="Helvetica", fontSize=12
    ))
    label.add(shapes.String(
        5*mm, 24*mm,
        'Email address:',
        fontName="Helvetica", fontSize=12
    ))
    label.add(shapes.String(
        84*mm, 20*mm,
        'myaddress+c%o@example.com' % checkin_id,
        fontName="Courier", fontSize=12, textAnchor='end'
    ))
    label.add(shapes.String(
        5*mm, 16*mm,
        'Home address:',
        fontName="Helvetica", fontSize=12
    ))
    label.add(shapes.String(
        15*mm, 12*mm,
        '12 SomeDusty Rd',
        fontName="Helvetica", fontSize=12
    ))
    label.add(shapes.String(
        15*mm, 8*mm,
        'BORING SUBURB, QLD, 4321',
        fontName="Helvetica", fontSize=12
    ))

    label.add(shapes.String(
        2, 2, 'Date: ',
        fontName="Helvetica", fontSize=10
    ))
    label.add(shapes.Rect(
        10*mm, 2, 12*mm, 4*mm,
        fillColor=colors.white, strokeColor=colors.gray
    ))
    label.add(shapes.String(
        22.5*mm, 2, '-', fontName="Helvetica", fontSize=10
    ))
    label.add(shapes.Rect(
        24*mm, 2, 6*mm, 4*mm,
        fillColor=colors.white, strokeColor=colors.gray
    ))
    label.add(shapes.String(
        30.5*mm, 2, '-', fontName="Helvetica", fontSize=10
    ))
    label.add(shapes.Rect(
        32*mm, 2, 6*mm, 4*mm,
        fillColor=colors.white, strokeColor=colors.gray
    ))
    label.add(shapes.String(
        40*mm, 2, 'Time: ',
        fontName="Helvetica", fontSize=10
    ))
    label.add(shapes.Rect(
        50*mm, 2, 6*mm, 4*mm,
        fillColor=colors.white, strokeColor=colors.gray
    ))
    label.add(shapes.String(
        56.5*mm, 2, ':', fontName="Helvetica", fontSize=10
    ))
    label.add(shapes.Rect(
        58*mm, 2, 6*mm, 4*mm,
        fillColor=colors.white, strokeColor=colors.gray
    ))

    label.add(shapes.String(
        10*mm, 5*mm, 'Year',
        fontName="Helvetica", fontSize=6, fillColor=colors.gray
    ))
    label.add(shapes.String(
        24*mm, 5*mm, 'Month',
        fontName="Helvetica", fontSize=6, fillColor=colors.gray
    ))
    label.add(shapes.String(
        32*mm, 5*mm, 'Day',
        fontName="Helvetica", fontSize=6, fillColor=colors.gray
    ))
    label.add(shapes.String(
        50*mm, 5*mm, 'Hour',
        fontName="Helvetica", fontSize=6, fillColor=colors.gray
    ))
    label.add(shapes.String(
        58*mm, 5*mm, 'Minute',
        fontName="Helvetica", fontSize=6, fillColor=colors.gray
    ))

    label.add(qr.QrCodeWidget(
            '%o' % checkin_id,
            barHeight=12*mm, barWidth=12*mm, barBorder=1,
            x=73*mm, y=0
    ))

# Grab the arguments
OCTAL_T = lambda x : int(x, 8)
parser = argparse.ArgumentParser()
parser.add_argument(
        '--base', type=OCTAL_T,
        default=(int(time.time() / 86400.0) << 8)
)
parser.add_argument('--offset', type=OCTAL_T, default=0)
parser.add_argument('pages', type=int, default=1)
args = parser.parse_args()

# Figure out cards per sheet (max of 256 cards per day)
cards = min(rows * cols * args.pages, 256)

# Figure out check-in IDs
start_id = args.base + args.offset
end_id = start_id + cards
print ('Generating cards from %o to %o' % (start_id, end_id))

# Create the sheet.
sheet = labels.Sheet(specs, draw_label, border=True)

sheet.add_labels(range(start_id, end_id))

# Save the file and we are done.
sheet.save('checkin-cards.pdf')
print("{0:d} cards(s) output on {1:d} page(s).".format(sheet.label_count, sheet.page_count))

That script (which may look familiar), generates up to 256 check-in cards. The check-in cards are business card sized and look like this:

That card has:

  1. the person’s full name
  2. a contact telephone number
  3. an email address with a unique sub-address component for verification purposes (compatible with services that use + for sub-addressing like Gmail)
  4. home address
  5. date and time of check-in (using ISO-8601 date format)
  6. a QR code containing a “check-in number” (which also appears in the email sub-address)

Each card has a unique check-in number (seen above in the email address and as the content of the QR code) which is derived from the number of days since 1st January 1970 and a 8-bit sequence number; so we can generate up to 256 cards a day. The number is just meant to be unique to the person generating them, two people using this script can, and likely will, generate cards with the same check-in ID.

I actually added the QR code after I printed off a batch (thought of the idea too late). Maybe the next batch will have the QR code. This can be used with a phone app of your choosing (e.g. maybe use BarcodeScanner to copy the check-in number to the clip-board then paste it into a spreadsheet, or make your own tool) to add other data. In my case, I’ll use a paper system:

The script that generates those is here:

# This file is part of pylabels, a Python library to create PDFs for printing
# labels.
# Copyright (C) 2012, 2013, 2014 Blair Bonnett
#
# pylabels is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# pylabels is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# pylabels.  If not, see <http://www.gnu.org/licenses/>.

import argparse
import labels
import time
from reportlab.lib.units import mm
from reportlab.graphics import shapes
from reportlab.lib import colors

rows = 4
cols = 2
# Specifications for Avery C32028 2×4 85×54mm
specs = labels.Specification(210, 297, cols, rows, 85, 54, corner_radius=0,
        left_margin=17, right_margin=17, top_margin=31, bottom_margin=32)

def draw_label(label, width, height, checkin_id):
    label.add(shapes.String(
        42.5*mm, 50*mm,
        'COVID-19 Check-in Log',
        fontName="Helvetica", fontSize=12, textAnchor='middle'
    ))

    label.add(shapes.Rect(
        1*mm, 3*mm, 20*mm, 45*mm,
        fillColor=colors.lightgrey,
        strokeColor=None
    ))
    label.add(shapes.Rect(
        41*mm, 3*mm, 28*mm, 45*mm,
        fillColor=colors.lightgrey,
        strokeColor=None
    ))

    for row in range(3, 49, 5):
        label.add(shapes.Line(1*mm, row*mm, 84*mm, row*mm, strokeWidth=0.5))
    for col in (1, 21, 41, 69, 84):
        label.add(shapes.Line(col*mm, 48*mm, col*mm, 3*mm, strokeWidth=0.5))

    label.add(shapes.String(
        2*mm, 44*mm,
        'In',
        fontName="Helvetica", fontSize=8
    ))

    label.add(shapes.String(
        22*mm, 44*mm,
        'Check-In #',
        fontName="Helvetica", fontSize=8
    ))

    label.add(shapes.String(
        42*mm, 44*mm,
        'Place',
        fontName="Helvetica", fontSize=8
    ))

    label.add(shapes.String(
        83*mm, 44*mm,
        'Out',
        fontName="Helvetica", fontSize=8, textAnchor='end'
    ))

# Grab the arguments
parser = argparse.ArgumentParser()
parser.add_argument('pages', type=int, default=1)
args = parser.parse_args()

cards = rows * cols * args.pages

# Create the sheet.
sheet = labels.Sheet(specs, draw_label, border=True)

sheet.add_labels(range(cards))

# Save the file and we are done.
sheet.save('checkin-log-cards.pdf')
print("{0:d} cards(s) output on {1:d} page(s).".format(sheet.label_count, sheet.page_count))

When I see one of these Check-in Queensland QR codes, I simply pull out the log card, a blank check-in card, and a pen. I write the check-in number from the blank card (visible in the email address) in my log with the date/time, place, and on the blank card, write the same date/time and hand that to the person collecting the details.

They can write that into their device at their leisure, and it saves time not having to spell it all out. As for me, I just have to remember to write the exit time. If Queensland Health come a ringing, I have a record of where I’ve been on hand… or if I receive an email, I can use the check-in number to validate that this is legitimate, or even tell if a venue has on-sold my personal details to an advertiser.

I guess it’d be nice if the Queensland Government could at least add a form to their fancy pages that their flashy QR codes send people to, so that those who do not have the application can still at least check-in without it, but that’d be too much to ask.

In the meantime, this at least meets them half-way, and hopefully does so which ensures minimal contact and increases efficiency.

Jan 282021
 

So, a new year, a new set of assignments… one being that I’ve been given the job to write a comms driver for a series of electricity meters. These meters happen to use encryption using the AES-256-CBC cipher, and I needed to talk to these from within a NodeJS environment.

No problem I thought, there’s a crypto module. I muddled my way through the documentation… wrote pack and unpack functions that process the packets for the meter… then began writing unit tests.

Testing the pack function went fine, but when I came to the unpack, I hit a strange issue. I was getting truncated data. WTF? I added some console.log statements to see what was going on…

write 8b6fbcd1964bc93b6dcd6be409ff0b6b15c11f0764b6c54b02f515b83cd1b164e620d753c349ac45bcf3eca31f93de4c - 48 bytes
- read cd25400dc12613687df9ce2f9f6161d23c2d1e0f000000000000000000000000
read done
read done
read cd25400dc12613687df9ce2f9f6161d23c2d1e0f000000000000000000000000 - 32 bytes

So this is using the stream interface of the Decipher object. Initially this is what I used since that was the first example I came to in the documentation. Did I miss an error? No, nothing there. Did I need to break up the input into smaller pieces instead of writing it in one gulp? No, that didn’t help.

The only thing that was async in my async function was the cryptographic steps… so I thought maybe reverting to update/final might help? No, although it did mean I could drop async/await so that wasn’t so bad.

Nothing made sense… then I had a look at what pack was doing… I just assumed ciphertext output would be the same size as the cleartext. Was it? No, it was giving me extra, that I was then truncating when inserting it into the packet. Where did this extra junk come from?

In the end, the problem was right here:

    if (decrypt) {
        const ciphertext = packet.fields.inner.raw;

        /* Next IV will be the last 128 bits */
        next_iv = Uint8Array.prototype.slice.call(
            ciphertext, ciphertext.length - 16, ciphertext.length
        );

        let cleartext = [];
        let decipher = crypto.createDecipheriv(
            cipher_algo || CIPHER_ALGO, key, iv
        );
        decipher.setAutoPadding(false); // ← HERE!!!

        cleartext.push(decipher.update(ciphertext));
        cleartext.push(decipher.final());

        packet.fields.inner.raw = Buffer.concat(cleartext);
    }

NodeJS by default, pads the data you give it. In my code, I am already padding the input, as I actually need to know how many bytes of the input are padding bytes so I can compute/validate CRC checksums and do things the way the meter does them.

Naturally, setAutoPadding needs to be called both sides. I had to update my test cases for pack, but now at least, I get out the same quantity of data I put in both sides with no surprises.

Dec 312020
 

So, this last 2 years, I’ve been trying to keep multiple projects on the go, then others come along and pile their own projects on top. It kinda makes a mess of one’s free time, including for things like keeping on top of where things have been put.

COVID-19 has not helped here, as it’s meant I’ve lugged a lot of gear that belongs to my workplace, or belongs at my workplace, home, to use there. This all needs tracking to ensure nothing is lost.

Years ago, I threw together a crude parts catalogue system. It was built on Django, django-mptt and PostgreSQL, and basically abused the admin part of Django to manage electronic parts storage.

I later re-purposed some of its code for an estate database for my late grandmother: I just wrote a front-end so that members of the family could be given login accounts, and “claim” certain items of the estate. In that sense, the concept was extremely powerful.

The overarching principle of how both these systems worked is that you had “items” stored within “locations”. Locations were in a tree-structure (hence django-mptt) where a location could contain further “locations”… e.g. a root-level location might be a bed room, within that might be a couple of wardrobes and draws, and there might be containers within those.

You could nest locations as deeply as you liked. In my parts database, I didn’t consider rooms, but I’d have labelled boxes like “IC Parts 1”, “IC Parts 2”, these were Plano StowAway 932 boxes… which work okay, although I’ve since discovered you don’t leave the inner boxes exposed to UV light: the plastic becomes brittle and falls apart.

The inner boxes themselves were labelled by their position within the outer box (row, column), and each “bin” inside the inner box was labelled by row and column.

IC tubes themselves were also labelled, so if I had several sitting in a box, I could identify them and their location. Some were small enough to fit inside these boxes, others were stored in large storage tubs (I have two).

If I wanted to know where I had put some LM311 op-amps, I might look up the database and it’d tell me that there were 3 of them in IC Box 1/Row 2/Row 3/Column 5. If luck was on my side, I’d go to that box, pull out the inner box, open it up and find what I was looking for plugged into some anti-static foam or stashed in a small IC tube.

The parts themselves were fairly basic, just a description, a link to a data sheet, and some other particulars. I’d then have a separate table that recorded how many of each part was present, and in which location.

So from the locations perspective, it did everything I wanted, but parametric search was out of the question.

The place here looks like a tip now, so I really do need to get on top of what I have, so much so I’m telling people no more projects until I get on top of what I have now.

Other solutions exist. OpenERP had a warehouse inventory module, and I suspect Odoo continues this, but it’s a bit of a beast to try and figure out and it seems customisation has been significantly curtailed from the OpenERP days.

PartKeepr (if you can tolerate deliberate bad spelling) is another option. It seems to have very good parametric search of parts, but one downside is that it has a flat view of locations. There’s a proposal to enhance this, but it’s been languishing for 4 years now.

VRT used to have a semi-active track-and-trace business built on a tracking software package called P-Trak. P-Trak had some nice ideas (including a surprisingly modern message-passing back-end, even if it was a proprietary one), but is overkill of my needs, and it’s a pain to try and deploy, even if I was licensed to do so.

That doesn’t mean though I can’t borrow some ideas from it. It integrated barcode scanners as part of the user interface, something these open-source part inventory packages seem to overlook. I don’t have a dedicated barcode scanner, but I do have a phone with a camera, and a webcam on my netbook. Libraries exist to do this from a web browser, such as this one for QR codes.

My big problem right now is the need to do a stock-take to see what I’ve still got, and what I’ve added since then, along with where it has gone. I’ve got a lot of “random boxes” now which are unlabelled, and just have random items thrown in due to lack-of-time. It’s likely those items won’t remain there either. I need some frictionless way to record where things are getting put. It doesn’t matter exactly where something gets put, just so long as I record that information for use later. If something is going to move to a new location, I want to be able to record that with as little fuss as possible.

So the thinking is this:

  • Print labels for all my storage locations with UUIDs stored as barcodes
  • Enter those storage locations into a database using the UUIDs allocated
  • Expand (or re-write) my parts catalogue database to handle these UUIDs:
    • adding new locations (e.g. when a consignment comes in)
    • recording movement of containers between parent locations
    • sub-dividing locations (e.g. recording the content of a consignment)
    • (partial and complete) merging locations (e.g. picking parts from stock into a project-specific container)

The first step on this journey is to catalogue the storage containers I have now. Some are already entered into the old system, so I’ve grabbed a snapshot of that and can pick through it. Others are new boxes that have arrived since, and had additional things thrown in.

I looked at ways I could label the boxes. Previously that was a spirit pen hand-writing a label, but this does not scale. If I’m to do things efficiently, then a barcode seems the logical way to go since it uses what I already have.

Something new comes in? Put a barcode on the box, scan it, enter it into the system as a new location, then mark where that box is being stored by scanning the location barcode where I’ll put the box. Later, I’ll grab the box, open it up, and I might repeat the process with any IC tubes or packets of parts inside, marking them as being present inside that box.

Need something? Look up where it is, then “check it out” into my work area… now, ideally when I’m finished, it should go back there, but if I’m in a hurry, I just throw it in a box, somewhere, then record that I put it there. Next time I need it, I can look up where it is. Logical order isn’t needed up front, and can come later.

So, step 1 is to label all the locations. Since I’m doing this before the database is fully worked-out, I want to avoid ID clashes, I’m using UUIDs to label all the locations. Initially I thought of QR codes, but then realised some of the “locations” are DIP IC storage tubes, which do not permit large square labels. I did some experiments with Code-128, but found it was near impossible to reliably encode a UUID that way, my phone had difficulty recognising the entire barcode.

I returned to the idea of QR-codes, and found that my phone will scan a 10mm×10mm QR code printed on a page. That’s about the right height for the side of an IC tube. We had some inkjet labels kicking around, small 38.1×21.2mm labels arranged in a 5×11 grid (Avery J8651/L7651 layout). Could I make a script that generated a page full of QR codes?

Turns out, pylabels will do this. It is built on reportlab which amongst other things, embeds a barcode generator that supports various symbologies including QR codes. @hugohadfield had contributed a pull request which demonstrated using this tool with QR codes. I just had to tweak this for my needs.

# This file is part of pylabels, a Python library to create PDFs for printing
# labels.
# Copyright (C) 2012, 2013, 2014 Blair Bonnett
#
# pylabels is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# pylabels is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# pylabels.  If not, see <http://www.gnu.org/licenses/>.

import uuid

import labels
from reportlab.graphics.barcode import qr
from reportlab.lib.units import mm

# Create an A4 portrait (210mm x 297mm) sheets with 5 columns and 13 rows of
# labels. Each label is 38.1mm x 21.2mm with a 2mm rounded corner. The margins
# are automatically calculated.
specs = labels.Specification(210, 297, 5, 13, 38.1, 21.2, corner_radius=2,
        left_margin=6.7, right_margin=3, top_margin=10.7, bottom_margin=10.7)

def draw_label(label, width, height, obj):
    size = 12 * mm
    label.add(qr.QrCodeWidget(
            str(uuid.uuid4()),
            barHeight=height, barWidth=size, barBorder=2))

# Create the sheet.
sheet = labels.Sheet(specs, draw_label, border=True)

sheet.add_labels(range(1, 66))

# Save the file and we are done.
sheet.save('basic.pdf')
print("{0:d} label(s) output on {1:d} page(s).".format(sheet.label_count, sheet.page_count))

The alignment is slightly off, but not severely. I’ll fine tune it later. I’m already through about 30 of those labels. It’s enough to get me started.

For the larger J8165 2×4 sheets, the following specs work. (I can see this being a database table!)

# Specifications for Avery J8165 2×4 99.1×67.7mm
specs = labels.Specification(210, 297, 2, 4, 99.1, 67.7, corner_radius=3,
        left_margin=5.5, right_margin=4.5, top_margin=13.5, bottom_margin=12.5)

Later when I get the database ready (standing up a new VM to host the database and writing the code) I can enter this information in and get back on top of my inventory once again.