YULBBS

It’s 2026 and I’m running a BBS* like it’s 1991.

To avoid burying the lede on this: ssh yulbbs.via.sc (telnet also works); or https://yulbbs.via.sc/ in your browser.

(I mentioned this on Mastodon a while back, so this might not be new to some of you, but I realized I never wrote about it here.)

I live very near to a large airport (CYUL, Dorval/Trudeau airport in Montreal). I’m certainly no airplane foamer, but living so close gives me a sort of aircraft osmosis. It’s not uncommon for me to hear a take-off or landing that “sounded weird” and look up the flight.

I’m also casually interested in self-sufficient communications (this year—especially—I’ve been learning more about local mesh networking and have become involved with the Meshcore buildout in Montreal (I will certainly post more about this at some point)), and the joy—for me at least—of getting data from the sky.

You’ve probably casually heard of ADS-B from viewing flight tracking data for commercial flights, through services like Flightradar24, FlightAware, or other similar offerings. The way these systems work (at a very high level) is that they ingest telemetry data and plot aircraft on a map. The actual data comes from ADS-B (and other systems in other regions), which the airplanes themselves broadcast. Anyone with an appropriate radio setup can receive these signals, and decode them into something that can be plotted on a map (in fact, most of the data that you see on the above-mentioned flight tracking services comes from a network of enthusiasts who share locally-received data over the Internet). A big part of what I find interesting about this, though, is that the data does’t come from a service—I don’t need to rely on my ISP. I get the data directly from the airplanes, via an antenna and (software-defined) radio.

With this data captured, I didn’t want to just plot it on a map—that part has been done, and frankly done better than I could spend the effort on doing myself. Instead, I built YULBBS: a retrocomputing project, inspired by my adolescent introduction to social computing.

YULBBS has 4 main functions:

  • a table of aircraft near-enough for my antenna to hear (as of this writing, I’ve received a broadcast from 688km away!)
  • a semi-interactive map of YUL and our nearby water features, with planes plotted, and inspired by mapscii.
  • heuristically determine take-offs and landings at YUL (ADS-B doesn’t give me “I’m landing right now” data, so I had to determine that an airplane is landing based on characteristics of how their position is progressing, which proved to be harder than it seems)
  • and providing alerts for all arrivals and departures, plus an additional channel for anomalies (like letting me know when there’s a (broadcasting) hot air balloon nearby, or when an aircraft declares an emergency situation—these reported emergencies, hijackings, radio failures, etc., seem to be overwhelmingly false positives, which is a different kind of concerning)

The app itself was made with Swift (yes, that Swift), and uses NIO via SwiftNIO SSH. It’s a cross-compiled statically-linked Linux-on-ARM app that runs happily on a Raspberry Pi 3 (other than the USB problems that are present in v3, but those have nothing to do with my app) in my office closet. This little node does all of the ADS-B decoding through a piece of software called dump1090, and I get some local-only API data through a mini stack from the FlightAware folks, called PiAware. The real IP address (and hostname) are through a nano sized node in AWS (ca-central-1 is nearby), and the SSH + Telnet connections are passed along through HAProxy and Tailscale.

For a while I had it broadcasting the transitions and alerts on a local Meshtastic channel, but I currently have that LoRa (mesh) radio board unplugged (due to the mentioned USB issues). I may hook it up to Meshcore sometime soon (remember the joy of self-sufficient communications). It does, however, send messages via the normal net to a couple Mastodon accounts: @YULAlerts for mostly-false-positive alerts like emergencies (but it was fun to see the Snowbirds (Canadian demonstration jets) show up), and @YULTraffic for all determined landings and take-offs (this is a noisy account; beware).

Some screen shots:

Table view of aircraft near CYUL map view yulbbs about page

