I Thought He Came With You is Robert Ellison’s blog about software, marketing, politics, photography, time lapse and the occasional well deserved rant. Follow along with a monthly email, RSS or on Facebook. About 7,250,102,050 people have not visited yet so it might be your first time here. Suggested reading: Got It, or roll the dice.

Looking for Catfood Software?

Catfood Software

Catfood Software products including Catfood Earth, WebCamSaver and PdfScan are now distributed from the Downloads page of I Thought He Came With You. For updates on new releases please subscribe to the email list or feed. If you need support please leave a comment here.

Book reviews for February 2018

The Killer Angels (The Civil War Trilogy, #2) by Michael Shaara

The Killer Angels (The Civil War Trilogy, #2) by Michael Shaara



Men Without Women by Haruki Murakami

Men Without Women by Haruki Murakami


I just wish each short story was a whole book.


Dark State (Empire Games #2) by Charles Stross

Dark State (Empire Games #2) by Charles Stross


It's going to be a long, long wait for the third book...


Gun Insurance could pay for Buybacks

Gun Insurance could pay for Buybacks

Gun buybacks are not a new idea, in fact a mandatory buyback scheme was a big success in Australia. Gun insurance as a means of reducing gun violence isn't either. But what if you combined the two?

Set up a mandatory government run insurance program for all new firearms sales. If you are a 40 year old farmer with a shotgun stored in a gun safe then insurance is pretty cheap. If you are a 19 year old with an assault rifle then it is eyewateringly expensive. The risk that you will do something harmful with the gun is priced into the cost of ownership.

One of the criticisms of gun insurance (and of any gun control measure in general) is that there are so many firearms already in circulation in the US. So take the proceeds of the insurance scheme and use them to buy back guns.

Responsible gun owners are not inconvenienced, we reduce risky firearms sales and slowly draw down the number of uninsured guns in circulation.


Liquid Democracy and united.vote

Liquid Democracy and united.vote

This November there will be a limited opportunity to put liquid democracy into action. Limited to District 19 of the California Assembly (where I happen to live) and limited to the candidacy of David Ernst (which is a long shot).

Liquid democracy is a referendum on every act, with the difference that you can delegate your vote to someone you trust instead of voting on every issue yourself. At one extreme it's mob rule and at the other (if you delegate your vote to David) it's no different than the current system. The potential magic is where various groups of voters are powerfully represented by someone who is closer to their ideal candidate. If elected David promises to vote based on a liquid democracy implementation at united.vote.

I think functionally liquid democracy shares some problems with proportional representation / transferable vote schemes. If you end up delegating to blocks that represent 40%, 45% and 15% of voters then the 15% block holds the balance of power and gains undue influence. It raises new problems as well. If my elected representative is just doing what my delegate says and I can change my delegate at any time then there aren't really elections any more. So we've replaced 1 representative with N delegates. Delegates are going to raise money to gain and hold onto power and will become beholden to special interests. Are there term limits for delegates? If we end up with a small pool of powerful delegates does it make any difference if I switch my vote to a group of 0.025% of the electorate?

I'm interested in giving it a try though. I like that I can take back control on issues that I particularly care about. I might well vote for David in November.

So what about united.vote?

You have to pay $1 with a credit card to sign up. This is for voter verification and it's not a steep cost but it is a massive conversion barrier. When I signed up today there were just over 1,000 participants. The site is currently oriented around the US Congress rather than the California Assembly. You don't need a password which is nice. Instead you get emailed a one time code each you log in on a new device.

One big missing feature is some sort of directory of prospective delegates. You can delegate to your existing representative (which is pointless) or search for someone you know but really I want to find someone who closely matches my views and who has a good voting record. This isn't possible right now. Until that's fixed you should delegate your vote to me ;)

The voting record is hard to parse as well. For example Kamala Harris, one of my Senators, has an F. For the votes where she has a score it's 100%, 100%, 92% and 15%. Most votes have no liquid democracy opinion attached (is this a 0?). I need to see the math here.

Having said all that my preferred option is legislative service.

(Photo by chuttersnap on Unsplash)

Export Google Fit Daily Steps to a Google Sheet

Google Fit Daily Step Export

Google Fit is a great way to keep track of your daily step count without needing to carry a Fitbit or other dedicated tracker. It's not easy to get that data out though, as far as I can tell the only way is Google Takeout which is not made for automation. Luckily there is an API and you can do almost anything with Google Sheets.

If you're looking to export your step count this post has everything you need, just follow the instructions below to get your spreadsheet up and running. This is also a good primer on using OAuth2 with Google Apps Script and should be a decent starting point for a more complex Google Fit integration. If you have any questions or feedback please leave a comment below.

To get started you need a Google Sheet, an apps script project attached to the sheet and a Google API Project that will provide access to the Fitness API. That might sound intimidating but it should only take a few minutes to get everything up and running.

In Google Drive create a new spreadsheet and call it whatever you like. Rename the first tab to 'Steps'. Enter 'Date' in cell A1 and 'Steps' in cell B2. Next select 'Script editor...' from the Tools menu which will open a new apps script project.

Give the apps script project a name and then select 'Libraries...' from the Resources menu. Next to 'Add a library' enter 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF ad click Add. This will find the Google OAuth2 library. Choose the most recent version (24 at the time of writing) and click Save. Then select 'Project properties' from the File menu and make a note of the Script ID (a long series of letters and numbers).

Open the Google API Console. Create a new project and name it something like 'Google Fit Sheet'. From the Dashboard click Enable APIs and Services and find and select the Fitness API. Then go to Keys and create an OAuth Client ID. You'll be asked to create a consent screen, the only field you need to enter is the product name (i.e. 'My Fit App'). Then choose Web Application as the application type. You need to set the name and the authorized redirect URL. The redirect URL is https://script.google.com/macros/d/{SCRIPTID}/usercallback replacing {SCRIPTID} with the actual Script ID you made a note of above. After adding this make a note of the Client ID and Client Secret.

Go back to the apps script project and paste the code below into the Code.gs window:

// add your Google API Project OAuth client ID and client secret here
var ClientID = '';
var ClientSecret = '';

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Google Fit')
      .addItem('Authorize if needed (does nothing if already authorized)', 'showSidebar')
      .addItem('Get Steps for Yesterday', 'getSteps')

// see step count example at https://developers.google.com/fit/scenarios/read-daily-step-total
function getSteps() {
  var start = new Date();

  var end = new Date();
  var fitService = getFitService();
  var request = {
    "aggregateBy": [{
    "bucketByTime": { "durationMillis": 86400000 },
    "startTimeMillis": start.getTime(),
    "endTimeMillis": end.getTime()
  var response = UrlFetchApp.fetch('https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate', {
    headers: {
      Authorization: 'Bearer ' + fitService.getAccessToken()
    'method' : 'post',
    'contentType' : 'application/json',
    'payload' : JSON.stringify(request, null, 2)
  var json = JSON.parse(response.getContentText());
  var steps = json.bucket[0].dataset[0].point[0].value[0].intVal;
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName('Steps');
  sheet.appendRow([start, steps]);

// functions below adapted from Google OAuth example at https://github.com/googlesamples/apps-script-oauth2

function getFitService() {
  // Create a new service with the given name. The name will be used when
  // persisting the authorized token, so ensure it is unique within the
  // scope of the property store.
  return OAuth2.createService('fit')

      // Set the endpoint URLs, which are the same for all Google services.

      // Set the client ID and secret, from the Google Developers Console.

      // Set the name of the callback function in the script referenced
      // above that should be invoked to complete the OAuth flow.

      // Set the property store where authorized tokens should be persisted.

      // Set the scopes to request (space-separated for Google services).
      // see https://developers.google.com/fit/rest/v1/authorization for a list of Google Fit scopes

      // Below are Google-specific OAuth2 parameters.

      // Sets the login hint, which will prevent the account chooser screen
      // from being shown to users logged in with multiple accounts.
      .setParam('login_hint', Session.getActiveUser().getEmail())

      // Requests offline access.
      .setParam('access_type', 'offline')

      // Forces the approval prompt every time. This is useful for testing,
      // but not desirable in a production application.
      //.setParam('approval_prompt', 'force');

function showSidebar() {
  var fitService = getFitService();
  if (!fitService.hasAccess()) {
    var authorizationUrl = fitService.getAuthorizationUrl();
    var template = HtmlService.createTemplate(
        '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
        'Close this after you have finished.');
    template.authorizationUrl = authorizationUrl;
    var page = template.evaluate();
  } else {
  // ...

function authCallback(request) {
  var fitService = getFitService();
  var isAuthorized = fitService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');

Right at the top of the code there are spaces to enter the Client ID and Client Secret from the API Console. Enter these and save the project.

Switch back to your Google Sheet and reload. After reloading there will be a Google Fit menu item. First select Authorize... You'll get a screen to authorize the script and then a sidebar with a link. Click the link to authorize the script to access your Google Fit data. You can then close the sidebar and select Get Steps for Yesterday from the Google Fit menu. You should see a new row added to the spreadsheet with yesterday's date and step count.

The final step is to automate pulling in the data. Go back to the apps script project and select Current project's triggers from the Edit menu. Add a trigger to run getSteps() as a time driven day timer - I recommend between 5 and 6am. You can also click notifications to add an email alert if anything goes wrong, like your Google Fit authorization expiring (in which case you just need to come back and authorize from the Google Fit menu again.

At this point you're all set. Every day the spreadsheet will automatically update with your step count from the day before. You can add charts, moving averages, export to other systems, pull in your weight or BMI, etc. I want to add a seven day moving average step count to this blog somewhere as a semi-public motivational tool... watch this space.

Book reviews for January 2018

The World to Come by Dara Horn

The World to Come by Dara Horn



The Fifth Season (The Broken Earth, #1) by N.K. Jemisin

The Fifth Season (The Broken Earth, #1) by N.K. Jemisin



The Cuban Affair by Nelson DeMille

The Cuban Affair by Nelson DeMille



I'm not afraid of Google

A Project Fi display ad on an article about Google's insane targeting prowess

At BGR Chris Smith writes about Google's prodigious data collection:

"But that doesn’t change the fact that Google collects an incredible amount of data about you, especially from that device you use most, your Android phone"

And so I was amused as a Google Fi subscriber on a Pixel XL running Google Chrome and signed into my Google Account that the ad in the middle of the article was for Project Fi. If Google can't help paying BGR under these set of circumstances then we're some way off from the adtech singularity.

News: Facebook defends its simple 2 question survey on the trustworthiness of news publishers

Facebook defends its simple 2 question survey on the trustworthiness of news publishers

What if everyone recognizes a news source and say half of them trust it but it isn't true?

Coho Salmon in Lagunitas Creek

Lagunitas Creek Coho Salmon Hike

Two Coho Salmon in Lagunitas Creek (center of picture)

Coho Salmon in Lagunitas Creek (center of picture)

A five mile guided hike from Samuel P. Taylor State Park to the Leo T Cronin Fish Viewing Area along Lagunitas Creek and back again to spot spawning Coho Salmon. The hike was a program offered by Turtle Island and the specific route will vary based on where the salmon are currently active. Having done this once I think I could spot them again without assistance but it's well worth donating to get the full naturalist led experience.

Hike starts at 38.0244751,-122.7291936 (Google Earth).

Banner Chat

Chat Notifications and Chat Banner Notifications in Skype

I'm hoping that a chat banner notification is for really great chats only and not some subtle distinction between chat notification types that I can't figure out even after some moderate to heavy googling. If anyone knows seriously please tell me.

Book reviews for December 2017

Artemis by Andy Weir

Artemis by Andy Weir