OS theme
Dark theme
Light theme
My iPhone battery
My iPhone Wi-Fi
Current motion
Click for current location
👈🏼 🕸💍 👉🏼
Skip to main content

Learn more about me. Email me at

cleverdevil

cleverdevil

cleverdevil

cleverdevil

cleverdevil

cleverdevil

www.npmjs.com/~cleverdevil

cleverca.st

trakt.tv/users/cleverdevil

mastodon.social/@cleverdevil

$cleverdevil

 

Taking Control of my Personal Health Data

9 min read

Over the past few years, I've invested time and effort into extricating important data and content from external services, and bringing it into systems that I own and control. I've moved on from Facebook and Instagram, established tracking for my movie, tv, and podcast activity, automatically track my location in multiple ways, and much more. But, for years now, one type of data has eluded me: my personal health data.

As of today, that has changed! I'd like to share with you what I've built.

Overview of Enhancements

My website now features my personal health metrics in several places. First, there is now a health section which shows both daily health metrics and historical metrics. You can go backward and forward in time and compare my daily metrics to historical min, max, and average values.

For the daily metrics, I use the familiar Apple Activity Rings format, and include supporting metrics across a variety of categories, including activity, heart health, and sleep analysis.

Daily Health Metrics Screenshot

For the historical metrics, I am particularly proud of the visualization. Each metric has a bar representing the minimum, maximum, and average values, and the gradient that is used to fill the bar adjusts to reflect the position of the average value.

Historical Health Metrics Screenshot

In addition, I have augmented my monthly summaries.

Monthly Health Summary Screenshot

Each day is represented by an Activity Ring and can be clicked on to view detailed, in-context metrics for that day.

Overall, I am quite pleased with how this project has turned out. Navigating through health metrics is snappy, the visualizations are attractive and useful, and it fits in neatly with the rest of my site.

Now that we've walked through what these features look like in practice, let's discuss how I gather the data and make it useful.

Unlocking HealthKit

I've owned an Apple Watch since the Series 2 watch was released, and have worn it fairly consistently ever since. As a result, I've got quite a lot of data amassed on my iPhone in Apple Health. That data is accessible through the Health app, and also via the HealthKit APIs. While I am a pretty strong developer, my skillset doesn't include much in the way of iOS development. I've made a few attempts at building an iOS app that will allow me to extract my HealthKit data automatically, but never made it far before I ran out of steam.

A few weeks ago, I discovered an app called Health Auto Export (which I will refer to as HAE for the rest of this post), which neatly solves the problem. HAE has many great features, but the key feature is "API Export," which allows you to automatically have your HealthKit data sent to an HTTP endpoint in JSON or CSV format, with control over time period and aggregation granularity. With this app in hand, I set about creating an API to store, index, and make that data searchable.

Introducing Health Lake

HAE uses a simple, but nested JSON data structure to represent health metrics. Because the data is structured, in plain-text, and will mostly sit at rest, a data lake is a natural target to store the data. Data lakes on Amazon Web Services (AWS) are generally implemented with Amazon S3 for storage, as it is well-suited to the use case, is deeply integrated with AWS' data, analytics, and machine learning (DAML) services.

In order to keep most of the complexity out of my website, I decided to build a microservice which is entirely focused on getting data into the data lake and making it useful. I call this service Health Lake, and the source is available on GitHub.

Sync and Store

Let's take a look at the first endpoint of Health Lake, which accepts data from HAE, trasforms it to align with the requirments for AWS's DAML services, and stores it in S3 - HTTP POST /sync.

HAE structures its data in a nested format:

{
    "data": {
        "metrics": [
            {
                "units": "kcal",
                "name": "active_energy",
                "data": [
                    {
                        "date": "2021-01-20 00:00:00 -0800",
                        "qty": 370.75
                    },
                    ...
                ]
            },
            ...
        ],
    }
}

As you can see, the data is nested fairly deeply. In order to simplify my ability to query the data, Health Lake transforms the data to a flatter structure, with each data point being formatted in JSON on a single line. On each sync, I create a single object that contains many data points, one per line, in a format like this:

{"name": "active_energy", "date": "2021-01-20 00:00:00 -0800", "units": "kcal", "qty": 370.75 }
...

Each sync object is stored in my target S3 bucket with the key format:

syncs/<ISO-format date and time of sync>.json

The prefix on the object name is critical, as it enables the indexing and querying of sync data independent from other data in the bucket.

Querying the Data Lake

Now that we have data being sent to our data lake and stored in an efficient, standardized format, we can focus on making that data searchable. Very often, I use relational databases like MySQL or PostgreSQL to store data and make it searchable with SQL. AWS provides a few great services which allow you to treat your data lake as a series of database tables that can be queried using SQL.

The first service we'll leverage is AWS Glue, which provides powerful data integration capabilities:

AWS Glue is a serverless data integration service that makes it easy to discover, prepare, and combine data for analytics, machine learning, and application development. AWS Glue provides all of the capabilities needed for data integration so that you can start analyzing your data and putting it to use in minutes instead of months.

Data integration is the process of preparing and combining data for analytics, machine learning, and application development. It involves multiple tasks, such as discovering and extracting data from various sources; enriching, cleaning, normalizing, and combining data; and loading and organizing data in databases, data warehouses, and data lakes.

Using AWS Glue, I created a database called "health," and then created a "crawler," which connects to my data store in S3, walks through all of the data, and attempts to infer the schema based upon hints and classifiers. The crawler can be run manually on-demand, or can be scheduled to run on a regular basis to continuously update the schema as new fields are discovered. Here is what the configuration of my crawler looks like in the AWS Glue console:

AWS Glue Crawler Configuration Screenshot

Upon the first run of the crawler, a new table was created in my health database called syncs, which inferred the following schema:

AWS Glue Table Schema Screenshot

I wasn't able to get the crawler to match the date format properly, so I ended up creating a "view" which adds a proper column that is a timestamp using the following SQL statement:

CREATE OR REPLACE VIEW 
    history 
AS SELECT
    date_parse(substr(date, 1, 19), '%Y-%m-%d %H:%i:%s') as datetime,
    *
FROM
    syncs

Now that our data lake has been crawled, and a database, table, and view have been defined in our AWS Glue Data Catalog, we can use Amazon Athena to query our data like using standard SQL. Athena is entirely serverless, so there is no infrastructure to manage, and you pay only for the queries that you run.

Daily Metrics

For our daily metric view, we need a summary of all metrics gathered on a specific day. To accomplish this, I added an endpoint to our microservice:

HTTP GET /detail/<YYYY-MM-DD>

In response to this request, the client will receive a JSON data structure collecting all data points for that day. Under the hood, the microservice is running the following SQL query:

SELECT * FROM history 
WHERE
    datetime >= TIMESTAMP 'YYYY-MM-DD 00:00:00'
AND
    datetime <= TIMESTAMP 'YYYY-MM-DD 23:59:59'

Because I pay for every query that I run on Athena, and to achieve great performance, I store the query results in the proper format for the client in S3 after I run the query. I then implemented some intelligence to decide if, for any given request, I should pull from the cache, or regenerate fresh data. Take a look at the source code for more detail.

Monthly Metrics

To show our monthly summaries, we need to get data for each day of the month. Rather than sending a request and query for every single day of the month, I decided to implement another endpoint to our microservice:

HTTP GET /summary/<YYYY-MM>

In response to this request, the client will receive a JSON data structure collecting all data points for the month, sorted by date. To accomplish this, I run the following SQL query:

SELECT * FROM history
WHERE
    datetime >= TIMESTAMP 'YYYY-MM-01 00:00:00'
AND
    datetime <= TIMESTAMP 'YYYY-MM-31 00:00:00'

The start and end range are actually calculated to ensure I have the proper end date, as not every month has the same number of days. Again, to save costs and improve performance, results are intelligently cached in our S3 bucket.

Global Metrics

Generating a global summary of all data points in the data lake was a bit more challenging. To make things more efficient, I created another view in my database with this query. Results are, again, intelligently cached.

Website Integration

With all of this great data available to me, it was time to integrate it with my website, which uses the Known CMS. I have created a Known plugin that provides enhancements that are specific to my website. Using this plugin, I simply send requests to the Health Lake microservice, parse the JSON, and create my visualizations.

Conclusions

Overall, I am quite pleased that I have been able to integrate this data into my website, and more importantly, to free the data from its walled garden and place it under my control and ownership.

 

Normal is a Privilege

4 min read

I just wish things would get back to normal.

Its a refrain we’ve all heard since the emergence of COVID-19. The world has been thrown into chaos, our way of life has been threatened, and many people have lost their jobs. The best minds in medicine and science are encouraging life-altering precautions like social distancing, wearing masks, and staying home. People are having to adapt to this “new normal” quickly, working from home, perhaps balancing the pressures of parenting children, or taking care of family members who are at higher risk. While its obviously the right thing to do to be cautious, and not re-open the country too quickly, protestors have taken to the streets to demand that things go back to “normal,” even if it puts others at risk.

Life in a global pandemic is not comfortable, convenient, or fun. We can all agree on that. The “new normal” sort of sucks.

But, what of the old normal? Well, consider George Floyd.

Last week, our chaotic world erupted into further chaos with the brutal murder of George Floyd by a Police Officer in Minneapolis. Make no mistake, I absolutely believe that this was a murder, and one that was the direct result of a fundamentally flawed system that demands reform. There must be justice for George Floyd, and it cannot just come in the form of punishment for the killers, it also must come in the form of radical, systematic change.

For many people, going back to “normal” is, on the surface, quite appealing. Returning to our privileged lives, where we feel safe to go out to eat, walk with our friends and family at shopping malls, gather in our places of worship, and to do it all with a strong sense of security – after all, the police, and all other systems of power, are there to protect us.

But, for a huge portion of our country, “normal” means avoiding the police because they cannot be trusted to protect you. It means less opportunity at work. It means overcoming an unequal system to fight for the same benefits that others readily receive.

You know what? The old normal sucks, too.

It is no surprise to me that many people who are calling to “re-open our country” in the midst of a global pandemic are also telling protestors to calm down, or to “tone down” their methods of protest. These demands often come from a position of privilege; of preserving a system that fundamentally benefits them at the expense of others. They like things just the way they were.

But, this time, we can’t let the cries for a “return to normalcy” win. As an ally, I cannot sit idly by, or demand that the oppressed respond to their generations-long oppression with calm, non-violent protest. It’s been nearly 30 years since the police brutalization of Rodney King, and it’s clear that nothing has fundamentally changed in that time. Peaceful protest isn’t enough. Voting isn’t enough. Patient conversation isn’t enough. Incremental change isn’t enough. Now, I am not advocating for violence, but I am advocating for persistent, enduring commitment to driving change.

We must listen to what Martin Luther King Jr. said in his Letter from a Birmingham Jail in August of 1963:

I have almost reached the regrettable conclusion that the Negro’s great stumbling block in the stride toward freedom is not the White Citizens Councillor or the Ku Klux Klanner but the white moderate who is more devoted to order than to justice; who prefers a negative peace which is the absence of tension to a positive peace which is the presence of justice; who constantly says, “I agree with you in the goal you seek, but I can’t agree with your methods of direct action”; who paternalistically feels that he can set the timetable for another man’s freedom; who lives by the myth of time; and who constantly advises the Negro to wait until a “more convenient season.”

So, fellow white people, it is time to step up. Acknowledge your privilege, speak up, and demand radical, fundamental change. Amplify the voices of people of color. Fearlessly support Black Lives Matter. Embrace the fact that creating a more just, equitable, and fair society likely means that you will need to sacrifice your own privilege for the benefit of others.

Let’s not go back to normal. Let’s create a better normal.

 

Hey, Siri... Don't Bother Daddy!