It’s ssh yulbbs.via.sc (or telnet; there’s even a web-based client (that I didn’t make) at https://yulbbs.via.sc). Let me know if you tinker with this; I’d love to hear about it.

*: note it’s not really a BBS—it doesn’t have local messaging, for example—but it’s set up to work and look like one, and that’s a big part of the fun part of retrocomputing for me.

How We Got Here

I don’t like how we got here.

Yes, this is—unfortunately—yet another AI thoughtpiece. I’m sure you must be tired of reading AI thoughtpieces. I won’t be offended if you skip this one. Before you jump, though, you should know that the emdashes in this paragraph (and entire contents of this post) were written by me, Sean, a 100% human. I won’t let it take my beloved emdashes.

I’ve been doing technology stuff professionally for more than 25 years (~10 years longer if you count pre-professionally), and I’m usually in the loop on new tech and what to think of it. But at the start of this year, I had to readjust how I think about the tech du jour, LLMs (which—as I’m sure you are aware—are commonly marketed as “AI”). I am trying very hard in this piece to avoid giving the impression that I am a LLM/AI fanperson (I truly am not): I like to think about these kinds of things critically, and I pride myself in being pragmatic when it comes to technology.

What changed for me is that LLMs started to actually work (in some ways; certainly not all). They went from “truly impressive that you can look at the JSON output I’ve put into a comment, and auto-complete on the keys in that stucture” to “truly impressive that I can say now add Meshtastic support for this kind of message and it went out, found the protobuf definitions, and built the whole integration” (not without mistakes).

Today, I am optimistically cautious about how these things work, but I do genuinely find them useful if used the right way.

For me, those ways are primarily:

  • Follow a pattern I’ve already established through good architecture (I don’t need to be the one typing the keys, but I do need to be the one reviewing the output)
  • I have a good understanding of the principles of how something should work, but I don’t have the acute knowledge of how to implement it—especially if it’s not [much of] a transferrable skill (a good example of this is: write some 6809 assembler to run on this computer from 1986)
  • Asking: what are some solutions that I should read about, related to a stated problem—especially ones that I have not yet considered?

I have found these tools—and they’re just tools like anything else—to be genuinely useful for certain tasks.

But I still hate how we got here.

The LLMs can be a force-multiplier for me. They can help me get several hours of work done in one hour, if I am careful about how I ask it to do those several hours of work.

I think of this as “the excavator problem” in some ways: an excavator (the earth-moving machine) can make a single human’s effort multiply by a giant factor—maybe even 1000x. But if the operator doesn’t know what they’re doing, or if they are intentionally malicious about their work, an excavator is an extremely dangerous tool. Normal people should not rent a large excavator for the weekend to dig a fence post hole. They should definitely not do so to weed a garden. (Okay okay; I’m done with that metaphor for now.)

You know what is a terrible opinion though? The one where people say “I would never have AI do the thing I am good at. I would only use it to do the thing I don’t understand.” I hold strongly that the most responsible use of a LLM (if you can consider it responsible at all), is in a way that you can at least know if it’s breaking things.

I still see people suggesting that “LLMs don’t work”, and I get it. I also think they don’t work for lots of things, but they sure do work for other things. I think “LLMs don’t work” as a general statement is really naïve, though. They can work, for specific things.

Now, I’m not suggesting that everyone has to use them, or even should use them for many tasks. All I’m saying here is “they don’t work” is wrong. That “should” bit sure is interesting though, right…?

How we got here is disgusting.

I don’t like the hype. The too-few-publicly-traded-companies holding up the economy, pretending there’s a boom, when really we normal folks are feeling the recession squeeze (“well, actually, we’re not technically in a recession!”). The every-product-must-be-AI anti-human behaviour. The can’t-have-nice-things unless they have an AI sticker on them nature of products/services, right now. This feels like everyone who failed at cryptocurrencies pushed their (unlimited because of our failure to govern) money into the next shiny thing. It’s exhausting, and it’s literally ruined things that used to be extemely reliable. The economic bubble will pop, and it’s going to hurt us all—but we’ve let the market become so unhinged that we can’t even do the rational thing to exit the public stock markets before it fails, because nothing works the way it was intended there anymore. (We can exit the market, as individuals, but it might be more valuable to not do that—especially too early.)

I can’t abide the intellectual property theft. I do, however, find myself agreeing strongly with Cory Doctorow’s position of (paraphrasing) “it’s over-all beneficial to let systems ingest intellectual property to distill; if we didn’t we wouldn’t have web search”, but it sure is not okay to steal copies of everyone’s books to facilitate this ingesting.

I have a hard time figuring out the actual environmental impact of this stuff, and an equally hard time doing the mental gymnastics of “datacentres for streaming movies are okay, but datacentres for this new thing are not.” I do know, though, that the panicky growth is bad for us humans. Putting loud industrial facilities next to established residences is bad. Pushing up the prices of energy, electronics, and even water: an absolute failure of our legislative bodies. Putting datacentres in space? C’mon, that’s even dumber than the Hyperloop.

The race to be first is incredibly stupid, and it makes us lose focus on how to do things in a safe, responsible way. The incredibly stupid part is that first isn’t usually the actual winner—Google replaced Lycos and Altavista; they didn’t get there first—they got there better.

The whole thing is plagued with gross people doing the grossest things.

But you know what? The actual technology works. Well, it can work. And by can work I mean it can be useful for some specific tasks. No, it’s not going to be “Artifical General Intelligence” unless we keep moving those goalposts. No, it’s not going to replace all human interactions.

So, should we use it? It’s tough. I think and hope there will be a future where this is more commoditized. A future where the best way to use these tools isn’t to give money to the worst people. In the meantime, though, I find it hard to argue unambiguously for either side. Should we use the tools to help find security problems that people who are less principled will find using those same tools? Should we avoid learning how to safely use the excavator because it can be dangerous in the wrong hands?

These are very tough questions, but for me, I do think it’s actually valuable to use some of these tools in an as-responsibly-as-possible of a manner as I can, for some limited tasks. Maybe for you, the line is “not at all”, or maybe the line is even further than what I would consider responsible. I don’t know. I also care deeply about food, but I’m not a vegan, and I think there’s some overlap here. Where’s the line for streaming? For using Google sheets? For buying computers from companies that are doing things that are bad for us as humans? Is my (PH)EV somehow worse than not because it uses conflict minerals? Should we stop using credit cards? The entire banking system? Should we use computers at all (the foundations of computing are in Shockley’s work, who was—as far as I can tell—the absolute worst)? Should we stop using trains (North American railroads were built on the unacceptably dangerous labour of underpaid foreigners)? Should I move to a cabin in the woods somewhere and plant a garden and feed my family through that land? What if the land isn’t warm enough to actually be sustainable, but I also don’t currently casually go over our southern border… ?

Unfortunately, there are no easy answers for me, here, and it all feels a little hypocritical. But also, it feels pragmatic in the same ways, if I’m not going to go full hermit mode.

No matter where that line ends up for me, I still completely despise how we got here.

Orval in Dorval

Short note on here to point at another project I’ve just published:

Orval in Dorval 2024

empty bottles of 14 years and some strays from the tasting

I’ve been collecting a case of Orval (the Belgian Trappist beer) every year since 2010. In 2024, eight of us got together at my place to taste my 14 year vertical. (I have 16 years now.)

I made us all take notes. The linked site is the data output from that night, and it might be the biggest trove of Orval tasting information on the whole web. (There are some photos, too.)

Studioworks

Yesterday, we launched Studioworks, the platform that I (and Nick, and Chris, and Jessica) have been working on all year.

There’s a lot more to what Studioworks will be, but for right now, it’s primarily a platform that lets creative people send really nice invoices in the easiest way we could manage. Our early (“founding member”) beta users seem to love it, and I’m really proud of what we’ve built.

I intend to write more about some of the technology we’re using, but Nick already shared some of this; you should definitely read that if you haven’t.

We’re doing things in a different-from-normal way—to build a sustainable business and responsible product that we (and our customers) can feel good about. Personally, there seem to be fewer and fewer things that I’m willing to work on, with surveillance capitalism, rampant scams, privacy invasions, and all kinds of really gross stuff taking over our beloved web. But I am really excited to make this work—to prove that it can work.

If you or someone you know are in the market for a tool that helps you create beautiful invoices, and want to give it a go, you can try it for free with my link:

https://studioworks.app/join/sean

Vendor Security

A few weeks ago I had to have a conversation with a vendor about credentials. Despite some push back from our side, they insisted that their Bearer Token style authentication key for HTTP requests was safe from MitM + Replay attacks. The token was to be used from a user’s device (their phone). They claimed it must be safe because the API access that uses this token is protected by HTTPS/TLS/SSL. In short: yes it’s protected from external snooping, but it’s not safe unless you fully trust all of your users and their devices, which you should not. (aside: that talk was from 2015? that’s surprising).

This reminded me of both an inadequacy in how we generally speak with vendors, and also a Kafkaesque conference call…

It is not uncommon for vendors to send credentials by email. For a long time I pushed back on this, but the practice became seemingly so common (and the security concerns of this approach such a foreign idea to vendors) that I’ve mostly stopped bothering, unless it’s a credential that my team needs to care about. If you broadcast your key in a place others can see it, that’s on you. We might mention it, but because there’s not a great universal way to use an established secure method to communicate (this is the first problem: the inadequacy of secure communication with vendors), it’s usually so much of a hassle that I don’t feel as strongly about identifying this kind of misstep when it’s solely someone else’s problem if/when it leaks. That might make me complicit in the worsening-of-everything, but honestly, these days I’d spend so much of my/their time chasing this kind of thing down, that it seems less worth it than in the past, and I’ve got my own work to do.

There was one client, though, where this happened all the time with their vendors (and themselves). It happened so often that we had to speak up about it whenever someone delivered a long-lasting security credential in an insecure matter. It seemed like every week we had to send a “please invalidate this credential and send it via the secure method we established at the start of this relationship”—which, in most cases, was Keybase.

There’s really not a great way to get normals to send secure messages with the tools they already have.

Anyway, this one client, and this one conference call went something like this: we get about 30 people together on a giant “go/no-go” call. These are mostly the big client, but in this case we’re one of the vendors, and there are at least 3 other vendors on the call. When it gets to be our turn I say “we’re go on our stuff, but we’d like Vendor X to invalidate the key they sent Vendor Y by email earlier today and generate a new one; even if we consider this email secure (which we don’t), we don’t want to have this key and you sent it to us in the big email thread.”

Vendors on this project were used to us saying this kind of thing. They didn’t care. We were—in part—getting paid to care, so we brought it up. There was non-visible eyerolling, but we eventually all agreed that Vendor X would regenerate and send to Vendor Y.

Next thing we know, and still on the conference call, the representative from Vendor X says “ok, Dave, I regenerated the key. It’s a7b 38…” I break in as soon as I realize what’s going on and I say “STOP. PLEASE. The whole point of regenerating is that we should exercise Least Privilege and there are a lot of people on this call that don’t need this key—they should not have it.” More eye rolling, but Vendor X’s person says “ok, Dave; I’ll call you directly then.”

Slight pause in the big conference call and we hear Dave (from Vendor Y) say “Hello?” then we hear Vendor X say “Hey Dave. It’s Kathy. Okay, the key is a7b 38…” Sure enough, Kathy called from her mobile phone to Dave’s mobile phone, and neither of them muted themselves. We heard both sides of the conversation and everyone got the key yet again.

I think we made them regenerate a 3rd one, but this kind of complete lack of diligence is a main factor in me noping out of pestering vendors about regenerating credentials that are compromised by neglect, unless we have a specific mandate to do so (or if the credentials are ours to regenerate).

Matter and Privacy

As of October 1, 2024, I've stepped away from my role as VP of Technology at Matter. This means that I can no longer personally vouch for the privacy aspects of the app.

When I was still working at Faculty, we took on a new client that was not yet named Matter. We eventually transitioned from an agency-client relationship to a startup relationship, where I became the VP of Technology. This is what I’ve been doing for the past two-ish years.

Chris wrote some good background on founding Matter, so I won’t repeat all of those details, but I’ve been wanting to write a bit about the origin story from my perspective.

When we were trying to figure out how to turn some of the neuroscience, existing material, and lots of our CEO Axel’s ideas into a product, we started discussing the idea of building an app around the concept of allowing users to log memories that they could later recall to improve their happiness. As a natural skeptic, it took me a little while to come around to believing that this was even possible and wasn’t just hand-wavy wellness stuff. I’ve since been convinced that we have technology that—when employed correctly—can actually improve happiness by having our users recall positive experiences. And we have actual science (which I will link in the future) that proves that their brains will create/release neurotransmitters (“happiness chemicals” in the context of what we’re working on) in line with their original positive experience, making them feel happier. For real.

So, as a very bare concept, we landed on the idea of allowing users to store photos of these positive experiences, as well as logging ratings of the emotions they experienced so they could recall them later to stimulate these neurotransmitters.

At one point Axel asked me “what do you think of that?” I said “To be honest, I have to ask: why would I ever send you a private photo and a positive rating for a sexual desire emotion? I would never send something like that to another party like Facebook, so why would I do it for us?”

This led us down an interesting—and mostly unique—path to how we handle private user content, and how we model threats against this private data. We adopted user privacy as a core value, and how we think about this informs many other decisions we make with the app and the whole ecosystem handling our users’ data. This became so important to us that we knew it needed to be one of the foundational aspects of how we work and this decision informed the product, not the inverse. We knew it was not something we could bolt on later—that trying to add this once we’d already exposed user data (to even ourselves) would be error-prone at best, and impossible at worst.

Early on, we set out some core principles:

  • we need to build trust with our users so they can believe what we say when it comes to how we handle their data (advanced users can audit traffic over the network to help build this trust, if they desire)
  • we need to protect our users from mistakes we might make (we shouldn’t be able to suffer a data leak of our users’ data if we have a weak password or our code has a bug)
  • even if we are competent enough to prevent a leak from ever happening, and even if our users trust us to do what we say, we must be resilient to being strong-armed by a future controlling power (e.g. if someone we don’t trust buys us)

We also had some extremely unusual conversations related to parameters around how far we can go with this:

  • “should we build our own datacentre for this?” “no, probably not. We can use an existing host if we’re careful about what data we collect and how we collect it.” “but would our things be safer if we did?” “honestly, someone much larger than us will do a much better job with the physical layer… I don’t think we want to spend our funding on hollowing out a mountain and hiring a militia.”
  • “we can have the best intentions, but we can’t always rely on those intentions. If one of our users’ data became valuable to an evil nation state and they kidnapped my family, I’ll be honest, I’d probably have to hand over the data.”

Given these criteria and extremes, we decided that our best course of action is to just never have our users’ private data.

This means that when you rate something high “pride” in Matter, we can’t tell you’ve done that. We’ve intentionally set up our metrics system to refuse to collect this kind of personal data, and we (the people and systems at Matter) simply never get it (only the app running on your device gets this data). We don’t store the data on our servers (outside of analytics—and even then never the data we consider private like emotion ratings); it always stays on your device and within your device’s datastore. (Matter is an iPhone app, so we store data on your phone with Core Data, and in a private database that syncs within your iCloud account, but is set up in a way that even we can’t access it. The app code that runs within our developer credentials, on your device, can read and write this data, but it is never sent to us and we have no way of accessing it through Apple’s tooling. It’s truly private to you.)

We (again, the people and systems at Matter) do get the product of some of those inputs, but never in a way that we can reverse it. A very simple version of this is if we were to collect the product of an a multiplication operation with the value “600”, we don’t know if the inputs were “1 × 600”, “100 × 6”, “30 × 20”, “12 × 50”, etc. We don’t know what went into a Matter Score for a memory but we do know the score. We know the “600” but not the “8” or the “75”. We don’t even know how you described a memory or what’s in a photo you attached. All we know is that there is a memory, it has a score of 600, and it belongs to a numbered account.

Numbered account? Well, we also don’t know who—specifically—our users are, and this is maybe the most controversial of our decisions. We don’t have accounts; we don’t even currently have email addresses, outside of our mailing list. There is no forced association between our mailing list users and our app users. In the future, we may allow users to opt in to self-identifying, but even then we’ll continue to be careful about never collecting private data.

When users add memories to the app, they’ll usually add content such as images. We don’t want to (and we don’t) hold these, either—at least not in a way we can see them. We primarily store these images on your device, but because the size of this storage is limited, we do have a system for storing assets such as images that have been encrypted on-device, and the actual photo contents or the decryption keys are never sent to us. We store data for users, here, but to us it looks like random noise (the binary ciphertext), never like a photo of whatever it is you’re storing a photo of. I intend to write more about this in the future, since we expect to publish some open source tooling related to this.

So, we don’t have your data in a database that we can access in any way (again, beyond collecting metrics on user-driven events that we don’t consider private, so that we can know number of active users, performance in the system, etc.).

This poses a couple serious problems. The main problem is: if I lose my data, how can I recover it?

Well, the short answer here is: we can’t. We can’t identify you by email to reset your password. We don’t have your email address (associated with your data, at least), and you don’t have a password. Even if we did have those things, we don’t have your data so we can’t restore it.

Right now the app has backup/restore functionality and we expect users to use that to protect themselves from data loss. We’ve put a lot of thought into storing these backups for a user, but having that user identify themselves is a difficult problem. Storing that data on behalf of the user, in a way that we can’t get to it is also a problem. But a very interesting problem. I think we have good solutions to these problems that we expect to build into the app before we’re out of beta, and I also hope to post about this in the future.

There’s a bit more info in our Privacy Policy, which we’re bound by.

I’ve been working on lots of technology things at Matter, but overseeing our privacy implementation has been one of the most rewarding.

One time, almost a year into working on this stuff, Axel said to me “I love that you’re thinking about this stuff and coming up with these kinds of solutions” to which I barely held back a tear and replied “I’ve been thinking about this stuff very seriously for over a decade, and I love that you’re the first person who’s really let me implement it.”

Exists is the enemy of good

We’ve all heard the adage “perfect is the enemy of good.” I take this to mean: if you only accept perfect, you might miss a good-enough solution. I still try to strive for perfect, but have learned to accept that good might be sometimes enough.

However, I’ve been saying something to friends and colleagues—for probably a decade now—that is closely related: exists is the enemy of good.

This idea is pretty simple, in principle: sometimes we miss a good-enough solution because a not-quite-good-enough solution is already out there and in use.

When I came up with this, it was specifically about bad software that seems good enough. Usually, stakeholders are less willing to invest in a good solution when one already exists.

Who of us hasn’t hacked together a prototype that somehow became the code we released to production and has been doing a not-quite-good-enough job of serving clients since that fateful deadline? In my experience, these things often become absolute nightmares when it comes to maintenance (and security, and performance, and stability…), but they exist, so the urgency to replace them with a good version gets traded for the thousand papercuts of unexpected “well, I didn’t think this would happen, so it’s going to take me a few more hours than expected” or “oops; we didn’t expect this kind of vulnerability…

Or, we’ve cut corners: “Yeah, we’ve been meaning to fix that wildcarded security policy, but it’s working for now, so we haven’t made time to tighten things down.” It exists though, so making it actually good doesn’t get priority; it gets kicked down the road—sometimes forever.

This concept applies more broadly, too.

Imagine a community that is served by a single bridge. The bridge was built in 1958 and doesn’t have safe walkways, or even a reasonable path for bicycles. Despite this, pedestrians and cyclists attempt to use the bridge daily—dangerously. The bridge exists, though. Replacing it with a better bridge is something the local council talks about every year or two, but no one is willing to risk their position by trying to justify the expense. Even worse: replacing the bridge would leave the community without a convenient link to the next town (they’d have to go the long way around) for a period of time while the replacement is being deployed, so even if someone were to get it into the budget, the citizens wouldn’t stand for the downtime. Sure, everyone wants a better bridge—a good bridge—but the bridge that exists is the enemy of this. Now imagine how quickly (relatively, of course—this is local government after all) action would be taken if the bridge were to become unusable. A flash flood knocks out the bridge, and there’s an opportunity—no, a downright necessity—to prop up a good bridge, because the old one practically no longer exists.

My home automation has been a bit neglected for the past year or so. Fundamentally, I’m still firmly in the position I mentioned (my lightbulbs can’t join a botnet to DDoS you if they physically can’t connect to the Internet (without a gateway)), but I’d like to tidy up a few things; recategorize, and maybe even explore a different system. But it exists and it mostly works, so it’s hard to justify the time and hassle of naming/grouping rooms, or switching control platforms to something that is potentially more good.

When I’ve complained about Electron apps on my desktop, saying things like “This would be so much better if it used the native toolkit. It’s super annoying that the tab key doesn’t work right and this scrollbar is janky. It destroys my battery life,” I’ve sometimes been met with the response “well, the Electron app is better than no app right?” Is it, though? If this bad app experience didn’t exist, there might be some reason for them to build a good app. The motivation is squelched by “what we have is good enough” when usually it isn’t good enough for me. My oven has what I would consider a terrible app. I have little hope that Anova will replace it with something good, though, because they have an app that exists.

Now, I’m not saying we need to greenfield everything, and we should usually avoid full rewrites—or at least approach them with a healthy dose of caution—but I’ve been thinking about this exists situation a lot and it’s a real mess.

We need to be very careful about justifying bad experiences with “perfect is the enemy of good” when we should be striving harder for good itself. The popularity of this expression is responsible for a lot of garbage. Sometimes we just shouldn’t do the quick and easy thing—even if we’ve tricked ourselves into thinking it’s temporary. Exists is also the enemy of good.

A Secret Career Code

or: How, 25 years ago, I went viral and met King (Prince) Charles

Someone has been paying me to work on technology stuff for over 20 years, now. And there’s one weird trick that I’ve learned that I think it’s worth passing on.

This isn’t really a secret code (and the one weird trick bit is a joke, of course), but it was so non-obvious to me in my early career, and it’s so valuable to me in my mid career that I wanted to share it—and also tell the story about going viral before anyone would have ever called it going viral.

The revelation is at the very end of this post, so feel free to skip ahead if you don’t want to hear the stories of how I learned it, but I think they’re fun.

Also, this turned out longer than I expected…

1998

In 1998, I graduated from high school. That last year, especially, I spent a good deal of my time really refining my would-be professional interests. I’d been programming for years, but multimedia was the hot thing, and I had a genuine interest in audio and video.

Our school had a really interesting (and maybe unique) setup where the 3 anglophone high schools in my city all shared the technical programs space, which we school-bussed to and from between classes, as needed.

This meant that instead of each of the high schools having to stock their own specialties labs, we shared resources with the other 2 main schools, at a formerly-4th school. This Science and Technology Centre campus held well-equipped labs for all kinds of chronically-underfunded subjects:

  • electronics (I saw my first Tesla coil there)
  • home economics (like cooking, well-equipped kitchens)
  • automotive tech (a fully equipped garage with a lift where students could learn to work on cars and even fix their own)
  • control technologies (electropneumatics, actuators, PLCs, etc.)
  • traditional machine shop and CAD/CAM (with actual mini manufacturing/machining spaces)
  • wood shop (carpentry, but also a full shop with planers, jointers, lathes, cabinetry facilities, etc.)
  • a computer programming lab that was set up with actual compilers, not just typing and Microsoft Office classes
  • its own well-equipped theatre for drama, live music, and video presentations
  • likely many other spaces that I didn’t participate in or really even notice
  • and where I spent most of my time: the media lab

The 4th school has been turned back into a normal high school for years, now, so students there no longer have the same shared resources opportunities that we were so privileged—yet unthankful for the most part since we were kids—to participate in. It was called the MacNaughton Science and Technology Centre, for future web archaeologists searching for references (see my own archaeology woes below).

The media lab was a suite that contained a main classroom that was set up—in addition to regular classroom stuff—for viewing videos and teaching media literacy (we learned about Manufacturing Consent, and talked about 1984), but it also contained a small-but-equipped recording studio (I spent hundreds of hours in there recording friends, learning to mix, mic, bounce tracks, EQ…), and a video production suite that had an expensive-at-the-time near-broadcast quality camera (a Canon XL-1), and a few workstations for editing video, photos, digital painting, CD ROM production (hey, this was big at the time), and all of the related things.

Aside from the unbelievable opportunity that we had with all of this equipment, as high school students, the teacher who ran the lab—the only teacher we called by a first name, by his request—Andrew Campbell was one of those special teachers you look back on and recognize how much time and effort—how much care—they put into us. Weekends and evenings, he was often available to help us get set up, or at least unlock the doors (and secretly share his keys if he couldn’t be there for a Saturday recording session or to kick off a multi-hour video compile). I’m forever grateful for being a part of that program with Andrew back then.

Anyway, because of this experience and the time I was able to spend in the lab, I got pretty good at stringing videos together and producing them into something that was worth watching. There were a few of us from each of the 3 schools that had above-average abilities to run these System 7 SCSI-based Macs with Adobe Premiere.

In Canada, in the 90s—at least where I lived—it seemed like everyone (unless your family was exceptionally religious or you lived on a rural farm or—I guess—just didn’t have cable or access to a friend’s basement that had cable) watched MuchMusic. This was roughly the same as MTV in the ’States. Many of us just kind of ambiently turned it on if we weren’t actually watching something else on the TV—I fell asleep to the overnight VJs, many nights.

One recurring public service campaign, every year on MuchMusic, which was partly funded by the Canadian Government, was “Stop Racism!” which promoted March 21: the international day for the elimination of racial discrimination. If you grew up in Canada in the ’90s, you might remember the hand logo from this campaign.

Racism: Stop It hand logo

Each year, as part of this public service, they ran a video competition where they called on students from all over the country to make a short (90 second) video that would enter into a competition where a panel of judges would choose the best high-schooler-produced videos, and these videos would be cut down and aired every few hours on MuchMusic over the course of a month or so. The winners would also be honoured in a ceremony with musical guests and dignitaries.

A few days before the deadline to submit videos for this, a couple of my not-even-very-close friends from school asked me if we could put something together. I said sure. We made up a script (in hindsight, it was probably very cringey, with eggs spray-painted different colours, and the message was that they were all the same inside). We shot and edited the video, and submitted it barely on time (I think we had to postal-mail a VHS tape). We certainly did not expect to win.

As you may have guessed if you’re still reading: we did win. There were several groups of winners, but we were the only winners from our region. They flew us to Vancouver (this was a really big deal to me; I’d only ever been on one plane trip before at this point) to participate in an awards ceremony, hosted by Prince Charles, and several musical guests that we cared much more about, and we got to meet them all at the reception afterwards. I honestly don’t remember what we talked about, but we definitely had an audience with the not-yet-king. (I do remember chatting with Great Big Sea, Matthew Good, and Chantal Kreviazuk, though.)

Our video aired on Much every few hours for the next month or so. We weren’t famous, but if this kind of thing had happened 10 years later, and if it was driven by social networks, I’m pretty sure we’d have called it viral. This was as close to viral you could get (without already being a celebrity, or doing something horribly newsworthy) in 1998.

There’s not much online about these events. I kept poor records back then. (-; I did find someone’s portfolio site about the event, and a video from another group that was entered in 1998 but didn’t win. Here are some newspaper clippings and a print + scan from our school’s Geocities page (thanks to my mom for clipping these way back then). Last thing: I found my official recognition certificate.

Certificate of Recognition … presented to Sean Coates … for Your winning entry in the 1998 Stop Racism National Video Competition

I learned a lot from this experience, but I think the biggest lesson was: if you’re any good at all, just try because you might succeed. I didn’t yet know the second part.

2001-2005

The second part of the lesson was revealed to me in 2004 or 2005, but let’s step back a little bit in time.

There are a few main events that put me on the path to learning what I learned. The first was when I took a new job in mid-2001, and I met Kevin Yank (who was living in Montreal at the time, and was just finishing up working at the place that had hired me—we became friends for a while there, too, until he moved away and we lost touch other than an occasional encounter in the Fediverse these days). Kev had just published a book with SitePoint: Build Your Own Database Driven Website Using PHP & MySQL. I learned a lot of PHP & MySQL from that book (I was working with Coldfusion at the time), and I still have my copy.

My copy of the aforementioned book.

What really struck me, though, was that he was close to my age, and wanted something more from his career, so he wrote this book. I thought I wanted that—or at least something like that—for my own career.

A few months later, I signed up to volunteer with the PHP documentation team and I remember it being a really big deal to me when they gave me my sean@php.net email address.

In 2003 (I think), I attended the first Conférence PHP Québec where I met Rasmus and many other people who would become peers in the PHP community. This conference eventually became ConFoo.

In late 2003 I decided I wanted to write for php|architect Magazine. I had a topic I liked and really wanted to see if I could—and still wanted to—build on that idea Kevin had imprinted on me. I did not expect it to be accepted—it seemed so prestigious. But my idea and draft/outline were accepted, and I was told I needed to write 4000 words, which—for a 23 year old non-academic-person—was a LOT of words (even this far-too-long blog post is “only” around 2000 words). But I did it. I was ecstatic to have my piece published in the January 2004 issue.

It was either later that year or in 2005 that I ran into the publisher of the magazine, Marco Tabini on IRC where we’d both been hanging out with PHP people for some time. He’d just lost his Editor-in-Chief, and was venting about having to pick up the editing duties in addition to his regular work. I—oh so naïvely—suggested that “I like editing” and he asked if I wanted to do it. After he reviewed an editing sample exercise he gave me, I started learning how to become the EiC and picked up the role pretty quickly.

So here’s where all of this has been going: when I started editing the magazine, I got to see our content pipeline. We had to run four 4000 word main articles per month, in addition to the columns, and what I saw blew my mind. I’d come into this believing that it was the cream of the crop that wrote for this trade magazine. It was really the best people who got published. That’s what I thought. I was so proud of my own accomplishment of writing for this prestigious magazine. And you know what? Some of the articles were excellent, but more often than not, I had to scrape together barely enough content to make each month’s issue great (and—admittedly—sometimes not great or even all that good). I had to rewrite whole articles. We had to beg past authors to write again. The illusion of prestige was completely revealed to me.

And this… this is the secret I’ve learned: if you’re good at something, you’re probably better than most everyone else who does that something. There’s always going to be the top tier, sure, and you might be in that top tier, or you might not, but the average is shockingly average. It’s really not that hard for you to accomplish many of these things if you set reasonable goals, and it turns out the bar for some of those goals is much lower than I expected early in my career.

Tl;DR: If you want to do something and think you can do it: just do it. If you’re any good at all, you’re probably better than most people who’ve done it, and far better than those who won’t even try.

Modified Microphone

I’ve owned a Blue Yeti microphone for a little over five years, now. It’s a pretty decent microphone for video calls, and I really like that it has its own audio interface for (pass-through) earphones, and a dedicated hardware mute feature. I’ve used it on probably 1500 calls, with only one complaint: the mute button makes noise.

For most of that five years, my colleagues could usually tell when I wanted into the conversation because the mute button has a satisfying ka-chunk tactile feedback that—unfortunately—transfers vibration into the thing to which it is attached, and that thing is a highly-sensitive vibration transducing machine… a microphone!

The ka-chunk noise has bothered me for years. Not so much for the signifier that I’m about to speak, but that I couldn’t unmute myself without people expecting me to speak. Plus, it’s kind of fundamentally annoying to me that a microphone has a button that makes noise.

So, I set out to fix this. I’ve been playing with these ESP32 microcontrollers from Espressif. These inexpensive chips (and development boards) are full of features and are way overpowered compared to the first-generation Arduinos I was playing with 15 years ago. They have WiFi and Bluetooth built in, come with enough RAM to do some basic stuff (and off-chip serial-bus-accessible RAM for more intensive work), and are easy to program (both from a software and hardware standpoint).

One of the cool built-in features of the ESP32 is a capacitive touch sensor. There are actually several of these sensors on board, and they’re often used to sense touch for emulating buttons… silent buttons. You can see where I’m going with this.

I laid out a test circuit on a breadboard, and wrote a little bit of firmware (including some rudimentary debouncing) using the Arduino framework, then tested:

(This is on a ESP32 WROOM development board that’s not ideal for some of my other projects, where I prefer the WROVER for the extra RAM, but is ideal—if not serious overkill—for this project.)

Not bad. But now I had to do the hard part: figure out how the microphone handles that button press. I looked around the Internet a little for someone who’d already done something similar, and I found some teardown videos, but couldn’t track down a schematic.

I took the microphone apart, dug into the Yeti’s board, and found the button. It was a bit more complicated than I’d imagined, mostly because the button is both illuminated (the built-in and light-piped LED will flash when muted, and will be lit solidly when unmuted), and mode-less (it’s a momentary button). With some creative probing with a volt-meter and some less-than-ideal hotwiring of the +5V/GND provided by the USB interface, I tracked the button press down to a single pin on the switch, which was sunk low when the button is pressed. I soldered on a wire to use with my project:

I also soldered on a way-too-big barrel connector to tap into the USB interface’s +5V and Ground lines. (Use what you have on-hand, right? Try not to get the connector genders backwards like I did… and also maybe solder better than me.)

My code would need to be modified to simulate this button “press”. In addition to the debouncing, I’d have to pretend to press and release the button, and also instead of providing +5 volts to an output pin (the normal way to signal something like this), I’d actually have to sink the 5V to ground. Here’s the (Arduino framework) code I ended up with (including some Serial debuggery):

#include <Arduino.h>

#define TOUCH_PIN 4
#define LED_PIN 2
#define EXT_LED_PIN 15

#define PULSE_DELAY 500

unsigned int threshold = 20;

// the last time the output pin was toggled:
unsigned long lastDebounceTime = 0;
// the debounce time
unsigned long debounceDelay = 500;

unsigned long pulseStartTime = 0;
bool toggledLow = false;

void gotTouch() {
  if (millis() < (lastDebounceTime + debounceDelay)) {
    return;
  }
  lastDebounceTime = millis();
  Serial.println("Touched.");

  // pulse the button
  digitalWrite(LED_PIN, LOW);
  digitalWrite(EXT_LED_PIN, LOW);
  Serial.println("(low)");
  pulseStartTime = millis();
  toggledLow = true;

}

void setup() {
  Serial.begin(9600);
  delay(100);
  Serial.println("Started.");
  pinMode(LED_PIN, OUTPUT);
  pinMode(EXT_LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);
  digitalWrite(EXT_LED_PIN, HIGH);
  touchAttachInterrupt(T0, gotTouch, threshold);
}

void loop() {
  // Touch0, T0, is on GPIO4
  Serial.println(touchRead(T0));  // get value using T0
  Serial.println("");
  delay(100);
  if (toggledLow && (millis() > (pulseStartTime + PULSE_DELAY))) {
    digitalWrite(LED_PIN, HIGH);
    digitalWrite(EXT_LED_PIN, HIGH);
    toggledLow = false;
    Serial.println("(high)");
  }
}

Please at least attempt to refrain from making fun of my weak C++ skills… but this seems to be surprisingly stable code in practice.

Now, I’d need to attach the ESP32 dev board, and reassemble the microphone part-way. The case of the Yeti is cast aluminum (or another softish metal, but I assume aluminum). This means that I could maybe—hopefully—use the case of the the Yeti itself as the touch sensor. I rigged up a sensor wire to loosely connect to the mounting hole (which gets a thumb-screwed bolt, and will, by force, make a good connection to the case), since it’s a huge pain (at best) to solder to aluminum:

Then, some bench testing before really putting it back together: it works! (You can see the blinking light in the middle of the Yeti’s board go solid and back to blinking when I touch the unassembled-but-connected case.)

Great! Success! I managed to do that mostly in a single evening! I put the microphone back together, including putting the case-mounting bolts back in and… suddenly it no longer worked. I disassembled, hooked up the serial monitor via USB, and… well… it works! Maybe I just pinched a connection or shorted a pin or something. More Kapton tape! Reassembled, and… failed again. So I ran a cable through the volume knob hole and reassembled, and tested it in-situ. Weird. The capacitance numbers are all very low. In fact, the might be always just (very near) 0 plus some occasional noise. What?

After a day or two of head-scratching, and then some measuring to confirm the hypothesis, I realized that when the bolts go into the case, the case gets connected to the chassis, and the chassis is grounded to the board, then through to the USB ground. So the case itself gets grounded. And that’s bad for a floating capacitance sensor. Turns out it didn’t work after all.

This led to experimentation with some insulating enamel paint for transformers, and me certainly burning through a few too many brain cells with the fumes from said paint. I gave up on isolating the case from ground (which is probably good, anyway, all things considered), and made a little touch pad out of some aluminum ducting tape, some solderable copper tape, and a chunk of cardboard, that I happened to have on hand (back to using what you have here).

Actual successful hack.

As you can see in the video, I also added a little toggle switch to the back of the microphone that could allow me to completely detach the switching line from the microphone, just in case my hack started to fail, and I was on an important call—the stock mute button still works, of course. But, I’m happy to report that it’s been nothing but stable for the past few weeks—it didn’t even overflow the millis() checks, somehow, which still surprises me—and I use the new ka-chunk-free touch-to-mute-unmute feature of my microphone every day.

A USB Mystery

TL;DR: got some hardware that didn’t have a driver I could use. Dived into some packet captures, learned some USB, wrote some code.

I’ve been on a mini quest to instrument a bunch of real-life things, lately.

One thing that’s been on my mind is “noise” around my house. So, after a very small amount of research, I bought a cheap USB Sound Pressure Level (“SPL”) meter. (I accidentally bought the nearly-same model that was USB-powered only (it had no actual USB connection), before returning it for this one, so be careful if you happen to find yourself on this path.) Why not use a regular microphone attached to a regular sound device? Calibration.

When the package arrived, and I connected it, I found that it was not the same model from my “research” above. I was hoping that many/most/all of these meters had the same chipset. So now I had a little bit of a mystery: how do I get data from this thing?

I managed to find it in my system devices (on Mac; I’d have used lsusb on Linux—this thing will eventually end up on a Raspberry Pi, though):

❯ system_profiler SPUSBDataType
(snip)
WL100:

  Product ID: 0x82cd
  Vendor ID: 0x10c4  (Silicon Laboratories, Inc.)
  Version: 0.00
  Speed: Up to 12 Mb/s
  Manufacturer: SLAB
  Location ID: 0x14400000 / 8
  Current Available (mA): 500
  Current Required (mA): 64
  Extra Operating Current (mA): 0
(snip)

So, I at least know it actually connects to the computer and identifies itself. But I really had no idea where to go from there. I found that Python has a PyUSB library, but even with that set up, my Mac was unhappy that I’d try accessing USB devices from userspace (non-sudo). I found there was also another way to connect to devices like this, over “HID”, which is the protocol normally used for things like the keyboard/mouse, but is over-all a simpler way to connect things.

The vendor supplied software on a mini-CD. Hilarious. There was also a very sketchy download link for Windows-only software. I have a Windows box in the networking closet for exactly this kind of thing (generally: testing of any sort). So, I went looking for some USB sniffing software, and a friend remembered that he thought Wireshark could capture USB. Perfect! I’d used Wireshark many times to debug networking problems, but never for USB. This was a lead nonetheless.

I fired up the vendor’s software and connected the SPL meter:

Okay. It’s ugly, but it seems to work. This app looks like it’s from the Win32 days, and I thought that was no longer supported… but it works—or at least seems to. I asked Wireshark to capture on USBPcap1, and waited until I saw it update a few times. Disconnected the capture, saved the pcap session file, and loaded it into Wireshark on my main workstation. Unfortunately, I didn’t have much of an idea what I was looking at.

I could, however, see what looked like the conversation between the computer (host), and the SPL meter (1.5.0). The was marked USBHID (as opposed to some other packets marked only USB), so this was a great clue:

The led to some searches around GET_REPORT and USB/HID/hidapi. Turns out that USB HID devices have “endpoints”, “reports”, and a lexicon of other terms I could only guess about. I didn’t plan to become a full USB engineer, and was hoping I could squeeze by with a bit of mostly-naïve-about-USB-itself-but-otherwise-experienced analysis.

Eventually, I figured out that I can probably get the data I want by asking for a “feature report”. Then I found get_feature_report in the Python hidapi bindings.

This function asks for a report_num and max_length:

def get_feature_report(self, int report_num, int max_length):
    """Receive feature report.

    :param report_num:
    :type report_num: int
    :param max_length:
    :type max_length: int
    :return: Incoming feature report
    :rtype: List[int]
    :raises ValueError: If connection is not opened.
    :raises IOError:
    """
    …

These two values sound familiar. From the Wireshark capture:

Now I was getting somewhere. Let’s use that decoded ReportID of 5 and a max_length (wLength) of 61.

import hid
import time

h = hid.device()
# these are from lsusb/system_profiler
h.open(0x10C4, 0x82CD)

while True:
    rpt = h.get_feature_report(5, 61)
    print(rpt)
    time.sleep(1)

This gave me something like:

[5, 97, 239, 60, 245, 0, 0, 1, 85, 0, 0, 1, 44, 5, 20, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[5, 97, 239, 60, 246, 0, 0, 1, 99, 0, 0, 1, 44, 5, 20, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[5, 97, 239, 60, 247, 0, 0, 1, 172, 0, 0, 1, 44, 5, 20, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[5, 97, 239, 60, 248, 0, 0, 3, 63, 0, 0, 1, 44, 5, 20, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[5, 97, 239, 60, 249, 0, 0, 2, 168, 0, 0, 1, 44, 5, 20, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[5, 97, 239, 60, 250, 0, 0, 1, 149, 0, 0, 1, 44, 5, 20, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[5, 97, 239, 60, 251, 0, 0, 1, 71, 0, 0, 1, 44, 5, 20, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

I played around with this data for a bit, and eventually noticed that the 8th and 9th (rpt[7:9]) values were changing. Sure enough, if I made a noise, the 9th value would change, and if it was a loud noise, the 8th value would also change:

1, 85
1, 99
1, 172
3, 63
2, 168

I was about to start throwing data into a spreadsheet when I made a guess: what if that’s a 16 (or 12…) bit number? So, if I shift the first byte over 8 bits and add the second byte…

(1 << 8) + 85 == 341
(1 << 8) + 99 == 355
(1 << 8) + 172 == 428
(3 << 8) + 64 == 831
(2 << 8) + 167 == 680 

The meter claims to have a range of 30dBA to 130dBA, and it sits around 35dBA when I’m not intentionally making any noise, in my office with the heat fan running. Now I’m worried that it’s not actually sharing dBA numbers and maybe they’re another unit or… wait… those ARE dBA numbers, just multiplied to avoid the decimal! 34.1, 35.5, 42.8, 83.1, 68.0

Got it!

Anyway, I wrote some (better) code to help read this data, on Python: scoates/wl100 on GitHub. Let me know if you use it!