Full Width [alt+shift+f] Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]

New here?

Welcome! BoredReading is a fresh way to read high quality articles (updated every hour). Our goal is to curate (with your help) Michelin star quality articles (stuff that's really worth reading). We currently have articles in 0 categories from architecture, history, design, technology, and more. Grab a cup of freshly brewed coffee and start reading. This is the best way to increase your attention span, grow as a person, and get a better understanding of the world (or atleast that's why we built it).

78
iOS 17.2 gained the capability to start Live Activities from a server, which is pretty cool and handy! I’ve been playing around with it a bit and found some parts a bit confusing, so I thought I’d do a little write up for future me as well as anyone else who could benefit! (For the uninitiated, Live Activities are the cool iOS feature that adds a live view of a specific activity to your Dynamic Island (if available) and Lock Screen, for example to see how your Uber Eats order is coming along.) Overview of starting a Live Activity server-side It’s pretty straightforward, just a few steps: Get token In your AppDelegate’s didFinishLaunching or somewhere very early in the lifecycle, start an async sequence to listen for a pushToStartToken so you can get the token: Task { for await pushToken in Activity<IceCreamWidgetAttributes>.pushToStartTokenUpdates { let pushTokenString = pushToken.reduce("") { $0 + String(format: "%02x", $1) } print("Our push token is:...
6 months ago

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Christian Selig

Curing Mac mini M4 fomo with 3D printing

Spoiler: 3D printed! The colored ports really sell the effect If you’re anything like me, you’ve found the new, tinier Mac mini to be absolutely adorable. But you might also be like me that you either already have an awesome M1 Mac mini that you have no real reason to replace, or the new Mac mini just isn’t something you totally need. While that logic might be sound, but it doesn’t make you want one any less. To help cure this FOMO, I made a cute little 3D printable Mac mini that can sit on your desk and be all cute. But then I had an even better idea, the new Mac mini is powerful sure, but it can’t hold snacks. Or a plant. Or your phone. Or pens/pencils. So I also made some versions you can print that add some cute utility to your desk in the form of the new Mac mini. They’re free of course! Just chuck ’em into your (or your friend’s) 3D printer. It even has all the little details modeled, like the power button, ports (including rear), and fan holes! They’re pretty easy to print, it’s in separate parts for ease of printing the bottom a different color (black) versus the top, then just put a dab of glue (or just use gravity) to keep them together. If you have a multi-color 3D printer, you can color the ports and power LED to make it look extra cool (or just do it after the fact with paint). Here are the different options for your desk! Secret item stash The possibilities for what you can store on your desk are now truly endless. Individually wrapped mints? Key switches? Screws? Paper clips? Rubber bands? Flash drives? Download link: https://makerworld.com/en/models/793456 A very green sorta Mac First carbon neutral Mac is cool and all but what if your Mac mini literally had a plant in it? Every desk needs a cute little plant. Download link: https://makerworld.com/en/models/793464 Phone holder A phone/tablet holder is an essential item on my desk for debugging things, watching a video, or just keeping an eye on an Uber Eats order. Before, guests came over and saw my boring phone stand and judged me, now they come over and think I’m exciting and well-traveled. You can even charge your phone/tablet in portrait mode by pushing the cable through a tunnel made through the Ethernet port that then snakes up to the surface. Download link: https://makerworld.com/en/models/793495 Pen holder The Playdate had the cutest little pen/pencil holder accessory but it unfortunately never shipped and my desk is sad. This will be a nice stand in for your beloved pens, pencils, markers, and Apple Pencils. Download link: https://makerworld.com/en/models/793470 A solid model Or if you just want to stare at it without any frills, you can just print the normal model too! Download link: https://makerworld.com/en/models/793447 Printer recommendation Whenever I post about 3D printing I understandably get a bunch of “Which 3D printer should I buy??” questions. This isn’t sponsored, but I’ve found over the last few years the answer has been pretty easy: something from Bambu Lab. Their printers are somehow super easy to use, well designed, and reasonably priced. Prusas are great too, but I think Bambu is hard to beat for the price. Don’t get an Ender. So if you’re looking for a printer now, Black Friday deals are aplenty so it’s pretty much the best time to pick one up. I’d grab something in their A series if you’re on a budget, or the P1S for a bit more if you can swing it (that’s what I use). https://bambulab.com On the other hand if you just want to print one thing now and again, a lot of local libraries are starting to have 3D printers so that might be worth looking into! And online services exist too (eg: JLCPCB and PCBWay), but if you do it with any regularity a 3D printer is a really fun thing to pick up. Enjoy! ❤️ Learning 3D modeling over the last year has been a ton of fun so I love a good excuse to practice, and shout out to Jerrod Hofferth and his amazing 3D printable Mac mini tower (that you should totally download) for the idea to solve my desire with some 3D printing! Also, the models are almost certainly not accurate down to the micrometer as I don’t actually have one, they’re based off Apple’s measurements as well as measuring screenshots. But it should be close! If you have a multi-color 3D printer, the linked models have the colors built-in for your ready to go, but if you want to print it in single-colors I also made versions available with the top and bottom separate as well as the logo, so you can print them separately in the individual colors then connect them with a touch of super glue or something.

4 months ago 51 votes
Introducing Tiny Storage: a small, lightweight UserDefaults replacement

Hey I'm a developer not an artist Following my last blog post about difficulties surrounding UserDefaults and edge cases that lead to data loss (give it a read if you haven’t, it’s an important precursor to this post!), I wanted to build something small and lightweight that would serve to fix the issues I was encountering with UserDefaults and thus TinyStorage was born! It’s open source so you can use it in your projects too if would like. GitHub link 🐙 Overview As mentioned in that blog post, UserDefaults has more and more issues as of late with returning nil data when the device is locked and iOS “prelaunches” your app, leaving me honestly sort of unable to trust what UserDefaults returns. Combined with an API that doesn’t really do a great job of surfacing whether it’s available, you can quite easily find yourself in a situation with difficult to track down bugs and data loss. This library seeks to address that fundamentally by not encrypting the backing file, allowing more reliable access to your saved data (if less secure, so don’t store sensitive data), with some niceties sprinkled on top. This means it’s great for preferences and collections of data like bird species the user likes, but not for sensitive details. Do not store passwords/keys/tokens/secrets/diary entries/grammy’s spaghetti recipe, anything that could be considered sensitive user information, as it’s not encrypted on the disk. But don’t use UserDefaults for sensitive details either as UserDefaults data is still fully decrypted when the device is locked so long as the user has unlocked the device once after reboot. Instead use Keychain for sensitive data. As with UserDefaults, TinyStorage is intended to be used with relatively small, non-sensitive values. Don’t store massive databases in TinyStorage as it’s not optimized for that, but it’s plenty fast for retrieving stored Codable types. As a point of reference I’d say keep it under 1 MB. This reliable storing of small, non-sensitive data (to me) is what UserDefaults was always intended to do well, so this library attempts to realize that vision. It’s pretty simple and just a few hundred lines, far from a marvel of filesystem engineering, but just a nice little utility hopefully! (Also to be clear, TinyStorage is not a wrapper for UserDefaults, it is a full replacement. It does not interface with the UserDefaults system in any way.) Features Reliable access: even on first reboot or in application prewarming states, TinyStorage will read and write data properly Read and write Swift Codable types easily with the API Similar to UserDefaults uses an in-memory cache on top of the disk store to increase performance Thread-safe through an internal DispatchQueue so you can safely read/write across threads without having to coordinate that yourself Supports storing backing file in shared app container Uses NSFileCoordinator for coordinating reading/writing to disk so can be used safely across multiple processes at the same time (main target and widget target, for instance) When using across multiple processes, will automatically detect changes to file on disk and update accordingly SwiftUI property wrapper for easy use in a SwiftUI hierarchy (Similar to @AppStorage) Uses OSLog for logging A function to migrate your UserDefaults instance to TinyStorage Limitations Unlike UserDefaults, TinyStorage does not support mixed collections, so if you have a bunch of strings, dates, and integers all in the same array in UserDefaults without boxing them in a shared type, TinyStorage won’t work. Same situation with dictionaries, you can use them fine with TinyStorage but the key and value must both be a Codable type, so you can’t use [String: Any] for instance where each string key could hold a different type of value. Installation Simply add a Swift Package Manager dependency for https://github.com/christianselig/TinyStorage.git Usage First, either initialize an instance of TinyStorage or create a singleton and choose where you want the file on disk to live. To keep with UserDefaults convention I normally create a singleton for the app container: extension TinyStorage { static let appGroup: TinyStorage = { let appGroupID = "group.com.christianselig.example" let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)! return .init(insideDirectory: containerURL) }() } (You can store it wherever you see fit though, in URL.documentsDirectory is also an idea for instance!) Then, decide how you want to reference your keys, similar to UserDefaults you can use raw strings, but I recommend a more strongly-typed approach, where you simply conform a type to TinyStorageKey and return a var rawValue: String and then you can use it as a key for your storage without worrying about typos. If you’re using something like an enum, making it a String enum gives you this for free, so no extra work! After that you can simply read/write values in and out of your TinyStorge instance: enum AppStorageKeys: String, TinyStorageKey { case likesIceCream case pet case hasBeatFirstLevel } // Read let pet: Pet? = TinyStorage.appGroup.retrieve(type: Pet.self, forKey: AppStorageKeys.pet) // Write TinyStorage.appGroup.store(true, forKey: AppStorageKeys.likesIceCream) (If you have some really weird type or don’t want to conform to Codable, just convert the type to Data through whichever means you prefer and store that, as Data itself is Codable.) If you want to use it in SwiftUI and have your view automatically respond to changes for an item in your storage, you can use the @TinyStorageItem property wrapper. Simply specify your storage, the key for the item you want to access, and specify a default value. @TinyStorageItem(key: AppStorageKey.pet, storage: .appGroup) var pet: = Pet(name: "Boots", species: .fish, hasLegs: false) var body: some View { Text(pet.name) } You can even use Bindings to automatically read/write. @TinyStorageItem(key: AppStorageKeys.message, storage: .appGroup) var message: String = "" var body: some View { VStack { Text("Stored Value: \(message)") TextField("Message", text: $message) } } It also addresses some of the annoyances of @AppStorage, such as not being able to store collections: @TinyStorageItem(key: "names", storage: .appGroup) var names: [String] = [] Or better support for optional values: @TinyStorageItem(key: "nickname", storage: .appGroup) var nickname: String? = nil // or "Cool Guy" Hope it’s handy! If you like it or have any feedback let me know! I’m going to start slowly integrating it into Pixel Pals and hopefully solve a few bugs in the process.

6 months ago 76 votes
Beware UserDefaults: a tale of hard to find bugs, and lost data

Excuse the alarmist title, but I think it’s justified, as it’s an issue that’s caused me a ton of pain in both support emails and actually tracking it down, so I want to make others aware of it so they don’t similarly burned. Brief intro For the uninitiated, UserDefaults (née NSUserDefaults) is the de facto iOS standard for persisting non-sensitive, non-massive data to “disk” (AKA offline). In other words, are you storing some user preferences, maybe your user’s favorite ice cream flavors? UserDefaults is great, and used extensively from virtually every iOS app to Apple sample code. Large amount of data, or sensitive data? Look elsewhere! This is as opposed to just storing it in memory where if the user restarts the app all the data is wiped out. It’s a really handy tool with a ton of nice, built-in things for you: No needing to mess with writing to files yourself, and better yet, no need to coordinate when to persist values back to the disk Easy to share data between your app’s main target and secondary targets (like a widget target) Automatic serialization and deserialization: just feed in a String, Date, Int, and UserDefaults handles turning it into bytes and back from bytes Thread-safe! So it’s no wonder it’s used extensively. But yeah, keep the two limitations in mind that Apple hammers home: Don’t store sensitive data in UserDefaults, that’s what Keychain is for Don’t store large amounts of data in UserDefaults, use something like Core Data or Swift Data Okay, so what’s the problem Turns out, sometimes you can request your saved data back from UserDefaults and it… just won’t have it! That’s a pretty big issue for a system that’s supposed to reliably store data for you. This can amount to an even bigger issue that leads to permanent data loss. Imagine a situation where a user has been meticulously opening your app for 364 days in a row. On day 365, your app promised a cool reward! When the user last closed the app, you stored 364 to UserDefaults. The user wakes up on day 365, excited for their reward: App launches App queries UserDefaults for how many days in a row the user has opened the app App returns 0 (UserDefaults is mysteriously unavailable so its API returns the default integer value of 0) It’s a new day, so you increment that value by 1, so that 0 changes to 1 Save that new value back to UserDefaults Now, instead of your user having a fun celebration, their data has been permanently overwritten and reset! They are having a Sad Day™. It basically means, if at any point you trust UserDefaults to accurately return your data (which you know, sounds like a fair assumption) you might just get incorrect data, which you then might make worse by overwriting good data with. And remember, you’re not meant to store sensitive data in UserDefaults, but even if it’s not sensitive data it might be valuable. The user’s day streak above is not sensitive data that would be bad if leaked online like a password, but it is valuable to that user. In fact I’d argue any data persisted to the disk is valuable, otherwise you wouldn’t be saving it. And you should be always be able to trust an API to reliably save your data. What??? How is this happening? 😵‍💫 As I understand it, there’s basically two systems coming together (and working incorrectly, if you ask me) to cause this: 1. Sensitive data encryption When using Keychain or files directly, as a developer you can mark data that should be encrypted until the device is unlocked by Face ID/Touch ID/passcode. This way if you’re storing a sensitive data like a token or password on the device, the contents are encrypted and thus unreadable until the device is unlocked. This meant if the device was still locked, and you, say, had a Lock Screen Widget that performed an API request, you would have to show placeholder data until the user unlocked the device, because the sensitive data, namely the user’s API token, was encrypted and unable to be used by the app to fetch and show data until the user unlocked the device. Not the end of the world, but something to keep in mind for secure data like API tokens, passwords, secrets, etc. 2. Application prewarming Starting with iOS 15, iOS will sometimes wake up your application early so that when a user launches it down the road it launches even quicker for them, as iOS was able to do some of the heavy lifting early. This is called prewarming. Thankfully per Apple, your application doesn’t fully launch, it’s just some processes required to get your app working: Prewarming executes an app’s launch sequence up until, but not including, when main() calls UIApplicationMain(::::). Okay, so what happened with these two? It seems at some point, even though UserDefaults is intended for non-sensitive information, it started getting marked as data that needs to be encrypted and cannot be accessed until the user unlocked their device. I don’t know if it’s because Apple found developers were storing sensitive data in there even when they shouldn’t be, but the result is even if you just store something innocuous like what color scheme the user has set for your app, that theme cannot be accessed until the device is unlocked. Again, who cares? Users have to unlock the device before launching my app, right? I thought so too! It turns out, even though Apple’s prewarming documentation states otherwise, developers have been reporting for years that that’s just wrong, and your app can effectively be fully launched at any time, including before the device is even unlocked. Combining this with the previous UserDefaults change, you’re left with the above situation where the app is launched with crucial data just completely unavailable because the device is still locked. UserDefaults also doesn’t make this clear at all, which it could do by for instance returning nil when trying to access UserDefaults.standard if it’s unavailable. Instead, it just looks like everything is as it should be, except none of your saved keys are available anymore, which can make your app think it’s in a “first launch after install” situation. The whole point of UserDefaults is that it’s supposed to reliably store simple, non-sensitive data so it can be accessed whenever. The fact that this has now changed drastically, and at the same time your app can be launched effectively whenever, makes for an incredibly confusing, dangerous, and hard to debug situation. And it’s getting worse with Live Activities If you use Live Activities at all, the cool new API that puts activities in your Dynamic Island and Lock Screen, it seems if your app has an active Live Activity and the user reboots their device, virtually 100% of the time the above situation will occur where your app is launched in the background without UserDefaults being available to it. That means the next time your user actually launches the app, if at any point during your app launching you trusted the contents of UserDefaults, your app is likely in an incorrect state with incorrect data. This bit me badly, and I’ve had users email me over time that they’ve experienced data loss, and it’s been incredibly tricky to pinpoint why. It turns out it’s simply because the app started up, assuming UserDefaults would return good data, and when it transparently didn’t, it would ultimately overwrite their good data with the returned bad data. I’ve talked to a few other developers about this, and they’ve also reported random instances of users being logged out or losing data, and after further experimenting been able to now pinpoint that this is what caused their bug. It happened in past apps to me as well (namely users getting signed out of Apollo due to a key being missing), and I could never figure out why, but this was assuredly it. If you’ve ever scratched your head at a support email over a user’s app being randomly reset, hopefully this helps! I don’t like this ☹️ I can’t overstate what a misstep I think this was. Security is always a balance with convenience. Face ID and Touch ID strike this perfectly; they’re both ostensibly less secure per Apple’s own admission than, say, a 20 digit long password, but users are much more likely to adopt biometric security so it’s a massive overall win. Changing UserDefaults in this way feels more on the side of “Your company’s sysadmin requiring you to change your password every week”: dubious security gains at the cost of user productivity and headaches. But enough moaning, let’s fix it. Solution 1 Because iOS is now seemingly encrypting UserDefaults, the easiest solution is to check UIApplication.isProtectedDataAvailable and if it returns false, subscribe to NotificationCenter for when protectedDataDidBecomeAvailableNotification is fired. This was previously really useful for knowing when Keychain or locked files were accessible once the device was unlocked, but it now seemingly applies to UserDefaults (despite not being mentioned anywhere in its documentation or UserDefault’s documentation 🙃). I don’t love this solution, because it effectively makes UserDefaults either an asynchronous API (“Is it available? No? Okay I’ll wait here until it is.”), or one where you can only trust its values sometimes, because unlike the Keychain API for instance, UserDefaults API itself does not expose any information about this when you try to access it when it’s in a locked state. Further, some developers have reported UserDefaults still being unavailable even once isProtectedDataAvailable returns true. Solution 2 For the mentioned reasons, I don’t really like/trust Solution 1. I want a version of UserDefaults that acts like what it says on the tin: simply, quickly, and reliably retrieve persisted, non-sensitive values. This is easy enough to whip up ourselves, we just want to keep in mind some of the things UserDefaults handles nicely for us, namely thread-safety, shared between targets, and an easy API where it serializes data without us having to worry about writing to disk. Let’s quickly show how we might approach some of this. UserDefaults is fundamentally just a plist file stored on disk that is read into memory, so let’s create our own file, and instead of marking it as requiring encryption like iOS weirdly does, we’ll say that’s not required: // Example thing to save let favoriteIceCream = "chocolate" // Save to your app's shared container directory so it can be accessed by other targets outside main let appGroupID = "" // Get the URL for the shared container guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID) else { fatalError("App Groups not set up correctly") } // Create the file URL within the shared container let fileURL = containerURL.appendingPathComponent("Defaults") do { let data = favoriteIceCream.data(using: .utf8) try data.write(to: fileURL) // No encryption please I'm just storing the name of my digital cow Mister Moo try FileManager.default.setAttributes([.protectionKey: .none], ofItemAtPath: fileURL.path) print("File saved successfully at \(fileURL)") } catch { print("Error saving file: \(error.localizedDescription)") } (Note that you could theoretically modify the system UserDefaults file in the same way, but Apple documentation recommends against touching the UserDefaults file directly.) Next let’s make it thread safe by using a DispatchQueue. private static let dispatchQueue = DispatchQueue(label: "DefaultsQueue") func retrieveFavoriteIceCream() -> String? { return dispatchQueue.sync { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "app-group-id") else { return nil } let fileURL = containerURL.appendingPathComponent(fileName) do { let data = try Data(contentsOf: fileURL) return String(data: data, encoding: .utf8) } catch { print("Error retrieving file: \(error.localizedDescription)") return nil } } } func save(favoriteIceCream: String) { return dispatchQueue.sync { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "app-group-id") else { return } let fileURL = containerURL.appendingPathComponent(fileName) do { let data = favoriteIceCream.data(using: .utf8) try data.write(to: fileURL) try FileManager.default.setAttributes([.protectionKey: .none], ofItemAtPath: fileURL.path) print("File saved successfully at \(fileURL)") } catch { print("Error saving file: \(error.localizedDescription)") } } } (You probably don’t need a concurrent queue for this, so I didn’t.) But with that we have to worry about data types, let’s just make it so long as the type conforms to Codable we can save or retrieve it: func saveCodable(_ codable: Codable, forKey key: String) { do { let data = try JSONEncoder().encode(codable) // Persist raw data bytes to a file like above } catch { print("Unable to encode \(codable): \(error)") } } func codable<T: Codable>(forKey key: String, as type: T.Type) -> T? { let data = // Fetch raw data from disk as done above do { return try JSONDecoder().decode(T.self, from: data) } catch { print("Error decoding \(T.self) for key \(key) with error: \(error)") return nil } } // Example usage: let newFavoriteIceCream = "strawberry" saveCodable(newFavoriteIceCream, forKey: "favorite-ice-cream") let savedFavoriteIceCream = codable(forKey: "favorite-ice-cream", as: String.self) Put those together, wrap it in a nice little library, and bam, you’ve got a UserDefaults replacement that acts as you would expect. In fact if you like the encryption option you can add it back pretty easily (don’t change the file protection attributes) and you could make it clear in the API when the data is inaccessible due to the device being locked, either by throwing an error, making your singleton nil, awaiting until the device is locked, etc. End Maybe this is super obvious to you, but I’ve talked to enough developers where it wasn’t, that I hope in writing this it can save you the many, many hours I spent trying to figure out why once in a blue moon a user would be logged out, or their app state would look like it reset, or worst of all: they lost data.

6 months ago 60 votes
Juno for YouTube has been removed from the App Store

For those not aware, a few months ago after reaching out to me, YouTube contacted the App Store stating that Juno does not adhere to YouTube guidelines and modifies the website in a way they don’t approve of, and alludes to their trademarks and iconography. I don’t personally agree with this, as Juno is just a web view, and acts as little more than a browser extension that modifies CSS to make the website and video player look more “visionOS” like. No logos are placed other than those already on the website, and the “for YouTube” suffix is permitted in their branding guidelines. I stated as much to YouTube, they wouldn’t really clarify or budge any, and as a result of both parties not being able to come to a conclusion I received an email a few minutes ago from Apple that Juno has been removed from the App Store. Juno was a fun hobby project for me to build. As a developer I wanted to get some experience building for the Vision Pro, and as a user I wanted a nice way to watch YouTube on this cool new device. As a result, I really enjoyed building Juno, but it was always something I saw as fundamentally a little app I built for fun. Because of that, I have zero desire to spin this into a massive fight akin to what happened with Reddit years ago. That’s kind of the opposite of fun. I hope that’s understandable. For those who have Juno, to my knowledge it should continue to work fine until/unless YouTube updates in some fashion that breaks stuff. Sorry it had to end this way, I had some really cool stuff planned for it that I think would have been a lot of fun! It’s been genuinely awesome hearing all the kind words from Vision Pro users who have loved the app. 🫡

6 months ago 60 votes

More in technology

Intel: Winning and Losing

Idling at a Zenith

7 hours ago 2 votes
MacLynx beta 6: back to the Power Mac

prior articles for more of the history, but MacLynx is a throwback port of the venerable Lynx 2.7.1 to the classic Mac OS last updated in 1997 which I picked up again in 2020. Rather than try to replicate its patches against a more current Lynx which may not even build, I've been improving its interface and Mac integration along with the browser core, incorporating later code and patching the old stuff. However, beta 6 is not a fat binary — the two builds are intentionally separate. One reason is so I can use a later CodeWarrior for better code that didn't have to support 68K, but the main one is to consider different code on Power Macs which may be expensive or infeasible on 68K Macs. The primary use case for this — which may occur as soon as the next beta — is adding a built-in vendored copy of Crypto Ancienne for onboard TLS without a proxy. On all but upper-tier 68040s, setting up the TLS connection takes longer than many servers will wait, but even the lowliest Performa 6100 with a barrel-bottom 60MHz 601 can do so reasonably quickly. The port did not go altogether smoothly. While Olivier Gutknecht's original fat binary worked fine on Power Macs, it took quite a while to get all the pieces reassembled on a later CodeWarrior with a later version of GUSI, the Mac POSIX glue layer which is a critical component (the Power Mac version uses 2.2.3, the 68K version uses 1.8.0). Some functions had changed and others were missing and had to be rewritten with later alternatives. One particularly obnoxious glitch was due to a conflict between the later GUSI's time.h and Apple Universal Interfaces' Time.h (remember, HFS+ is case-insensitive) which could not be solved by changing the search order in the project due to other conflicting headers. The simplest solution was to copy Time.h into the project and name it something else! Even after that, though, basic Mac GUI operations like popping open the URL dialogue would cause it to crash. Can you figure out why? Here's a hint: application: your application itself was almost certainly fully native. However, a certain amount of the Toolbox and the Mac OS retained 68K code, even in the days of Classic under Mac OS X, and your PowerPC application would invariably hit one of these routines eventually. The component responsible for switching between ISAs is the Mixed Mode Manager, which is tightly integrated with the 68K emulator and bridges the two architectures' different calling conventions, marshalling their parameters (PowerPC in registers, 68K on the stack) and managing return addresses. I'm serious when I say the normal state is to run 68K code: 68K code is necessarily the first-class citizen in Mac OS, even in PowerPC-only versions, because to run 68K apps seamlessly they must be able to call any 68K routine directly. All the traps that 68K apps use must also look like 68K code to them — and PowerPC apps often use those traps, too, because they're fundamental to the operating system. 68K apps can and do call code fragments in either ISA using the Code Fragment Manager (and PowerPC apps are obliged to), but the system must still be able to run non-CFM apps that are unaware of its existence. To jump to native execution thus requires an additional step. Say a 68K app running in emulation calls a function in the Toolbox which used to be 68K, but is now PowerPC. On a 68K MacOS, this is just 68K code. In later versions, this is replaced by a routine descriptor with a special trap meaningful only to the 68K emulator. This descriptor contains the destination calling convention and a pointer to the PowerPC function's transition vector, which has both the starting address of the code fragment and the initial value for the TOC environment register. The MMM converts the parameters to a PowerOpen ABI call according to the specified convention and moves the return address into the PowerPC link register, and upon conclusion converts the result back and unwinds the stack. The same basic idea works for 68K code calling a PowerPC routine. Unfortunately, we forgot to make a descriptor for this and other routines the Toolbox modal dialogue routine expected to call, so the nanokernel remains in 68K mode trying to execute them and makes a big mess. (It's really hard to debug it when this happens, too; the backtrace is usually totally thrashed.) the last time that my idea with MacLynx is to surround the text core with the Mac interface. Lynx keys should still work and it should still act like Lynx, but once you move to a GUI task you should stay in the GUI until that task is completed. In beta 5, I added support for the Standard File package so you get a requester instead of entering a filename, but once you do this you still need to manually select "Save to disk" inside Lynx. That changes in beta 6: :: which in MacOS is treated as the parent folder. Resizing, scrolling and repainting are also improved. The position of the thumb in MacLynx's scrollbar is now implemented using a more complex but yet more dynamic algorithm which should also more properly respond to resize events. A similar change fixes scroll wheels with USB Overdrive. When MacLynx's default window opens, a scrollbar control is artificially added to it. USB Overdrive implements its scrollwheel support by finding the current window's scrollbar, if any, and emulating clicks on its up and down (or left and right) buttons as the wheel is moved. This works fine in MacLynx, at least initially. When the window is resized, however, USB Overdrive seems to lose track of the scrollbar, which causes its scrollwheel functionality to stop working. The solution was to destroy and rebuild the scrollbar after the window takes its new dimensions, like what happens on start up when the window first opens. This little song and dance may also fix other scrollwheel extensions. Always keep in mind that the scrollbar is actually used as a means to send commands to Lynx to change its window on the document; it isn't scrolling, say, a pre-rendered GWorld. This causes the screen to be redrawn quite frequently, and big window sizes tend to chug. You can also outright crash the browser with large window widths: this is difficult to do on a 68K Mac with on-board video where the maximum screen size isn't that large, but on my 1920x1080 G4 I can do so reliably. lynx.cfg a no-op. However, if you are intentionally using another character set and this will break you, please feel free to plead your use case to me and I will consider it. Another bug fixed was an infinite loop that could trigger during UTF-8 conversion of certain text strings. These sorts of bugs are also a big pain to puzzle out because all you can do from CodeWarrior is force a trap with an NMI, leaving the debugger's view of the program counter likely near but probably not at the scene of the foul. Eventually I single-stepped from a point near the actual bug and was able to see what was happening, and it turned out to be a very stupid bug on my part, and that's all I'm going to say about that. SameSite and HttpOnly (irrelevant on Lynx but supported for completeness) attributes are, the next problem was that any cookie with an expiration value — which nowadays is nearly any login cookie — wouldn't stick. The problem turned out to be the difference in how the classic MacOS handles time values. In 32-bit Un*xy things, including Mac OS X, time_t is a signed 32-bit integer with an epoch starting on Thursday, January 1, 1970. In the classic MacOS, time_t is an unsigned 32-bit integer with an epoch starting on Friday, January 1, 1904. (This is also true for timestamps in HFS+ filesystems, even in Mac OS X and modern macOS, but not APFS.) Lynx has a utility function that can convert a ASCII date string into a seconds-past-the-epoch count, but in case you haven't guessed, this function defaults to the Unix epoch. In fact, the version previously in MacLynx only supports the Unix epoch. That means when converted into seconds after the epoch, the cookie expiration value would always appear to be in the past compared to the MacOS time value which, being based on a much earlier epoch, will always be much larger — and thus MacLynx would conclude the cookie was actually expired and politely clear it. I reimplemented this function based on the MacOS epoch, and now login cookies actually let you log in! Unfortunately other cookies like trackers can be set too, and this is why we can't have nice things. Sorry. At least they don't persist between runs of the browser. Even then, though, there's still some additional time fudging because time(NULL) on my Quadra 800 running 8.1 and time(NULL) on my G4 MDD running 9.2.2, despite their clocks being synchronized to the same NTP source down to the second, yielded substantially different values. Both of these calls should go to the operating system and use the standard Mac epoch, and not through GUSI, so GUSI can't be why. For the time being I use a second fudge factor if we get an outlandish result before giving up. I'm still trying to figure out why this is necessary. ogle). This didn't work for PNG images before because it was using the wrong internal MIME type, which is now fixed. (Ignore the MIME types in the debug window because that's actually a problem I noticed with my Internet Config settings, not MacLynx. Fortunately Picture Viewer will content-sniff, so it figures it out.) Finally, there is also miscellaneous better status code and redirect handling (again not a problem with mainline Lynx, just our older fork here), which makes login and browsing sites more streamlined, and you can finally press Shift-Tab to cycle backwards through forms and links. If you want to build MacLynx from source, building beta 6 is largely the same on 68K with the same compiler and prerequisites except that builds are now segregated to their own folders and you will need to put a copy of lynx.cfg in with them (the StuffIt source archive does not have aliases predone for you). For the PowerPC version, you'll need the same set up but substituting CodeWarrior Pro 7.1, and, like CWGUSI, GUSI 2.2.3 should be in the same folder or volume that contains the MacLynx source tree. There are debug and optimized builds for each architecture. Pre-built binaries and source are available from the main MacLynx page. MacLynx, like Lynx, is released under the GNU General Public License v2.

22 hours ago 2 votes
Humanities Crash Course Week 14: Roman Decadence

During week 14 of the humanities crash course, I explored foundational myths of Western Culture — written during the Roman Empire when decadence was setting in. I paired them with a classic film about social decadence set in mid-20th century Rome. Readings Gioa’s recommendations were a bit more open-ended this week: books 1 and 2 of the Aeneid, book 1 of Ovid’s Metamorphoses, and “selected Roman poems and aphorisms.” Rather than read a whole book, I sought out some of these latter shorter works online. I also read the most famous section of the Satyricon. The Aeneid is one of the classic epic poems of antiquity. Written by Virgil between 29 and 19 BCE, it aimed to formalize founding myths for the Roman Empire. It’s a sort of sequel to the Iliad and explicitly echoes the Odyssey’s structure. Fortunately, I only had read the first two books: Book 1: As Aeneas sails toward Italy after the events of the Trojan war; the gods conjure a storm that scatters the fleet. Aeneas and his crew are shipwrecked on the coast of Libya. The goddess Venus guides them to nearby Carthage, where Queen Dido is building a new city. She asks Aeneas to tell his story. Book 2: Aeneas recounts the fall of Troy, focusing on the Trojan horse episode. Despite warnings, the Trojans bring the gift horse into the city, only to be overwhelmed by the Greek soldiers hiding inside. King Priam is killed in the ensuing fight; Aeneas is about to kill himself in despair but the ghost of Hector urges him to flee instead. I said “fortunately” because these poems were the least pleasant reading I’ve done so far in the course. Perhaps it’s due to the translation I used, but it might also be because this work was created to glorify the Roman emperor. Like most ideological art, I found it pompous and tedious. In many ways, Metamorphoses was its opposite. Written later, (2-8 CE,) its focus is mythology drawn from the classic Greek pantheon, but parsed through Roman sensibilities. The theme is transformation: gods, demigods, and humans shapeshift, becoming plants, animals, constellations, etc. to change the trajectory of events or escape suffering Like the Aeneid, Metamorphoses also explores the foundations of Roman imperial rule, but it comes to the subject from a more ironic and irreverent POV. By focusing on change, the poem has interesting parallels with Buddhist teachings and the I Ching. Given its tone, cadence, and subject matter, I enjoyed it more than the Aeneid. The third major work this week was Petronius’s Satyricon, arguably the first Western novel. It was written during the reign of emperor Nero (mid 1st-century CE,) and only fragments survive. They present the misadventures of Encolpius and his companions Giton (a former slave and teenage lover) and Ascyltus, a friend, antagonist, and former lover. The novel portrays a morally chaotic and decadent society reveling in excess, deception, and sensuous pleasures. I read the most famous section, Trimalchio’s feast, which provides vivid descriptions of Roman excesses during this time. Characters come across as tone-deaf — especially the nouveau riche, whose vulgarity and self-importance must have seemed outrageous even to people with moral standards very different from ours. I was surprised at how richly these characters were portrayed — and how little people have changed in two millennia. Audiovisual Music: Arias by Puccini and Verdi. This is the second time during the course I’ve listened to opera: a medium I love but don’t include much in my regular rotation. This was a good opportunity to revisit some of these magnificent works. Art: Gioia recommended looking at cave paintings. I punted. Several years ago, I saw Werner Herzog’s CAVE OF FORGOTTEN DREAMS, and had my fill for cave paintings. Cinema: I read the Satyricon this week because I saw Fellini’s adaptation in college. At the time, I also saw his LA DOLCE VITA. Even though LDV is the more famous of the two, my memory of it was fuzzier, so I chose to revisit it now. Marcello Mastroianni plays Marcello, a womanizing gossip journalist drifting through postwar Rome. He has serious aspirations as a novelist, but allows himself to be swept along by the seductive currents of Roman nightlife and celebrity culture. Like the Satyricon, we get story fragments rather than a unified plot. The fragments loosely echo the seven deadly sins and seven sacraments of the Catholic Church, framing the film as a kind of spiritual inventory of modern life. I was surprised this classic film was only available to stream through Plex. It was a poor experience, riddled with ads and useless subtitles. My Italian is rusty, but I got most of the film anyway. (I may start seeing other Italian and French movies without subtitles as practice.) Reflections The common thread this week was social decadence — especially the kind that takes hold when a society grows too wealthy and powerful for its own good. I read the Aeneid as a puff piece intended to instill a false sense of pride through a constructed continuity with the Greek civilization Romans admired — and supplanted. Metamorphoses took a more lighthearted approach, suggesting all things pass and that change is the only constant. Both the Satyricon and LA DOLCE VITA illuminated the vulgarity and moral emptiness at the core of decadence, turning it into ironic — and sometimes painful — entertainment. Can we see our own time with such detachment? Can we recognize the Virgils trying to enshrine the inexcusable? The Ovids who myth-make more lightly and playfully? Or the Petroniuses and Fellinis who nudge us to laugh — and maybe cry — at the foolishness and pain of living adrift in a sensuous world? Notes on Note-taking Given my difficulties with the Aeneid, I found its Wikipedia page insightful. The summary of books 1 and 2 gave me an outline to better understand the work. As with previous weeks, I also bounced reflections off ChatGPT and summarized using the Obsidian Text Generator plugin. Both helped me grok the readings and how they relate to each other. A realization on process: writing these posts helps me focus and power through even when readings get tedious. If I hadn’t committed to sharing publicly, I likely would’ve bailed on the Aeneid. I read with more focus and attention when knowing I’ll publish these little “book reports” at the end of the week. Up Next From Gioia’s description, I expect next week’s reading — Boethius’s On the Consolation of Philosophy — will be highly relevant to our current predicament. Again, there’s a YouTube playlist for the videos I’m sharing here. I’m also sharing these posts via Substack if you’d like to subscribe and comment. See you next week!

20 hours ago 1 votes
2025-04-06 Airfone

We've talked before about carphones, and certainly one of the only ways to make phones even more interesting is to put them in modes of transportation. Installing telephones in cars made a lot of sense when radiotelephones were big and required a lot of power; and they faded away as cellphones became small enough to have a carphone even outside of your car. There is one mode of transportation where the personal cellphone is pretty useless, though: air travel. Most readers are probably well aware that the use of cellular networks while aboard an airliner is prohibited by FCC regulations. There are a lot of urban legends and popular misconceptions about this rule, and fully explaining it would probably require its own article. The short version is that it has to do with the way cellular devices are certified and cellular networks are planned. The technical problems are not impossible to overcome, but honestly, there hasn't been a lot of pressure to make changes. One line of argument that used to make an appearance in cellphones-on-airplanes discourse is the idea that airlines or the telecom industry supported the cellphone ban because it created a captive market for in-flight telephone services. Wait, in-flight telephone services? That theory has never had much to back it up, but with the benefit of hindsight we can soundly rule it out: not only has the rule persisted well past the decline and disappearance of in-flight telephones, in-flight telephones were never commercially successful to begin with. Let's start with John Goeken. A 1984 Washington Post article tells us that "Goeken is what is called, predictably enough, an 'idea man.'" Being the "idea person" must not have had quite the same connotations back then, it was a good time for Goeken. In the 1960s, conversations with customers at his two-way radio shop near Chicago gave him an idea for a repeater network to allow truckers to reach their company offices via CB radio. This was the first falling domino in a series that lead to the founding of MCI and the end of AT&T's long-distance monopoly. Goeken seems to have been the type who grew bored with success, and he left MCI to take on a series of new ventures. These included an emergency medicine messaging service, electrically illuminated high-viz clothing, and a system called the Mercury Network that built much of the inertia behind the surprisingly advanced computerization of florists [1]. "Goeken's ideas have a way of turning into dollars, millions of them," the Washington Post continued. That was certainly true of MCI, but every ideas guy had their misses. One of the impressive things about Goeken was his ability to execute with speed and determination, though, so even his failures left their mark. This was especially true of one of his ideas that, in the abstract, seemed so solid: what if there were payphones on commercial flights? Goeken's experience with MCI and two-way radios proved valuable, and starting in the mid-1970s he developed prototype air-ground radiotelephones. In its first iteration, "Airfone" consisted of a base unit installed on an aircraft bulkhead that accepted a credit card and released a cordless phone. When the phone was returned to the base station, the credit card was returned to the customer. This equipment was simple enough, but it would require an extensive ground network to connect callers to the telephone system. The infrastructure part of the scheme fell into place when long-distance communications giant Western Union signed on with Goeken Communications to launch a 50/50 joint venture under the name Airfone, Inc. Airfone was not the first to attempt air-ground telephony---AT&T had pursued the same concept in the 1970s, but abandoned it after resistance from the FCC (unconvinced the need was great enough to justify frequency allocations) and the airline industry (which had formed a pact, blessed by the government, that prohibited the installation of telephones on aircraft until such time as a mature technology was available to all airlines). Goeken's hard-headed attitude, exemplified in the six-year legal battle he fought against AT&T to create MCI, must have helped to defeat this resistance. Goeken brought technical advances, as well. By 1980, there actually was an air-ground radiotelephone service in general use. The "General Aviation Air-Ground Radiotelephone Service" allocated 12 channels (of duplex pairs) for radiotelephony from general aviation aircraft to the ground, and a company called Wulfsberg had found great success selling equipment for this service under the FliteFone name. Wulfsberg FliteFones were common equipment on business aircraft, where they let executives shout "buy" and "sell" from the air. Goeken referred to this service as evidence of the concept's appeal, but it was inherently limited by the 12 allocated channels. General Aviation Air-Ground Radiotelephone Service, which I will call AGRAS (this is confusing in a way I will discuss shortly), operated at about 450MHz. This UHF band is decidedly line-of-sight, but airplanes are very high up and thus can see a very long ways. The reception radius of an AGRAS transmission, used by the FCC for planning purposes, was 220 miles. This required assigning specific channels to specific cities, and there the limits became quite severe. Albuquerque had exactly one AGRAS channel available. New York City got three. Miami, a busy aviation area but no doubt benefiting from its relative geographical isolation, scored a record-setting four AGRAS channels. That meant AGRAS could only handle four simultaneous calls within a large region... if you were lucky enough for that to be the Miami region; otherwise capacity was even more limited. Back in the 1970s, AT&T had figured that in-flight telephones would be very popular. In a somewhat hand-wavy economic analysis, they figured that about a million people flew in the air on a given day, and about a third of them would want to make telephone calls. That's over 300,000 calls a day, clearly more than the limited AGRAS channels could handle... leading to the FCC's objection that a great deal of spectrum would have to be allocated to make in-flight telephony work. Goeken had a better idea: single-sideband. SSB is a radio modulation technique that allows a radio transmission to fit within a very narrow bandwidth (basically by suppressing half of the signal envelope), at the cost of a somewhat more fiddly tuning process for reception. SSB was mostly used down in the HF bands, where the low frequencies meant that bandwidth was acutely limited. Up in the UHF world, bandwidth seemed so plentiful that there was little need for careful modulation techniques... until Goeken found himself asking the FCC for 10 blocks of 29 channels each, a lavish request that wouldn't really fit anywhere in the popular UHF spectrum. The use of UHF SSB, pioneered by Airfone, allowed far more efficient use of the allocation. In 1983, the FCC held hearings on Airfone's request for an experimental license to operate their SSB air-ground radiotelephone system in two allocations (separate air-ground and ground-air ranges) around 850MHz and 895MHz. The total spectrum allocated was about 1.5MHz in each of the two directions. The FCC assented and issued the experimental license in 1984, and Airfone was in business. Airfone initially planned 52 ground stations for the system, although I'm not sure how many were ultimately built---certainly 37 were in progress in 1984, at a cost of about $50 million. By 1987, the network had reportedly grown to 68. Airfone launched on six national airlines (a true sign of how much airline consolidation has happened in recent decades---there were six national airlines?), typically with four cordless payphones on a 727 or similar aircraft. The airlines received a commission on the calling rates, and Airfone installed the equipment at their own expense. Still, it was expected to be profitable... Airfone projected that 20-30% of passengers would have calls to make. I wish I could share more detail on these ground stations, in part because I assume there was at least some reuse of existing Western Union facilities (WU operated a microwave network at the time and had even dabbled in cellular service in the 1980s). I can't find much info, though. The antennas for the 800MHz band would have been quite small, but the 1980s multiplexing and control equipment probably took a fare share of floorspace. Airfone was off to a strong start, at least in terms of installation base and press coverage. I can't say now how many users it actually had, but things looked good enough that in 1986 Western Union sold their share of the company to GTE. Within a couple of years, Goeken sold his share to GTE as well, reportedly as a result of disagreements with GTE's business strategy. Airfone's SSB innovation was actually quite significant. At the same time, in the 1980s, a competitor called Skytel was trying to get a similar idea off the ground with the existing AGRAS allocation. It doesn't seem to have gone anywhere, I don't think the FCC ever approved it. Despite an obvious concept, Airfone pretty much launched as a monopoly, operating under an experimental license that named them alone. Unsurprisingly there was some upset over this apparent show of favoritism by the FCC, including from AT&T, which vigorously opposed the experimental license. As it happened, the situation would be resolved by going the other way: in 1990, the FCC established the "commercial aviation air-ground service" which normalized the 800 MHz spectrum and made licenses available to other operators. That was six years after Airfone started their build-out, though, giving them a head start that severely limited competition. Still, AT&T was back. AT&T introduced a competing service called AirOne. AirOne was never as widely installed as Airfone but did score some customers including Southwest Airlines, which only briefly installed AirOne handsets on their fleet. "Only briefly" describes most aspects of AirOne, but we'll get to that in a moment. The suddenly competitive market probably gave GTE Airfone reason to innovate, and besides, a lot had changed in communications technology since Airfone was designed. One of Airfone's biggest limitations was its lack of true roaming: an Airfone call could only last as long as the aircraft was within range of the same ground station. Airfone called this "30 minutes," but you can imagine that people sometimes started their call near the end of this window, and the problem was reportedly much worse. Dropped calls were common, adding insult to the injury that Airfone was decidedly expensive. GTE moved towards digital technology and automation. 1991 saw the launch of Airfone GenStar, which used QAM digital modulation to achieve better call quality and tighter utilization within the existing bandwidth. Further, a new computerized network allowed calls to be handed off from one ground station to another. Capitalizing on the new capacity and reliability, the aircraft equipment was upgraded as well. The payphone like cordless stations were gone, replaced by handsets installed in seatbacks. First class cabins often got a dedicated handset for every seat, economy might have one handset on each side of a row. The new handsets offered RJ11 jacks, allowing the use of laptop modems while in-flight. Truly, it was the future. During the 1990s, satellites were added to the Airfone network as well, improving coverage generally and making telephone calls possible on overseas flights. Of course, the rise of satellite communications also sowed the seeds of Airfone's demise. A company called Aircell, which started out using the cellular network to connect calls to aircraft, rebranded to Gogo and pivoted to satellite-based telephone services. By the late '90s, they were taking market share from Airfone, a trend that would only continue. Besides, for all of its fanfare, Airfone was not exactly a smash hit. Rates were very high, $5 a minute in the late '90s, giving Airfone a reputation as a ripoff that must have cut a great deal into that "20-30% of fliers" they hoped to serve. With the rise of cellphones, many preferred to wait until the aircraft was on the ground to use their own cellphone at a much lower rate. GTE does not seem to have released much in the way of numbers for Airfone, but it probably wasn't making them rich. Goeken, returning to the industry, inadvertently proved this point. He aggressively lobbied the FCC to issue competitive licenses, and ultimately succeeded. His second company in the space, In-Flight Phone Inc., became one of the new competitors to his old company. In-Flight Phone did not last for long. Neither did AT&T AirOne. A 2005 FCC ruling paints a grim picture: Current 800 MHz Air-Ground Radiotelephone Service rules contemplate six competing licensees providing voice and low-speed data services. Six entities were originally licensed under these rules, which required all systems to conform to detailed technical specifications to enable shared use of the air-ground channels. Only three of the six licensees built systems and provided service, and two of those failed for business reasons. In 2002, AT&T pulled out, and Airfone was the only in-flight phone left. By then, GTE had become Verizon, and GTE Airfone was Verizon Airfone. Far from a third of passengers, the CEO of Airfone admitted in an interview that a typical flight only saw 2-3 phone calls. Considering the minimum five-figure capital investment in each aircraft, it's hard to imagine that Airfone was profitable---even at $5 minute. Airfone more or less faded into obscurity, but not without a detour into the press via the events of 9/11. Flight 93, which crashed in Pennsylvania, was equipped with Airfone and passengers made numerous calls. Many of the events on board this aircraft were reconstructed with the assistance of Airfone records, and Claircom (the name of the operator of AT&T AirOne, which never seems to have been well marketed) also produced records related to other aircraft involved in the attacks. Most notably, flight 93 passenger Todd Beamer had a series of lengthy calls with Airfone operator Lisa Jefferson, through which he relayed many of the events taking place on the plane in real time. During these calls, Beamer seems to have coordinated the effort by passengers to retake control of the plane. The significance of Airfone and Claircom records to 9/11 investigations is such that 9/11 conspiracy theories may be one of the most enduring legacies of Claircom especially. In an odd acknowledgment of their aggressive pricing, Airfone decided not to bill for any calls made on 9/11, and temporarily introduced steep discounts (to $0.99 a minute) in the weeks after. This rather meager show of generosity did little to reverse the company's fortunes, though, and it was already well into a backslide. In 2006, the FCC auctioned the majority of Airfone's spectrum to new users. The poor utilization of Airfone was a factor in the decision, as well as Airfone's relative lack of innovation compared to newer cellular and satellite systems. In fact, a large portion of the bandwidth was purchased by Gogo, who years later would use to to deliver in-flight WiFi. Another portion went to a subsidiary of JetBlue that provided in-flight television. Verizon announced the end of Airfone in 2006, pending an acquisition by JetBlue, and while the acquisition did complete JetBlue does not seem to have continued Airfone's passenger airline service. A few years later, Gogo bought out JetBlue's communications branch, making them the new monopoly in 800MHz air ground radiotelephony. Gogo only offered telephone service for general aviation aircraft; passenger aircraft telephones had gone the way of the carphone. It's interesting to contrast the fate of Airfone to to its sibling, AGRAS. Depending on who you ask, AGRAS refers to the radio service or to the Air Ground Radiotelephone Automated Service operated by Mid-America Computer Corporation. What an incredible set of names. This was a situation a bit like ARINC, the semi-private company that for some time held a monopoly on aviation radio services. MACC had a practical monopoly on general aviation telephone service throughout the US, by operating the billing system for calls. MACC still exists today as a vendor of telecom billing software and this always seems to have been their focus---while I'm not sure, I don't believe that MACC ever operated ground stations, instead distributing rate payments to private companies that operated a handful of ground stations each. Unfortunately the history of this service is quite obscure and I'm not sure how MACC came to operate the system, but I couldn't resist the urge to mention the Mid-America Computer Corporation. AGRAS probably didn't make anyone rich, but it seems to have been generally successful. Wulfsberg FliteFones operating on the AGRAS network gave way to Gogo's business aviation phone service, itself a direct descendent of Airfone technology. The former AGRAS allocation at 450MHz somehow came under the control of a company called AURA Network Systems, which for some years has used a temporary FCC waiver of AGRAS rules to operate data services. This year, the FCC began rulemaking to formally reallocate the 450MHz air ground allocation to data services for Advanced Air Mobility, a catch-all term for UAS and air taxi services that everyone expects to radically change the airspace system in coming years. New uses of the band will include command and control for long-range UAS, clearance and collision avoidance for air taxis, and ground and air-based "see and avoid" communications for UAS. This pattern, of issuing a temporary authority to one company and later performing rulemaking to allow other companies to enter, is not unusual for the FCC but does make an interesting recurring theme in aviation radio. It's typical for no real competition to occur, the incumbent provider having been given such a big advantage. When reading about these legacy services, it's always interesting to look at the licenses. ULS has only nine licenses on record for the original 800 MHz air ground service, all expired and originally issued to Airfone (under both GTE and Verizon names), Claircom (operating company for AT&T AirOne), and Skyway Aircraft---this one an oddity, a Florida-based company that seems to have planned to introduce in-flight WiFi but not gotten all the way there. Later rulemaking to open up the 800MHz allocation to more users created a technically separate radio service with two active licenses, both held by AC BidCo. This is an intriguing mystery until you discover that AC BidCo is obviously a front company for Gogo, something they make no effort to hide---the legalities of FCC bidding processes are such that it's very common to use shell companies to hold FCC licenses, and we could speculate that AC BidCo is the Aircraft Communications Bidding Company, created by Gogo for the purpose of the 2006-2008 auctions. These two licenses are active for the former Airfone band, and Gogo reportedly continues to use some of the original Airfone ground stations. Gogo's air-ground network, which operates at 800MHz as well as in a 3GHz band allocated specifically to Gogo, was originally based on CDMA cellular technology. The ground stations were essentially cellular stations pointed upwards. It's not clear to me if this CDMA-derived system is still in use, but Gogo relies much more heavily on their Ku-band satellite network today. The 450MHz licenses are fascinating. AURA is the only company to hold current licenses, but the 246 reveal the scale of the AGRAS business. Airground of Idaho, Inc., until 1999 held a license for an AGRAS ground station on Brundage Mountain McCall, Idaho. The Arlington Telephone Company, until a 2004 cancellation, held a license for an AGRAS ground station atop their small telephone exchange in Arlington, Nebraska. AGRAS ground stations seem to have been a cottage industry, with multiple licenses to small rural telephone companies and even sole proprietorships. Some of the ground stations appear to have been the roofs of strip mall two-way radio installers. In another life, maybe I would be putting a 450MHz antenna on my roof to make a few dollars. Still, there were incumbents: numerous licenses belonged to SkyTel, which after the decline of AGRAS seems to have refocused on paging and, then, gone the same direction as most paging companies: an eternal twilight as American Messaging ("The Dependable Choice"), promoting innovation in the form of longer-range restaurant coaster pagers. In another life, I'd probably be doing that too. [1] This is probably a topic for a future article, but the Mercury Network was a computerized system that Goeken built for a company called Florist's Telegraph Delivery (FTD). It was an evolution of FTD's telegraph system that allowed a florist in one city to place an order to be delivered by by a florist in another city, thus enabling the long-distance gifting of flowers. There were multiple such networks and they had an enduring influence on the florist industry and broader business telecommunications.

yesterday 2 votes
Why do lemon batteries work?

And chemistry, I guess

2 days ago 2 votes