2 min read

In the new world, where many of us are working 100% from home, it can be really difficult to find quiet and focus. Distractions abound, especially if you have kids. As I mentioned yesterday, closed office doors don't seem to get the message across to my children, so I thought it would be great if I could have a digital sign on my offce door. Well, I made it happen.

I'm really happy with how it turned out! How did I do it? It was pretty simple, really. I had a really old Amazon Fire tablet that hasn't been used in years, as the kids have both transitioned over to iPads. So, I charged it up, installed a "full screen browser" app and a hack that lets you keep the screen awake perpetually. Then, I wrote a quick web service to track state, and hooked it into HomeKit and Siri using Homebridge and this excellent plugin.

Tada! Now I can toggle the state of the sign using just my voice, or with automations. I can even automatically flip the status to "Don't Bother Daddy" when my calendar shows that I am scheduled for a meeting.

Update: the source code is available for the web service.

 

📺 Automatically Downloading YouTube Content to Plex

4 min read

I have a small number of content creators that I enjoy following on YouTube, but I fundamentally dislike YouTube's shady algorithms, poor user experience, and invasive ads. These days, most of the content I watch is stored on my Plex server, so I decided to find a way to automatically download and add my favorite YouTube content to my Plex server. After a bit of searching, I found this guide from DIY Futurism which outlined a nice approach to the problem.

My Plex YouTube library

My approach is similar, and makes use of the excellent youtube-dl project, along with a personal media scanner and personal media agent for Plex. I run my Plex Media Server on my Synology DS1019+ NAS, which I absolutely adore, so that's where I set everything up. The process was quite simple:

  1. Installed the aforementioned personal media scanner and personal media agent in my Plex server and enabled them in my Plex server settings.
  2. Created a "TV Shows" library in Plex that uses the scanner and agent. I called mine "YouTube."
  3. Installed youtube-dl on my Synology. I already have Python installed there, so it was as simple as running pip install youtube-dl.

At this point, I was ready to create a script that would download the content and add it to Plex. The key is to take advantage of all of the great features in youtube-dl, including the ability to provide a "batch" file containing target channels, the ability to embed metadata and download thumbnails, and an "archive" feature which tracks what has already been downloaded. My script is a variation on the one from DIY Futurism:

#!/bin/sh

cd /var/services/homes/admin/Media/YouTube
/volume1/@appstore/python3/bin/youtube-dl --playlist-reverse \
  --dateafter now-2weeks \
  --download-archive /var/services/homes/admin/Media/YouTube/downloaded.txt \
  -i \
  -o "%(uploader)s/%(playlist)s/%(playlist)s - S01E%(playlist_index)s - %(title)s [%(id)s].%(ext)s" \
  --add-metadata \
  --write-thumbnail \
  --batch-file=/var/services/homes/admin/Media/YouTube/channel_list.txt

Let's walk through the script. First, I change directories to where I want all of my content downloaded. This is the same directory that I configured in Plex for my "YouTube" library that I created earlier. Next, I specify that I want to process the videos in the playlist chronologically (in "reverse"). I also specify that I only want to download videos that were published in the last two weeks using the --dateafter parameter. You can tweak this to download as much or as little of the content as you'd like.

Next, I point youtube-dl to a text file containing a list of all content that I've already downloaded using the --download-archive parameter, which youtube-dl will automatically maintain for me. Because I am limiting my downloads to the last two weeks, I did need to pre-populate this text file with all of the historical content to avoid having to scan through thousands of videos on each run of the script.

Next, I specify a format for where to store the downloaded content and what to name the files and directories, instruct youtube-dl to embed metadata, and to write a thumbnail image as well. This data will be used by the personal media scanner and agent to help Plex index the content.

Finally, I specify a "batch file," which contains a list of channels that I want to download content from. The format is simply one YouTube URL per line.

After an initial run that I performed manually, I scheduled the script to run every four hours, and now my Plex server is my central location for my YouTube content.

 

IndieWeb Summit 2019

8 min read

I had the great pleasure of spending this weekend in Portland, OR for the 2019 IndieWeb Summit. IWS is my favorite event of the year, as it gives me the opportunity to spend time with so many smart, like-minded, principled people who are interested in using technology to make a positive impact on the world. This year’s IWS was a runaway success, selling out for the first time ever, and featuring some truly great sessions, keynotes, and projects.

Day Zero: The Pre-Party

On Friday evening, a large subset of the attendees gathered at Pine Street Market to share in food, drink, and community. It was fun to reconnect with IndieWeb friends like David Shanske, Aaron Parecki, Tantek Çelik, Marty McGuire, Jacky Alciné, and Malcom Blaney. Much fun was had by all, and the post-party festivities carried on well into the night.

Day One: Keynotes and Sessions

Keynotes

5d17a64c2daf2f84f031723d_IMG_0226.JPG

The next morning, we kicked off the summit with coffee and breakfast, followed by some great keynotes.

First up, Tantek gave a brief state of the IndieWeb presentation, and shared details about the IndieWeb’s Code of Conduct, our use of color-coded lanyards to give people a choice about whether or not they should be included in photos, and our use of pins to enable people to display their preferred pronouns.

Next was fellow Micro.blog community member Kitt Hodsden who gave an inspiring talk on contractions and expansions, which really set the tone for the rest of the summit. Kitt should be very proud of both the content of her talk, and her wonderful, passionate delivery. Well done, Kitt!

Continuing the thoughtful keynote presentations was Jordan Brady, a native of Portland that I met several years ago at a Homebrew Website Club meeting at the DreamHost PDX offices. Jordan shared her story of changing her domain during her job search. Her experience shined a light on the challenges of being a woman in tech.

Marty McGuire followed, with an interactive keynote about his IndieWeb experience from his iPhone. It was very cool to see how far we’ve come in the past few years, with native mobile apps like Indigenous by Eddie Hinkle, and deep integrations thanks to iOS Shortcuts. Kudos to Marty for presenting the entire keynote directly from his iPhone.

