My experience with shortcuts is a bit frustrating. I started to use iPhone as a trigger, but I move as much logic outside of the phone.
Beemind charging time
- Go to https://spreadsheets.new
- Put in A1, A2, A3: timestamp, type, delta
- Extensions → Apps Script
- Put this code with proper values
- Deploy (make sure anyone can access, authorise new app)
- Copy URL. Add
?token=AUTH_TOKEN&action=start or stop
- This URL should fire when you use your iOS Shortcut for start/stop charging the battery
const CONFIG = {
AUTH_TOKEN: 'REPLACE_WITH_SECRET_TOKEN',
SPREADSHEET_ID: 'REPLACE_WITH_SPREADSHEET_ID',
SHEET_NAME: 'charging',
BEEMINDER_USER: 'REPLACE_WITH_USERNAME',
BEEMINDER_GOAL: 'REPLACE_WITH_GOAL_SLUG',
BEEMINDER_TOKEN: 'REPLACE_WITH_BEEMINDER_TOKEN',
MIN_SESSION_MIN: 30,
MAX_SESSION_MIN: 120
};
function doGet(e) {
try {
const token = e.parameter.token;
const action = e.parameter.action; // start | stop
if (token !== CONFIG.AUTH_TOKEN) {
return error('Invalid token');
}
if (!['start', 'stop'].includes(action)) {
return error('Invalid action');
}
const sheet = SpreadsheetApp
.openById(CONFIG.SPREADSHEET_ID)
.getSheetByName(CONFIG.SHEET_NAME);
if (!sheet) {
return error('Sheet not found');
}
const now = new Date();
if (action === 'start') {
writeRow(sheet, now, 'start', '');
return ok('Start logged');
}
if (action === 'stop') {
return handleStop(sheet, now);
}
} catch (err) {
return error(err.message);
}
}
function handleStop(sheet, stopTime) {
const lastStart = findLastStart(sheet);
if (!lastStart) {
return error('No matching start event');
}
const startTime = new Date(lastStart.timestamp);
const diffMs = stopTime - startTime;
const diffMin = Math.floor(diffMs / 60000);
// Save raw stop
writeRow(sheet, stopTime, 'stop', diffMin);
if (diffMin < CONFIG.MIN_SESSION_MIN) {
return ok('Session too short – ignored');
}
const capped = Math.min(diffMin, CONFIG.MAX_SESSION_MIN);
sendToBeeminder(capped);
return ok(`Stop logged, ${capped} min sent to Beeminder`);
}
function findLastStart(sheet) {
const data = sheet.getDataRange().getValues();
for (let i = data.length - 1; i >= 0; i--) {
if (data[i][1] === 'start') {
return {
timestamp: data[i][0]
};
}
}
return null;
}
function writeRow(sheet, timestamp, type, delta) {
sheet.appendRow([
timestamp,
type,
delta
]);
}
function sendToBeeminder(minutes) {
const url = `https://www.beeminder.com/api/v1/users/${CONFIG.BEEMINDER_USER}/goals/${CONFIG.BEEMINDER_GOAL}/datapoints.json`;
const payload = {
value: minutes,
comment: 'Charging session',
auth_token: CONFIG.BEEMINDER_TOKEN
};
const options = {
method: 'post',
payload: payload,
muteHttpExceptions: true
};
UrlFetchApp.fetch(url, options);
}
function ok(msg) {
return ContentService
.createTextOutput(JSON.stringify({ status: 'ok', message: msg }))
.setMimeType(ContentService.MimeType.JSON);
}
function error(msg) {
return ContentService
.createTextOutput(JSON.stringify({ status: 'error', message: msg }))
.setMimeType(ContentService.MimeType.JSON);
}
Log iOS screen time
Similar for Jomo:
- Automation at 23:45 daily
- Get screentime from Jomo
- Ask GPT to generate
.gs file you can use to store the data
Tracking update
I lost a few days in a box, because the automation was not logging it, because my iCloud is full and it can’t reliably store things on iCloud when you use mobile data. I don’t want to dive into details, but I think decoupling a trigger and a function or a controller and a model is always a good idea.
On Jomo screen time. It’s different to what iOS reports by sometimes 20%, so I sort of lost continuity of screen time data. This is okay for me, as pick ups become the new north star.
I want to have the awareness of the screen time, but measure pick ups. I’ll leave my phone-in-the-box goal though. I removed all other goals, I find little value in them overall, it’s too much.
More than beeminder goals, I’d like to have a log of my life - did I drink coffee within 1 hour of waking up, did I drink enough water etc. I think this will totally happen before 2030s via some wearable, neuralink or something like that. Oura is close to achieve this dream.