More from Christian Selig
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.
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.
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.
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. 🫡
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: \(pushTokenString)") } } Use token to start Live Activity server-side Now that you have the token, we use that to send a push notification (using APNs) to start the Live Activity on the user’s device. There’s lots of server side libraries for this, or you can just use curl, or you can an online site to test like this one (in the case of the latter where you’re uploading your token to random sites, please create a sample token that you delete afterward). The key points that differ from sending a “normal” push notification: Headers Set your push token to the pushToStartToken you received above Set the topic to your app’s normal bundle ID with an added suffix of .push-type.liveactivity Set the priority to 5 for low priority or 10 for a high priority notification (does not seem like any values in between work) Set apns-push-type to liveactivity Payload timestamp field with the current unix timestamp event field set to start attributes-type set to the name of your Swift Live Activities attributes struct attributes with a dictionary representing your Swift Live Activity attributes content-state with the initial content state as a dictionary, similar to attributes alert field set with a title and a body (will silently fail if you just set alert to a string like the old days) Note that you cannot use the old certificate based authentication and instead have to use token based authentication and http2. Send the http request and it should start the Live Activity on the iOS device! 🎉 Aside Sending push notifications is kinda complicated, so you likely want a server-side library. I wanted to play around with Node for this for the first time, and in case you go down that path, in September 2024 Node is in a weirdly lacking spot for APNs libraries. The de facto one is abandoned, the community replacement for it doesn’t work with TypeScript, and there’s a third option with TypeScript support but it isn’t super popular and has some issues. I ended up going back to Go, and there’s an excellent APNs library there. It’s broken on iOS 17 On iOS 17, getting the above push token is really hard (you can seemingly only get it once). I tried for ages to get the token before stumbling upon a thread on the Apple forums where a user said to delete the app, reboot your device, then fresh install it. Sure enough that worked and the token displayed, but if I just rebooted the device, or just reinstalled the app, it wouldn’t. Had to do all of them. And no it didn’t change if I used a release configuration. I tried this on iOS 17.6.1 (latest iOS 17 release at the time of writing). It does not seem to be an issue at all on iOS 18. The difficulty in acquiring it makes it incredibly hard to use on iOS 17 if you add in the feature in an update and the user isn’t getting your app from a fresh install, to the extent that I can’t really trust its reliability on iOS 17 as a feature you could advertise, for instance. John Gruber recently wrote about the Apple Sports app and wondered why its Live Activities feature is iOS 18 only. A reader wrote in to mention the new broadcast push notifications feature requiring iOS 18, and that well may be it, but I’d say it’s equally as likely that it just doesn’t work reliably enough on iOS 17 for even Apple to bother. Update/end the activity This part admittedly confused me a bit. The docs state: Send the push-to-start token to your server and use the pushToStartTokenUpdates sequence to receive token updates. Similar to the update token, update it on your server when needed and invalidate the old token. While the system starts the new Live Activity and wakes up your app, you receive the push token you use for updates. To update and end the Live Activity on devices that aren’t running iOS 18 or iPadOS 18, use this update push token as if you obtained it by starting a Live Activity from within your app. I assumed that this all operated through that same pushToStartTokenUpdates, because as soon as you start the activity server-side, your app wakes up, and your pushToStartTokenUpdates async sequence fires again with a “new” token. However the “new” token is just the same one that you started the activity with, and if you try to end your activity server-side with this token, nothing happens. Turns out, your pushToStartTokenUpdates is (per the name!) only able to start Live Activities. Not sure why it fires a second time with the same token, but you do want to use that async sequence to monitor for changes to the start token, because it might change and the next time you want to start a new Live Activity you’ll need that token. To update/end your Live Activity, what you actually want to do is create a separate async sequence to monitor your app for Live Activities that get created, and then monitor its push token: // Listen for local Live Activity updates for await activity in Activity<IceCreamWidgetAttributes>.activityUpdates { // Upon finding one, listen for its push token (it is not available immediately!) for await pushToken in activity.pushTokenUpdates { let pushTokenString = pushToken.reduce("") { $0 + String(format: "%02x", $1) } print("New activity detected with push token: \(pushTokenString)") } } iOS will also call this async sequence on start of a new Live Activity from a server, and you use that token to update/end it. I’m not blaming the documentation on this, I understand it as it is written now, but I wanted to clarify in case anyone else gets confused. Once your server is made aware of this token, you can end your Live Activity server-side with the following changes from the above start considerations: Payload: event should be end or update Payload: If ending you might want a dismissal-date unix timestamp. If you don’t set this, iOS will immediately remove the Live Activity from the Dynamic Island but leave it on the Lock Screen for up to four hours. You may want this, but you can control how long it stays there by setting dismissal-date, if you set it to now or in the past it will remove it upon receipt of the notification. Payload: Send a new content-state if updating (optional if ending, if ending and you want to leave it on the lock screen (see previous point) you can set a content-state which will serve as the final content state for the Live Activity) Payload: Do not send attributes or attributes-type as these are intended to be immutable through the life of the Live Activity There’s other interesting ones that you might want to consider but aren’t as important, like stale-date, discussed in the docs. That’s it! That should cover most things! I want to thank the incredible Francesc Bruguera for helping me get unstuck a few places.
More in technology
When 3D printing matured from an industrial edge case to a mainstream commercial technology in the 2010s, it captured the imaginations of everyone from schoolteachers to fashion designers. But if there’s one group that really, really got excited about 3D printing, it was makers. When 3D printers became commercially available, they knew that everything was […] The post What can you do with Arduino and a new 3D printer? appeared first on Arduino Blog.
There are a handful of instruments that are staples of modern music, like guitars and pianos. And then there are hundreds of other instruments that were invented throughout history and then fell into obscurity without much notice. The Luminaphone, invented by Harry Grindell Matthews and unveiled in 1925, is a particularly bizarre example. Few people […] The post Recreating a bizarre century-old electronic instrument appeared first on Arduino Blog.
Sometimes I think I should pivot my career to home automation critic, because I have many opinions on the state of the home automation industry---and they're pretty much all critical. Virtually every time I bring up home automation, someone says something about the superiority of the light switch. Controlling lights is one of the most obvious applications of home automation, and there is a roughly century long history of developments in light control---yet, paradoxically, it is an area where consumer home automation continues to struggle. An analysis of how and why billion-dollar tech companies fail to master the simple toggling of lights in response to human input will have to wait for a future article, because I will have a hard time writing one without descending into incoherent sobbing about the principles of scene control and the interests of capital. Instead, I want to just dip a toe into the troubled waters of "smart lighting" by looking at one of its earliest precedents: low-voltage lighting control. A source I generally trust, the venerable "old internet" website Inspectapedia, says that low-voltage lighting control systems date back to about 1946. The earliest conclusive evidence I can find of these systems is a newspaper ad from 1948, but let's be honest, it's a holiday and I'm only making a half effort on the research. In any case, the post-war timing is not a coincidence. The late 1940s were a period of both rapid (sub)urban expansion and high copper prices, and the original impetus for relay systems seems to have been the confluence of these two. But let's step back and explain what a relay or low-voltage lighting control system is. First, I am not referring to "low voltage lighting" meaning lights that run on 12 or 24 volts DC or AC, as was common in landscape lighting and is increasingly common today for integrated LED lighting. Low-voltage lighting control systems are used for conventional 120VAC lights. In the most traditional construction, e.g. in the 1940s, lights would be served by a "hot" wire that passed through a wall box containing a switch. In many cases the neutral (likely shared with other fixtures) went directly from the light back to the panel, bypassing the switch... running both the hot and neutral through the switch box did not become conventional until fairly recently, to the chagrin of anyone installing switches that require a neutral for their own power, like timers or "smart" switches. The problem with this is that it lengthens the wiring runs. If you have a ceiling fixture with two different switches in a three-way arrangement, say in a hallway in a larger house, you could be adding nearly 100' in additional wire to get the hot to the switches and the runner between them. The cost of that wiring, in the mid-century, was quite substantial. Considering how difficult it is to find an employee to unlock the Romex cage at Lowes these days, I'm not sure that's changed that much. There are different ways of dealing with this. In the UK, the "ring main" served in part to reduce the gauge (and thus cost) of outlet wiring, but we never picked up that particular eccentricity in the US (for good reason). In commercial buildings, it's not unusual for lighting to run on 240v for similar reasons, but 240v is discouraged in US residential wiring. Besides, the mid-century was an age of optimism and ambition in electrical technology, the days of Total Electric Living. Perhaps the technology of the relay, refined by so many innovations of WWII, could offer a solution. Switch wiring also had to run through wall cavities, an irritating requirement in single-floor houses where much of the lighting wiring could be contained to the attic. The wiring of four-way and other multi-switch arrangements could become complex and require a lot more wall runs, discouraging builders providing switches in the most convenient places. What if relays also made multiple switches significantly easier to install and relocate? You probably get the idea. In a typical low-voltage lighting control system, a transformer provides a low voltage like 24VAC, much the same as used by doorbells. The light switches simply toggle the 24VAC control power to the coils of relays. Some (generally older) systems powered the relay continuously, but most used latching relays. In this case, all light switches are momentary, with an "on" side and an "off" side. This could be a paddle that you push up or down (much like a conventional light switch), a bar that you push the left or right sides of, or a pair of two push buttons. In most installations, all of the relays were installed together in a single enclosure, usually in the attic where the high-voltage wiring to the actual lights would be fairly short. The 24VAC cabling to the switches was much smaller gauge, and depending on the jurisdiction might not require any sort of license to install. Many systems had enclosures with separate high voltage and low voltage components, or mounted the relays on the outside of an enclosure such that the high voltage wiring was inside and low voltage outside. Both arrangements helped to meet code requirements for isolating high and low voltage systems and provided a margin of safety in the low voltage wiring. That provided additional cost savings as well; low voltage wiring was usually installed without any kind of conduit or sheathed cable. By 1950, relay lighting controls were making common appearances in real estate listings. A feature piece on the "Melody House," a builder's model home, in the Tacoma News Tribune reads thus: Newest features in the house are the low voltage touch plate and relay system lighting controls, with wide plates instead of snap buttons---operated like the stops of a pipe organ, with the merest flick of a finger. The comparison to a pipe organ is interesting, first in its assumption that many readers were familiar with typical organ stops. Pipe organs were, increasingly, one of the technological marvels of the era: while the concept of the pipe organ is very old, this same era saw electrical control systems (replete with relays!) significantly reduce the cost and complexity of organ consoles. What's more, the tonewheel electric organ had become well-developed and started to find its way into homes. The comparison is also interesting because of its deficiencies. The Touch-Plate system described used wide bars, which you pressed the left or right side of---you could call them momentary SPDT rocker switches if you wanted. There were organs with similar rocker stops but I do not think they were common in 1950. My experience is that such rocker switch stops usually indicate a fully digital control system, where they make momentary action unobtrusive and avoid state synchronization problems. I am far from an expert on organs, though, which is why I haven't yet written about them. If you have a guess at which type of pipe organ console our journalist was familiar with, do let me know. Touch-Plate seems to have been one of the first manufacturers of these systems, although I can't say for sure that they invented them. Interestingly, Touch-Plate is still around today, but their badly broken WordPress site ("Welcome to the new touch-plate.com" despite it actually being touchplate.com) suggests they may not do much business. After a few pageloads their WordPress plugin WAF blocked me for "exceed[ing] the maximum number of page not found errors per minute for humans." This might be related to my frustration that none of the product images load. It seems that the Touch-Plate company has mostly pivoted to reselling imported LED lighting (touchplateled.com), so I suppose the controls business is withering on the vine. The 1950s saw a proliferation of relay lighting control brands, with GE introducing a particularly popular system with several generations of fixtures. Kyle Switch Plates, who sell replacement switch plates (what else?), list options for Remcon, Sierra, Bryant, Pyramid, Douglas, and Enercon systems in addition to the two brands we have met so far. As someone who pays a little too much attention to light switches, I have personally seen four of these brands, three of them still in use and one apparently abandoned in place. Now, you might be thinking that simply economizing wiring by relocating the switches does not constitute "home automation," but there are other features to consider. For one, low-voltage light control systems made it feasible to install a lot more switches. Houses originally built with them often go a little wild with the n-way switching, every room providing lightswitches at every door. But there is also the possibility of relay logic. From the same article: The necessary switches are found in every room, but in the master bedroom there is a master control panel above the bed, from where the house and yard may be flooded with instant light in case of night emergency. Such "master control panels" were a big attraction for relay lighting, and the finest homes of the 1950s and 1960s often displayed either a grid of buttons near the head of the master bed, or even better, a GE "Master Selector" with a curious system of rotary switches. On later systems, timers often served as auxiliary switches, so you could schedule exterior lights. With a creative installer, "scenes" were even possible by wiring switches to arbitrary sets of relays (this required DC or half-wave rectified control power and diodes to isolate the switches from each other). Many of these relay control systems are still in use today. While they are quite outdated in a certain sense, the design is robust and the simple components mean that it's usually not difficult to find replacement parts when something does fail. The most popular system is the one offered by GE, using their RR series relays (RR3, RR4, etc., to the modern RR9). That said, GE suggests a modernization path to their LightSweep system, which is really a 0-10v analog dimming controller that has the add-on ability to operate relays. The failure modes are mostly what you would expect: low voltage wiring can chafe and short, or the switches can become stuck. This tends to cause the lights to stick on or off, and the continuous current through the relay coil often burns it out. The fix requires finding the stuck switch or short and correcting it, and then replacing the relay. One upside of these systems that persists today is density: the low voltage switches are small, so with most systems you can fit 3 per gang. Another is that they still make N-way switching easier. There is arguably a safety benefit, considering the reduction in mains-voltage wire runs. Yet we rarely see such a thing installed in homes newer than around the '80s. I don't know that I can give a definitive explanation of the decline of relay lighting control, but reduced prices for copper wiring were probably a main factor. The relays added a failure point, which might lead to a perception of unreliability, and the declining familiarity of electricians means that installing a relay system could be expensive and frustrating today. What really interests me about relay systems is that they weren't really replaced... the idea just went away. It's not like modern homes are providing a master control panel in the bedroom using some alternative technology. I mean, some do, those with prices in the eight digits, but you'll hardly ever see it. That gets us to the tension between residential lighting and architectural lighting control systems. In higher-end commercial buildings, and in environments like conference rooms and lecture halls, there's a well established industry building digital lighting control systems. Today, DALI is a common standard for the actual lighting control, but if you look at a range of existing buildings you will find everything from completely proprietary digital distributed dimming to 0-10v analog dimming to central dimmer racks (similar to traditional theatrical lighting). Relay lighting systems were, in a way, a nascent version of residential architectural lighting control. And the architectural lighting control industry continues to evolve. If there is a modern equivalent to relay lighting, it's something like Lutron QSX. That's a proprietary digital lighting (and shade) control system, marketed for both residential and commercial use. QSX offers a wide range of attractive wall controls, tight integration to Lutron's HomeSense home automation platform, and a price tag that'll make your eyes water. Lutron has produced many generations of these systems, and you could make an argument that they trace their heritage back to the relay systems of the 1940s. But they're just priced way beyond the middle-class home. And, well, I suppose that requires an argument based on economics. Prices have gone up. Despite tract construction being a much older idea than people often realize, it seems clear that today's new construction homes have been "value engineered" to significantly lower feature and quality levels than those of the mid-century---but they're a lot bigger. There is a sort of maxim that today's home buyers don't care about anything but square footage, and if you've seen what Pulte or D. R. Horton are putting up... well, I never knew that 3,000 sq ft could come so cheap, and look it too. Modern new-construction homes just don't come with the gizmos that older ones did, especially in the '60s and '70s. Looking at the sales brochure for a new development in my own Albuquerque ("Estates at La Cuentista"), besides 21st century suburbanization (Gated Community! "East Access to Paseo del Norte" as if that's a good thing!) most of the advertised features are "big." I'm serious! If you look at the "More Innovation Built In" section, the "innovations" are a home office (more square footage), storage (more square footage), indoor and outdoor gathering spaces (to be fair, only the indoor ones are square footage), "dedicated learning areas" for kids (more square footage), and a "basement or bigger garage" for a home gym (more square footage). The only thing in the entire innovation section that I would call a "technical" feature is water filtration. You can scroll down for more details, and you get to things like "space for a movie room" and a finished basement described eight different ways. Things were different during the peak of relay lighting in the '60s. A house might only be 1,600 sq ft, but the builder would deck it out with an intercom (including multi-room audio of a primitive sort), burglar alarm, and yes, relay lighting. All of these technologies were a lot newer and people were more excited about them; I bring up Total Electric Living a lot because of an aesthetic obsession but it was a large-scale advertising and partnership campaign by the electrical industry (particularly Westinghouse) that gave builders additional cross-promotion if they included all of these bells and whistles. Remember, that was when people were watching those old videos about the "kitchen of the future." What would a 2025 "Kitchen of the Future" promotional film emphasize? An island bigger than my living room and a nook for every meal, I assume. Features like intercoms and even burglar alarms have become far less common in new construction, and even if they were present I don't think most buyers would use them. But that might seem a little odd, right, given the push towards home automation? Well, built-in home automation options have existed for longer than any of today's consumer solutions, but "built in" is a liability for a technology product. There are practical reasons, in that built-in equipment is harder to replace, but there's also a lamer commercial reason. Consumer technology companies want to sell their products like consumer technology, so they've recontextualized lighting control as "IoT" and "smart" and "AI" rather than something an electrician would hook up. While I was looking into relay lighting control systems, I ran into an interesting example. The Lutron Lu Master Lumi 5. What a name! Lutron loves naming things like this. The Lumi 5 is a 1980s era product with essentially the same features as a relay system, but architected in a much stranger way. It is, essentially, five three way switches in a box with remote controls. That means that each of the actual light switches in the house (which could also be dimmers) need mains-voltage wiring, including runner, back to the Lumi 5 "interface." Pressing a button on one of the Lutron wall panels toggles the state of the relay in the "interface" cabinet, toggling the light. But, since it's all wired as a three-way switch, toggling the physical switch at the light does the same thing. As is typical when combining n-way switches and dimming, the Lumi 5 has no control over dimmers. You can only dim a light up or down at the actual local control, the Lumi 5 can just toggle the dimmer on and off using the 3-way runner. The architecture also means that you have two fundamentally different types of wall panels in your house: local switches or dimmers wired to each light, and the Lu Master panels with their five buttons for the five circuits, along with "all on" and "all off." The Lumi 5 "interface" uses simple relay logic to implement a few more features. Five mains-voltage-level inputs can be wired to time clocks, so that you can schedule any combination(s) of the circuits to turn on and off. The manual recommends models including one with an astronomical clock for sunrise/sunset. An additional input causes all five circuits to turn on; it's suggested for connection to an auxiliary relay on a burglar alarm to turn all of the lights on should the alarm be triggered. The whole thing is strange and fascinating. It is basically a relay lighting control system, like so many before it, but using a distinctly different wiring convention. I think the main reason for the odd wiring was to accommodate dimmers, an increasingly popular option in the 1980s that relay systems could never really contend with. It doesn't have the cost advantages of relay systems at all, it will definitely be more expensive! But it adds some features over the fancy Lutron switches and dimmers you were going to install anyway. The Lu Master is the transitional stage between relay lighting systems and later architectural lighting controls, and it straddled too the end of relay light control in homes. It gives an idea of where relay light control in homes would have evolved, had the whole technology not been doomed to the niche zone of conference centers and universities. If you think about it, the Lu Master fills the most fundamental roles of home automation in lighting: control over multiple lights in a convenient place, scheduling and triggers, and an emergency function. It only lacks scenes, which I think we can excuse considering that the simple technology it uses does not allow it to adjust dimmers. And all of that with no Node-RED in sight! Maybe that conveys what most frustrates me about the "home automation" industry: it is constantly reinventing the wheel, an oligopoly of tech companies trying to drag people's homes into their "ecosystem." They do so by leveraging the buzzword of the moment, IoT to voice assistants to, I guess now AI?, to solve a basic set of problems that were pretty well solved at least as early as 1948. That's not to deny that modern home automation platforms have features that old ones don't. They are capable of incredibly sophisticated things! But realistically, most of their users want only very basic functionality: control in convenient places, basic automation, scenes. It wouldn't sting so much if all these whiz-bang general purpose computers were good at those tasks, but they aren't. For the very most basic tasks, things like turning on and off a group of lights, major tech ecosystems like HomeKit provide a user experience that is significantly worse than the model home of 1950. You could install a Lutron system, and it would solve those fundamental tasks much better... for a much higher price. But it's not like Lutron uses all that money to be an absolute technical powerhouse, a center of innovation at the cutting edge. No, even the latest Lutron products are really very simple, technically. The technical leaders here, Google, Apple, are the companies that can't figure out how to make a damn light switch. The problem with modern home automation platforms is that they are too ambitious. They are trying to apply enormously complex systems to very simple tasks, and thus contaminating the simplest of electrical systems with all the convenience and ease of a Smart TV. Sometimes that's what it feels like this whole industry is doing: adding complexity while the core decays. From automatic programming to AI coding agents, video terminals to Electron, the scope of the possible expands while the fundamentals become more and more irritating. But back to the real point, I hope you learned about some cool light switches. Check out the Kyle Switch Plates reference and you'll start seeing these buildings and homes, at least if you live in an area that built up during the era that they were common (1950s to the 1970s).
Is there anything more irritating than living with a partner who procrastinates on their share of the chores? Even if it isn’t malicious, it sure is annoying. Taking out the trash is YouTuber CircuitCindy’s boyfriend’s responsibility, but he often fails to do the task in a timely manner. That forced Cindy to implement a sinister […] The post YouTuber builds robot to make boyfriend take out the trash appeared first on Arduino Blog.