Finally, Jacky Alciné closed the keynotes with an exploration of how to make the IndieWeb available for all. Jacky is a really thoughtful guy, and I enjoyed hearing his perspective.

Lightning Intros

After keynotes, all IWS participants were encouraged to give brief, 2-minute introductions, along with demos of their personal websites. During my introduction, I talked about the importance of preserving and owning your memories, and showed how my website helped me cope with the loss of my sixteen year old dog Winston earlier this year.

Sessions

5d17e52b6ad3cb4f3e219a19_IMG_1304-p-1080.jpeg

After a group photo and lunch at local Portland food carts, we returned to the venue to plan out the rest of the day. IWS uses the BarCamp format for session planning, where attendees lead interactive session proposals, and build the schedule together. We came up with quite an impressive collection of sessions!

There were a lot of great conversations, but I’ll touch on three of my personal favorites:

  1. “Follow That Bird” – This session was proposed by David Shanske, and addressed the problem of discovery. On platforms like Facebook and Twitter, it is easy to just click the “follow” or “friend” button, and following / friend lists help users discover other people. On the IndieWeb, we’re still figuring out the mechanics of how to improve this user experience, especially in social readers like Together and Monocle. Lots of great discussion all around, and this session ended up inspiring my project for day two, which I’ll talk about shortly.
  2. Private Posts – I ran this session, and we talked about another problem that is neatly solved by traditional social platforms: sharing content privately. Again, some great discussion about how to solve this problem in the IndieWeb. Thankfully, there’s plenty of prior art out there to build upon. I’m looking forward to working on solving this for Known, which is the CMS I use for this website.
  3. Possible Futures and IndieWeb – An extremely engaging and interactive session facilitated by Ariana Lutterman. Ariana guided the group through the process of exploring the many possible futures for the IndieWeb based upon emerging signals of change through the lens of growth, collapse, discipline, and transformation. We brainstormed on future implications across a number of axes – STEEPV (social, technological, environmental, economic, political, and values). Finally, we used our exploration to write headlines from the future. A really thoughtful and fun exercise. Many thanks to Ariana for guiding us.

At the end of a very long and engaging day, we split out into groups for evening activities. I joined a great group of nine for dinner at Jackrabbit, which featured an impressive and delicious 4-pound steak that we shared. Yum!

Day Two: Creative Hack Day!

IndieWebCamp Logo

On Sunday, we started things off with some tasty vegan breakfast options, and then I headed off to the first ever meeting of the Known Open Collective. It was great to finally connect, at least over video chat, with Marcus Povey, Jeremy Cherfas, and Paul Lindner. We definitely missed having Ben Werdmüller at the summit, but all send our best to him as he deals with this complicated thing we call “life.”

For the rest of day two, I focused on two things – helping other people and personal projects.

Inspired by the “Follow that Bird” session from day one, I decided that it would be extremely valuable to focus on the problem of discovery. I chose to update my site to make it easier to subscribe to my website, and to discover who and what I am following. The first, smaller part of the project, was to update my Subscribe page with more detailed instructions on how to subscribe to one or more of the feeds that are available on my website.

The second part of my project was to build a way for people to see what I am following. There is a wealth of information on “following” on the IndieWeb wiki, but none of the approaches discussed for documenting subscriptions really sat right with me. I am a fan of the DRY principle, and I don’t like the idea of manually creating “follow posts” when I have a perfectly serviceable source of truth for my subscriptions in my Microsub server. Thus, I embarked upon a project to automatically generate a page on my website that displays an up-to-date list of my subscriptions from Aperture.

Thanks to some enhancements by Aaron Parecki to Aperture, I was able to create a beautiful Following page that gives visitors to my website a way to see exactly what I am following in my social reader. They can use this to discover new content that they may enjoy. Even better, if you parse my Following page with a microformats2 parser, you’ll find that it is an h-feed containing h-entry “follow posts” with a u-follow-of property for each feed that I subscribe to. The only missing piece is that Aperture doesn’t yet provide me with the date and time that my subscription was created. Aaron has indicated that this is something he’d like to add, and once he does, I’ll update my page to include that data.

At the end of the summit, participants demoed their projects and progress before we closed out the summit. My personal favorite demo was from Malcom Blaney and Gregor Love, who demonstrated one of the first implementations of subscribing to private posts using AutoAuth. It was awesome to see private sharing in action, and it gives me confidence that 2020 could be the year that we solve this problem for good.

IndieKaraoke

Once the summit wrapped, a small group of us enjoyed what is becoming a wonderful annual tradition: IndieWeb Karaoke night! This year, we celebrated at Voicebox Northwest thanks to our wonderful host Lillian Karabaic. There was singing. There was laughing. There was a non-stop stream of incredible IndieWeb lyrical jokes:

  • “Bow down before the Zuckerberg. You’re going to get what you deserve.” – To the tune of Head Like a Hole by Nine Inch Nails.
  • “If you want to destroy the silos (whoa oh-oh), POSSE first, and then walk away!” – To the tune of Undone by Weezer.

We’re a bunch of very lovable nerds. I’d especially like to call out Fluffy for her amazing energy at karaoke. She made sure that fun was had by all.

Conclusions

Thank you so much to all of the organizers of IndieWeb Summit for doing such a great job building an inclusive, fun, and enjoyable event each year. This year’s summit was the best yet, and I have no doubt that next year will be even better.

I love you

 

iPad Pro Impressions

9 min read

iPad Pro

This past weekend, I took the plunge and purchased myself an iPad Pro, including an Apple Pencil and Smart Keyboard Folio. Amazon had the iPad Pro on sale for 16% off of list price, which is an uncommonly large discount that I couldn’t pass up. I also had saved up quite a bit of Amazon rewards credit, so my out of pocket cost was quite low. I’ve had my eye on an iPad Pro for quite some time, and now that I have one, its time to share my impressions.

Which iPad Pro?

I chose to purchase the smaller 11" iPad Pro in Space Gray with 256GB of storage. Why? Well, the 12.9" iPad Pro was very tempting, but my primary use case for this device is to be a highly portable alternative to my MacBook Pro. What do I plan to use it for? Ideally:

  • Productivity
    • Email
    • Documents
    • Task Management
    • Note-taking, as an alternative to my trusty paper notebooks
  • Development
    • SSH’ing into my various Linux environments
    • Local development, preferably using Python
  • Writing / Blogging
    • Publishing to my website
  • Media
    • Streaming from my Plex server
    • Hulu, Netflix, Amazon Prime Video, etc.
  • Reading

Given my constraints and desire to have something more portable than my MacBook Pro, I opted for the smaller size iPad Pro and Apple’s very slim keyboard case, with the Apple Pencil to help me replace my paper notebooks. I chose the 256 GB storage option because the base model only offers 64 GB, which is just not enough for my needs.

The Good: Hardware

So, what’s the good news? Well, there’s a lot to like. First off, the hardware itself is simply stunning. Its light, thin, fast, and beautiful. The screen is bright and crisp, and the bezel-less design is reminiscent of Dieter Rams' greatest hits. The last hardware design that I loved this much was the iPhone 5s.

The accessories are similarly well designed. The Smart Keyboard Folio attaches to the iPad Pro with ease thanks to an array of powerful magnets, and the Smart Connector means that I never have to worry about charging or pairing the keyboard. It just works. The Apple Pencil is similarly impressive, with an ingenious magnetic attachment to the side of the iPad Pro, and wireless charging that is effortless.

The Good: Software

iOS has come a long way in the past few years, adding rudimentary file management in the Files app, early multi-tasking capabilities, and iPad-specific features that enhance the overall experience. That said, there’s a long, long way to go from an OS-level to truly make the iPad Pro a professional tool. I’ll touch on that more later.

Now, there are some truly amazing apps that I have been enjoying to help me with my target use cases. They’re not all perfect, but I am encouraged by the vibrant and growing ecosystem of truly professional apps for iPad. These give me a great deal of hope for the future of the Mac as these apps begin to show up via Marzipan. Below is a list of apps I am using or experimenting with so far:

  • Productivity
    • Email – Apple Mail. I am a heavy email user, and try out email clients often. For now, I am sticking with the built-in option, which is adequate.
    • Documents – Pages, Numbers, Keynote, and Drafts for personal projects. For work, we use GSuite, so I have installed Google’s Drive, Slides, Sheets, and Docs apps.
    • Task-Management – I use Things on my Mac and iPhone, and now I am using it on my iPad Pro.
    • Note-taking – This is an area where I am spending a lot of time experimenting. I have very much enjoyed note-taking in Drafts with my Smart Keyboard Folio attached, but am also trying out note-taking apps that are more Apple Pencil driven, including Notability and Nebo.
  • Development
    • SSHPanic’s Prompt and the emerging iSH, which adds an emulated Linux environment to iOS.
    • Local Development – The aforementioned iSH has been a revelation, enabling me to do local development in a very similar way to how I would on macOS, with vim, Python 3.7, git, virtualenv, and other common terminal-based tools. I’m also experimenting with Pythonista and have my eye on a few other editors to play with (Textastic, Buffer, etc.).
  • Writing / Blogging
    • Blogging – Drafts with a custom Micropub action for publishing to my website.
    • Microblogging – Directly on my website, through Indigenous, or via the Micro.blog app.
  • Media
    • StreamingPlex, Hulu, Netflix, Amazon Prime Video, etc.
    • Local VideoInfuse, VLC, and Plex. To get video into Infuse and VLC, I tend to use youtube-dl inside of iSH.
  • Reading
    • Books – Apple’s Books app works great for ePub content.
    • News – Apple’s News app is decent, but mostly I use Safari with my favorite news sites, or more likely I use my feed reader.
    • ComicsChunky Reader is pretty solid, though I wish this entire category was more like Plex, with rich metadata indexing and organization on the server, with clients for reading.
    • Web – Safari.
    • Feeds – I have installed Together as a Progressive Web App on my home screen and it works well.

While none of the above apps are perfect, I have been quite impressed with them as a whole.

The Bad: Hardware

While the iPad Pro and its accessories are truly impressive hardware, they’re not free of issues. Because the bezels are so small on the iPad Pro, it can be a little uncomfortable to hold in portrait layout while reading. In the lap, the whole Smart Keyboard Folio and iPad Pro setup is a bit top-heavy, making it slightly unstable. Other than these minor nits, overall I think the hardware is top-notch.

The Bad: Software

While the app ecosystem is amazing, and iOS has made great strides, there are still some fundamental missing pieces that prevent me from viewing iOS as a true alternative to macOS:

  • Keyboard – While the Smart Keyboard Folio is generally great to type on, in spite of its small size and low key travel, it is greatly hampered by software limitations in iOS. There is no ability to re-map keys in iOS, so I am stuck with a system-wide Caps Lock key, and no ESC key. Some apps, such as iSH, allow you to map Caps Lock to ESC, but this should really be handled system-wide. In addition, the Smart Keyboard Folio has a “globe” button in the bottom left corner which is infuriating. Pressing it pops up the Emoji keyboard on screen, and its right next to the control key, which I use heavily.
  • Fonts – iOS comes with a small set of fonts, and there is no standard, built-in way to install additional fonts. I have been able to use an app called AnyFont to install fonts, including my preferred programming font, Dank Mono, but because the system itself doesn’t have support for font management, most apps don’t surface font customization. Kudos to Drafts, though, for allowing users to pick from any font available to the system, including ones installed through AnyFont.
  • File Management – Apple added the Files app to iOS, and its a good start, but has so far to go to truly make it a pro-level file management tool. In addition, there isn’t any ability to plug in external storage to my iPad Pro, in spite of the fact that it has a USB-C port.
  • Multi-Tasking – iOS has a very rudimentary multi-tasking system, which allows you to place multiple apps onto the screen at the same time, in floating panels, and in split views. It works, but is fiddly to use, with delicate gestures required to bring up the dock, drag apps over each other, and position them. In addition, there is no way to have multiple “windows” of an app used in different multi-tasking sessions. I think Apple is definitely innovating here, looking for new ways to approach multi-tasking than traditional window management. In many ways, iOS multi-tasking reminds me of tiling window managers, just… not as good. I’m hoping for good news on this front at WWDC.
  • Web Browsing – Safari is an awesome browser. But, on the iPad, too often websites give you the mobile version of their site, rather than serving up the “full size” website. In addition, there isn’t any sort of download manager, or support for extensions other than content blockers.
  • iSH – I have heaped praise on iSH above, and it really is pretty incredible. Its also an open source project, and is rapidly improving… but its not there yet. Things I’d love to see added to iSH that would greatly improve my experience: custom font selection, better performance, compatibility with additional software, tabbed sessions, and a choice of a different base operating system than Alpine Linux.

Conclusions

Overall, I am thrilled with my iPad Pro, and really excited to see where Apple is headed with iOS for “pro” users. There is so much to like, and massive potential for improvement. While I don’t see the iPad Pro displacing my laptop anytime soon, I think it will become an important part of my workflow.

 

Tracking My Movie, TV, and Podcast Activity

6 min read

As part of my continuing efforts to preserve and capture my memories, I’ve been spending some energy adding more capabilities to my website. I already capture photos, recipes, blog posts, status updates, and other more traditional types of content. In addition, I’ve been privately tracking my own location continuously for months now, including the ability to see some current details about my location and status. I also use the excellent OwnYourSwarm service from Aaron Parecki to record check-ins at specific locations on my site.

Last week, I realized that I was missing some data on my website that would add additional context when exploring my memories: my TV and movie watching history, and a record of what podcasts I listen to. As of today, I am now automatically tracking all of this data, and I’m happy to share a bit about how I made it happen.

Movies and TV

Let’s start with how I am tracking what movies and TV shows I watch. As it turns out, there is already a wonderful service out there for tracking this data called Trakt, which is a startup based out of San Diego. Trakt has done the hard part for me, with an extensive and complete database of movies and TV shows for me to pull from, and a host of great apps that use its powerful API to help users put data into their Trakt account. I am personally using the Watcht app for iOS to manually ad TV episodes and movies to my watch history on Trakt, and to show me a calendar of upcoming TV episodes for my favorite shows.

Trakt Logo

But, being an IndieWeb community member, I want to make sure that my memories don’t get lost in the event that Trakt goes away one day. In addition, I want to be able to see my TV and movie history in the context of the rest of my website’s content. So, I needed to find a way to automatically sync that data to my website.

At first, I thought about using the extremely capable Trakt API to periodically sync to my website, but then I noticed that Trakt Pro members get access to an Atom feed of their watch history. Working with a custom REST API takes a lot of effort, while integrating with a standard feed format is extremely easy. So, I happily paid for a Trakt Pro subscription!

I created a Python script which periodically parses my Trakt feed and then creates entries on my website for each movie and TV episode I watch. It only took me about an hour to put the whole thing together.

Sidebar: Automatic Tracking from Plex

Plex Logo

As you may already know, I have a great collection of digital movies and TV shows. I use the outstanding Plex Media Server to enable me to stream and sync content to all my devices. As Eddie, my co-host from Two Dads Talking, recently mused, tracking activity automatically is much more reliable than remembering to manually track activity. Trakt provides a Plex plugin that automatically syncs your Plex history to Trakt, and once I had that installed, a significant chunk of my activity is now automatically synced!

I still have to manually track content watched outside of Plex, like live TV from Hulu, and content consumed on Netflix, but it’s a good start.

Podcasts

I’m not only a podcaster with a microcast and a podcast, I’m also an avid podcast listener. I listen to podcasts on my daily commute, to relax after work, and to kill time on airplanes. Wouldn’t it be great to have that history tracked on my website as well? As I mentioned above, tracking that history automatically is greatly preferable to manually having to log every episode I listen to. With that in mind, I set out to see if I could capture my activity.

Overcast Logo

I use the wonderful Overcast podcast app for iOS. Overcast is created by Marco Arment, who is also a prolific podcaster. It’s a fantastic and pleasant app to use, and is perhaps my favorite iOS app ever. Overcast has a sync service and web frontend available for users at overcast.fm. Not only can you listen to podcasts in your web browser on overcast.fm, you can also export an extended OPML file that contains all details about your account, including a listing of all podcast episodes you’ve ever listened to. Bingo!

I whipped up a script that logs into my account at Overcast.fm, then downloads a copy of this OPML file, and uses it to sync my history to my website. It works a treat, but I will caution that Marco seems to be rate limiting that OPML export pretty aggressively. For the time being, I’ve limited my sync to once daily, and I’ve also contacted Marco to get his input on how I am using his service. In an ideal world, I’d love to see Marco add a standard RSS, Atom, or JSON Feed for Overcast paid subscribers similar to what Trakt has done for Trakt Pro users. In the meantime, I’ll be conservative about how often I sync and await a reply from Marco.

Why Track Activity?

You may be wondering why I want to track all of this information. Eddie and I briefly touched on the topic in the last episode of Two Dads Talking, but it really comes down to the fact that our memories are precious, and the more context I have when looking back on my life, the richer my appreciation will be for the life I’ve been blessed to live.

During the process of adding these new types of memories to my website, I also have added a monthly “recap” feature which has been one of my all time favorite enhancements. I like them so much, I’ve added links to the last twelve monthly summaries to my home page to surface them. My favorite examples of monthly summaries so far:

  • January 2018, which marked my departure from DreamHost, the beginning of a new chapter in my career, and my second viewing of my favorite musical of all time.
  • July 2018, which includes travel all over the globe, some progress on my Indiepaper project, and outdoor movies in my front yard with my kids.
  • November 2017, which includes a trip to Australia, the acquisition of the best car I’ve ever owned, and my son’s first ever NFL football game.

I’m delighted to continue enriching my database of memories, and really happy with the way my movie, TV, and podcast tracking is shaping up so far.

 

New Podcast: Two Dads Talking

2 min read

I am not ashamed to admit that I love podcasts. In the early 2000s, blogs were the hot thing: an open publishing medium that allowed people to exchange ideas, converse, and share. Since then, blogs have faded, as increasingly toxic social media walled gardens have moved people off the open web. It’s a shame, really. I’m hopeful that blogs will rise again, but it’s going to take time.

Podcasts, on the other hand, have only grown in popularity, and are still blissfully free of central control from creepy ad-driven social media giants. We are in the golden age of podcasting!

I’ve had a personal microcast for a while now, and I’ve been pretty undisciplined about publishing it. Still, it has been a fun exercise, and I plan to keep at it for years to come.

Today, however, I’m excited to announce a new podcast project: Two Dads Talking. TDT is a longer form podcast featuring myself and my co-host Eddie Hinkle. Eddie and I have known each other for a few years now through the IndieWeb community, and we’ve found that we both have a lot in common, but also a significant amount of differences. This podcast is an opportunity for us to get to know each other better, and for our listeners to join us in that discovery.

Eddie and I are both parents, though at very different life stages, and both are people of faith, and technologists. I’m really looking forward to getting to know Eddie better, and I hope you’ll all join us in our journey of Two Dads Talking.

To subscribe, visit the Two Dads Talking website, follow us on Micro.blog, or subscribe in your favorite podcast client.

 

Indiepaper for macOS

1 min read

Indiepaper LogoIndieWeb Summit 2018 took place a few weeks ago in Portland, OR, and my project on day two was to create a service called Indiepaper, which is a "read it later" service for the IndieWeb. Indiepaper makes use of Mercury by Postlight Labs under the hood to extract article content and then publish it to a Micropub destination for later reading. Indiepaper is open source and is deployed on AWS Lambda using the Zappa framework. The Indiepaper website includes a tool to create a Bookmarklet for your web browser, and a Workflow for iOS that adds system-wide support for sending links to Indiepaper.

In order to make Indiepaper even easier to use, I created Indiepaper for macOS, which adds system-wide sharing support for Indiepaper to macOS. Here is a quick video demo of Indiepaper for macOS in action. Indiepaper for macOS is also open source, so feel free to poke around in the source code, and submit pull requests if you have improvements!

 

Add native support for Indiepaper

1 min read

Regarding Together

Now that I've launched Indiepaper, I'd love to see Together add native support for sending articles to Indiepaper with the click of a button. This would require a few configuration settings, including the configuration of a bearer token and a target micropub destination.

 

Going Serverless with Python WSGI Apps

1 min read

I've been writing web applications and services in Python since the late 1990s, and enjoy it so much that I created the Pecan web application framework way back in 2010. Configuring and deploying Python web applications, especially WSGI compliant applications, is fairly straightforward, with great WSGI servers like Gunicorn and uWSGI, and excellent Apache integration via mod_wsgi. But, for many use cases, creating and maintaining one or more cloud servers creates unnecessary cost and complexity. Security patches, kernel upgrades, SSL certificate management, and more, can be a real burden.

Since the creation of AWS Lambda, "serverless" has become a pretty popular buzzword. Could Lambda provide a way to deploy Python WSGI applications that helps reduce cost, complexity, and management overhead?

It was a fun topic to explore, and I've published a blog post over at Reliam.com about running Python WSGI apps on Lambda!

 
 

Freeing Myself from Facebook

5 min read

Ever since my discovery of the IndieWeb movement, I've wanted to free myself from Facebook (and Instagram) and their brand of surveillance capitalism. I want to own my own data, and be in control of how it is shared, and I don't want it to be used for advertising.

I've had this incarnation of a personal website for a few years, and have mostly been following the POSSE publishing model, publishing most forms of content on my website, and then automatically (or manually) syndicating that content to silos like Facebook and Twitter. But, much of my content still remains trapped inside of Facebook and Instagram.

Until now.

As of March 4, 2018, I've pulled the vast majority of my Facebook content into my website, and all of my Instagram photos into my website, paving the way for me to delete myself from Facebook (and potentially Instagram) by the end of 2018. What follows is a high-level overview of how I made the move.

Facebook

Exporting Data from Facebook

While Facebook does offer an export feature, its extremely limited, only includes very low resolution versions of your photos, and is generally very difficult to process programmatically. After some research, I discovered the excellent fb-export project on GitHub. Once installed, this tool will dump a huge amount (though, not quite all) of your Facebook data into machine-readable JSON files.

Since my website is compatible with the Micropub publishing standard, I then needed to convert this Facebook-native JSON data into microformats2 formatted JSON. Enter granary, an amazing swiss-army knife of IndieWeb by Ryan Barrett. Using granary, I whipped up a quick script that transforms the exported data into native microformats2 formatted JSON:

https://gist.github.com/cleverdevil/f33530706d6e8dacd13a8bd8e8c15dba

Publishing Liberated Data

At this point, I had a directory full of data ready to publish. Sort of. Unfortunately, not all of the data is easily translatable, or even desirable, to publish to my website. As a result, I created another script that let me, on a case by case basis, publish a piece of content, choose to skip it entirely, or save it to deal with later.

https://gist.github.com/cleverdevil/c857695bb2de1e46686d720cad9d124c

After running this script, I had a significant amount of my data copied from Facebook to my website. Huzzah!

Dealing with Photo Albums

Facebook has a "photo albums" feature, and I definitely wanted to get those memories onto my website. Again, I wrote a script that processes the exported data, and selectively allows me to upload all of the photos in an album to my website via Micropub, and then drops microformats2 JSON out that I could publish later.

https://gist.github.com/cleverdevil/d9c08ddc6eb2da0d060a5f6fe87ddf64

Once I finished processing and uploading all of the photos for the albums I wished to copy over, I ran a simple utility script I keep around to publish all of the albums as new posts to my website.

Here are some of the results:

Notice, one of these comes all the way back from 2009!

Almost There

There are still quite a few photos and other types of posts that I haven't yet been able to figure out how to migrate. Notably, Facebook has strange special albums such as "iOS Uploads," "Mobile Uploads," and "iPhoto Uploads" that represent how the photos were uploaded, not so much a group of related photos. Unfortunately, the data contained in the export produced by fb-export isn't quite adequate to deal with these yet.

Still, I am quite pleased with my progress so far. Time to move on to Instagram!

Instagram

Instagram has been slowly deteriorating as a service for years, so much so that I decided to completely stop publishing to Instagram earlier this year. It turns out, dealing with Instagram is a lot easier than Facebook when it comes to liberating your data.

Downloading My Data

After some research, I found instaLooter on GitHub, which allowed me to quickly export every single photo in its original resolution, along with nearly every bit of data I needed... except the photo captions. I ran instaLooter, and embedded the unique identifier in the filenames (which instaLooter refers to as the "code').

Getting Metadata and Publishing

I wrote a script that used granary to lookup the photo metadata and publish to my website via Micropub:

https://gist.github.com/cleverdevil/5bb767fd152de9b4c246d01086e91399

Note, I used the non-JSON form of Micropub in this case, because Known's Micropub implementation doesn't properly handle JSON for photos yet.

Conclusions

It turns out, that with a little knowhow, and a lot of persistence, you can liberate much of your data from Facebook and Instagram. I feel well on target to my goal of leaving Facebook (and maybe Instagram) entirely.

 

Kiddo Concentration

1 min read

William and Colette have been asking for “homework,” since they’ve heard about it from others. William is practicing his fine motor skills and Colette remains goofy.

 

Special Lunch With William

1 min read

Colette had rehearsal for a children’s musical, so Lacey and I took William to lunch at Playa Hermosa on the Hermosa Beach Pier. Good food and lots of smiles with my sweet boy.

 

Editing a post in Known can have destructive side effects on content

1 min read

There is a bug in Known which causes HTML posts published via Micropub to be changed (usually in bad ways) when "editing" the post, even when you don't actually make any changes to the post. I discovered this issue when publishing via Sunlit 2.0, which supports Micropub.

I published two stories:

Because Sunlit doesn't yet support syndication via Micropub, I clicked "edit" on one of the posts, and toggled on syndication to Twitter and Facebook, and then clicked "save." The result was that the post's content was changed (in a destructive way, resulting in visual regressions), even though I hadn't actually edited the content, or even clicked into the content editor.

Seems like this is a bug.

 

Car Museum with William

1 min read

Last week I took William to the car museum in El Segundo for a Hot Wheels event. The event was fine, but we had a lot more fun in the museum itself. William even had the chance to sit in a Deloreon.

 

San Diego with Lacey

1 min read

2018-01-20

a3e2d44e0f465dd7442a63127ea6c75725d3e48007e0b18eb8c6b1ed9717120939b9a74b49cf911cb8db789557f5114b2c31d321ac99e955b1761c2bdbd329ae

Dinner and drinks before our second viewing of Hamilton at the San Diego Civic Theatre.

2018-01-21

2a37b4070bde2bdaaa41023fb219e8ef

Late brunch “the morning after.” The view was far better than the food.

 

1955 Jaguar XKSS

1 min read

Season two of Amazon’s The Grand Tour has been a huge improvement over the lackluster debut season. The latest episode featured the Jaguar XKSS, which was originally released in 1955, and is being rereleased this year by Jaguar. The XKSS is easily one of the best looking cars of all time.

 

Baptism and Political Resistance ✊

3 min read

In today's church service, our pastor delivered a message about the biblical sacrament of baptism. After the message, the congregation was invited to the front of the church to participate in what the United Methodist Church refers to as the "Congressional Reaffirmation of the Baptismal Covenant." Following the process outlined in the UMC Book of Worship, I reaffirmed my baptism in Christ today. Before approaching the baptismal font, I read the following:

Renunciation of Sin and Profession of Faith

On behalf of the whole church, I ask you: Do you renounce the spiritual forces of wickedness, reject the evil powers of this world, and repent of your sin?

I do.

Do you accept the freedom and power God gives you to resist evil, injustice, and oppression in whatever forms they present themselves?

I do.

Do you confess Jesus Christ as your Savior, put your whole trust in his grace, and promise to serve him as your Lord, in union with the church which Christ has opened to people of all ages, nations, and races?

I do.

According the to grace given to you, will you remain faithful members of Christ’s holy church and serve as Christ’s representatives to the world?

I will.

This past year has been extremely trying for me in my faith in the wake of the 2016 Presidential election. Each passing day, I watch as many evangelicals enable injustice and oppression, and fail to reject the evil powers of this world, as the President of the United States spouts overtly racist, isolationist, and sexist words. I watch as many churches and Christians say nothing as the President pushes policies that harm the poor and deepen racial and gender inequality. Evangelicals, especially white evangelicals, voted hugely in favor of this man, and are enabling him every step of the way. I'm personally sick of it.

As I walked through the process of reaffirming my baptism today, I also am renewing my commitment to resist this despicible administration. I have made a promise to God to serve as Christ’s representative, to reject the evil forces of this world, to resist evil, injustice, and oppression in whatever forms they present themselves, and to do it all together with the church, which is open to all ages, nations, and races. Therefore I choose to resist this President, his hateful and isolationist agenda, and all who support him. I wish to do this together with the church, just as I am called. I encourage you to join me.

Note: I am not advocating that the Church should be "democrat" or "republican," or even political. I'm advocating that Christians, as Christ's representatives in the world, are called to fight injustice, oppression, and evil, no matter what form it takes. Right now, one of the most pressing forms of oppression and injustice in the world is Donald Trump, and his administration. Resist.