vfo-knob-phone

Google Contacts on the Telephone knob

The Telephone firmware (vfo-knob-phone) can take your starred Google contacts and make them the knob's favourites. Those are the names the dial steps through and the slab calls. The knob signs in to Google once, then keeps the list up to date by itself. It only ever reads your contacts.

This page covers:

  • setting it up: your own Google Cloud client, the small return page, and the knob;
  • what the knob does with your contacts and what it stores;
  • every message the page can show, and what to do about each;
  • the knob page's API.

The Telephone firmware itself is in the Telephone guide.

At a glance

What it readsthe contacts in your Starred group, with their names and phone numbers
What it writes to Googlenothing: the permission is read-only (contacts.readonly)
What it becomesthe favourites list: up to 40 entries, sorted by name, one per number
When it syncswhen the knob starts (if signed in), every six hours, right after you sign in, and when you press Sync now
What a sync needsthe knob on WiFi and no call up. A SIP account is not needed
What you needa free Google Cloud project of your own. The return page it needs is on the VFO-Knob website, at https://vfoknob.com/google.html

Why it needs two things of yours

Your own Google Cloud client. Google lets a device read contacts only through an app registered with Google, an OAuth client. Nothing of Google's is built into the knob. Each owner makes a client in their own free Google Cloud project, and the knob signs in with that.

A return page at an https address. After you sign in, Google sends the browser back to an address registered with the client. Google only accepts https addresses there. Your knob is http, on your own network, and Google cannot reach it. So Google returns to a small static page, google.html, and that page hands the sign-in on to your knob. The page has no server code and holds no secrets. It works from any https host.

1. The return page

The knob expects the page at:

https://vfoknob.com/google.html

That is the repository's google.html, served by the VFO-Knob website. An owner has nothing to do here but put that address in their Google client (step 4 below). It is the firmware's default, so the knob's page has it already.

Or host a copy yourself. If you would rather not depend on the website, copy google.html to any https address you control, for example https://example.org/vfo/google.html. Then enter that same address in two places:

  • in your Google client's Authorized redirect URIs (step 4 below);
  • on the knob's page, in Sign-in returns through (the client's redirect URI).

The two must match exactly, character for character.

2. Your Google Cloud client

This takes about five minutes, once. The console names below are Google's as of 2026. Since April 2025 the OAuth pages have their own section, the Google Auth Platform, which was formerly the OAuth consent screen under APIs & Services.

  1. A project. At console.cloud.google.com, create a project, for example VFO-Knob.

  2. The People API. Under APIs & Services → Library, find the Google People API and press Enable.

  3. The app. Open Google Auth Platform and press Get started:

    • give the app a name (VFO-Knob will do) and a support email address;
    • choose External as the audience;
    • give a contact email address, and finish.

    Then open Audience and press Publish app, so its status reads In production. Two reasons:

    • An app left in Testing can only be used by the test users listed there.
    • In Testing, Google expires every sign-in seven days after consent. The knob would lose access every week.

    You do not need Google's verification for this. See the unverified-app warning.

  4. The client. Under Clients, press Create client:

    • choose Web application;
    • under Authorized redirect URIs, add the return page's address exactly: https://vfoknob.com/google.html, or your own copy's;
    • press Create;
    • copy the Client ID and the Client secret.

    Copy the secret now. Since June 2025, Google shows a new client's secret only on this screen. Afterwards the console shows only its last four characters. If you lose it, add a new secret to the same client (a client can have two), then disable and delete the old one.

  5. On the knob: see the next section.

Optionally, under Data Access, add the scope https://www.googleapis.com/auth/contacts.readonly ("See and download your contacts"). The knob asks for it at sign-in either way.

One client can serve all your knobs. They share the same redirect address, and each knob signs in for itself.

3. On the knob

Open the knob's configuration page:

  • hold a finger on the meter arc until the knob clicks, and browse to the address on the card;
  • the user is admin, and the password is admin until you change it.

Use a computer or phone on the same network as the knob. At the end of the sign-in, the browser itself goes back to the knob's local address. Open the page by the knob's IP address (for example http://192.168.1.40) or by http://vfo-knob.local. The address you use is the one the sign-in returns to.

Under Google Contacts:

  1. Paste the OAuth client ID and the Client secret.
  2. If you host the return page yourself, put its address in Sign-in returns through. Otherwise leave the default, https://vfoknob.com/google.html.
  3. Press Save the client. The page says Google client saved.
  4. Press Sign in with Google. The button stays greyed out until a client ID and a secret are saved.
  5. Choose your Google account. Google then warns that it hasn't verified this app (see below). Go on, and allow the app to see your contacts.
  6. Google returns to the return page, which takes you back to the knob's page, at its Google Contacts section. The Sync row says signed in, reading the contacts. A few seconds later it says, for example, 12 favourites from Google, and the Favourites list above it fills in.

The unverified-app warning

Reading contacts is what Google calls a sensitive permission. An app asking for one without Google's review shows a warning screen first: Google hasn't verified this app. The app is your own, so this is expected:

  1. choose Advanced;
  2. choose Go to VFO-Knob (unsafe), which carries the name you gave the app;
  3. continue, and allow access to your contacts.

Google allows an unverified app up to 100 users in total. That is no limit for a client only you use.

What the knob does with your contacts

  • Which contacts. Your Starred group: the contacts you starred in Google Contacts or on your Android phone. Up to 200 are read.
  • Which numbers. Every phone number of each starred contact. The knob uses Google's international form when Google has one (+32475123456), otherwise the number as it was typed. It keeps only the digits, and a + at the start.
  • The names.
    • A contact with one number keeps its name.
    • A contact with several numbers gets one favourite per number. Each is named by up to 14 characters of the name and up to 8 of the number's kind: Jan Peeters Mobile, Jan Peeters Work.
    • A contact without a name is named by its number.
  • The list. Sorted by name, at most 40 favourites. Each sync replaces the whole list, entries added by hand included. To keep a list of your own, sign out of Google and edit the favourites on the page.
  • Starred contacts without a number are skipped. If none of the starred contacts has a number, the knob's log says so.

What the knob keeps

WhatWhere
The client IDflash (NVS namespace phone, key gcid)shown on the page, as every sign-in link carries it anyway
The client secretflash (gcsec)never shown again, and never returned by the API (only whether one is set)
Google's refresh tokenflash (grtok)what lets the knob sync without you
The return page's addressflash (grelay)only when it is not the default
An access tokenRAM, during a syncgone when the sync is done

Sign out on the page deletes the refresh token. The client stays saved for the next sign-in, and the favourites stay as they are.

To cut the knob off from Google's side, open myaccount.google.com/connections ("Your connections to third-party apps & services"), choose the app, and remove its access. The knob's next sync then fails with Google's sign-in expired: sign in again, and it deletes its token.

How it works

 browser           knob (http, LAN)          Google                 return page (https)
    |  Sign in with Google  |                    |                           |
    |---------------------->|                    |                           |
    |  302 to Google, state = "<knob address>/<nonce>"                       |
    |<----------------------|                    |                           |
    |  sign in, allow contacts                   |                           |
    |------------------------------------------->|                           |
    |  302 to the return page, with code and state                           |
    |<-------------------------------------------|                           |
    |------------------------------------------------------------------------>|
    |  the page checks the knob address is on the LAN,                       |
    |  and sends the browser on to http://<knob>/api/phone/google/callback   |
    |<------------------------------------------------------------------------|
    |---------------------->|  nonce checked, used once                     |
    |  302 to the page, #phonegoogle             |                           |
    |<----------------------|                    |                           |
    |                       |  then, by itself:  |                           |
    |                       |  code -> refresh token (HTTPS, with the secret)|
    |                       |------------------->|                           |
    |                       |  the starred group, then its people            |
    |                       |------------------->|                           |
    |                       |  favourites saved  |                           |

In words:

  1. Sign in with Google sends the browser to Google, with the knob's address (as your browser had it) and a fresh nonce in state.

  2. After you allow access, Google sends the browser to the return page with a one-time code.

  3. The return page sends the browser on to the knob, and only to a LAN address:

    • 10.x.x.x, 172.16-31.x.x or 192.168.x.x;
    • a name without dots (vfo-knob);
    • a name ending in .local, .lan, .home, .internal or .home.arpa;
    • optionally with a :port.

    For anything else it says there is no knob to return to.

  4. The knob checks the nonce: it must be the one from its own pending sign-in, and it is used once. The nonce lives in RAM, so a knob that restarted in between asks you to sign in again. The browser goes straight back to the knob's page, which shows the sync as it runs.

  5. In the background, the knob trades the code for a refresh token, directly with Google over HTTPS, using the client secret it holds. Then it reads the starred group (the People API's contactGroups/starred), fetches those people's names and numbers in batches of 20 (people:batchGet), and saves the favourites.

The code that passes through the browser is useless without the client secret, which only the knob holds. The knob's page and its Google endpoints are behind the page's login.

The page and its API

The Google Contacts section shows on the Telephone firmware's page only:

Row or control
Accountsigned in or not signed in
Syncthe state, why, and how many minutes ago the last good sync was: ok · 12 favourites from Google · 3 min ago
OAuth client ID, Client secret (with Show)your client's
Sign-in returns through (the client's redirect URI)the return page's address; it must start with https://
Save the clientstores the three fields. An empty secret field keeps the secret already saved
Sign in with Googlegreyed out until a client ID and a secret are saved
Sync nowgreyed out until signed in
Sign outshown only while signed in. It asks first: Sign out of Google? The favourites stay as they are.

The endpoints are all behind the page's login (HTTP basic auth, admin and your password):

Request
GET /api/phone/googlethe state, as JSON (below)
POST /api/phone/googleform fields client_id, client_secret, relay. Quotes, backslashes, spaces and control characters are stripped; a relay must start with https://
GET /api/phone/google/signina 302 to Google's sign-in, or {"ok":false,"why":"set the Google client first"}
GET /api/phone/google/callbackwhere the return page brings code (or error) and state; answers with a 302 to /#phonegoogle
POST /api/phone/google/synca sync, now (once the knob is on WiFi with no call up)
POST /api/phone/google/forgetsigns out: the refresh token is deleted, the favourites stay

GET /api/phone/google returns:

{"client":true,"client_id":"1234-abc.apps.googleusercontent.com","secret":true,
 "signed_in":true,"relay":"https://vfoknob.com/google.html",
 "state":"ok","why":"12 favourites from Google","count":12,"minutes_ago":3}
Field
client, secretwhether a client ID and a secret are saved. The secret itself is never returned
client_id, relayas saved
signed_inthe knob holds a refresh token
stateidle, syncing, ok or failed
whythe message for the state (see below)
countfavourites from the last good sync
minutes_agosince the last good sync; -1 if none since the knob started

The favourites themselves are at GET /api/phone/favourites.

Messages

What the Sync row can say:

StateMessageMeaning, and what to do
idlenot signed inno sign-in yet: press Sign in with Google
idlesigned outafter Sign out
syncingsigned in, reading the contactsback from Google; the first sync is running
syncing(empty)a sync is running
ok12 favourites from Googledone; the list on the page is the new one
failedthe sign-in was cancelledyou chose Cancel at Google, or did not allow access: sign in again
failednot this knob's sign-in: sign in againthe return carried no nonce of this knob's: an old Google tab, a second attempt, or the knob restarted in between. Start again from the page
failedGoogle refused the sign-in, or Google's own words (Unauthorized, Bad Request, ...)the code could not be exchanged: usually a wrong client secret (invalid_client), or a return address that differs from the client's redirect URI. Check both and sign in again
failedGoogle's sign-in expired: sign in againGoogle no longer accepts the token. Either the app is still in Testing (seven days), you removed its access, or the token went unused for six months. The knob has deleted it: publish the app if needed, and sign in again
failedGoogle refused the tokenGoogle refused to renew access for another reason: check the client in Google Cloud
failedGoogle not reachedno answer from Google: check the knob's internet connection
failedthe starred contacts were refusedusually the People API is not enabled in the project (Google answers 403, SERVICE_DISABLED). Enable it, wait a few minutes, press Sync now
failedthe contacts could not be reada list came back incomplete or unreadable; the favourites were left as they were. Press Sync now
failedno memory for the sync just nowthe knob is short of memory for a moment; it tries again by itself half a minute later
failedanother word such as invalid_requestGoogle's error code, passed on as it is

The knob's log, on TCP port 3333, has the details under the tag contacts. For example: signed in to Google, sign-in refused: 401 Unauthorized, 12 favourites from Google, 5 starred contacts, none with a number.

Troubleshooting

What you seeWhy, and the fix
Google: Error 400: redirect_uri_mismatchThe knob's Sign-in returns through differs from the client's Authorized redirect URIs. Make them identical, then sign in again
Google: Access blocked, the app has not completed the Google verification process or is currently being testedThe app is still in Testing, and your account is not a test user. Under Google Auth Platform → Audience, press Publish app
The return page: there is no knob to return toThe knob's page was opened by an address the return page does not accept: a public name, an IPv6 address, or a name in a router's own domain such as .fritz.box. Open the knob's page by its IP address, or by vfo-knob.local, and sign in again
The browser cannot open the knob after GoogleThe browser is not on the knob's network (a phone on mobile data, say), or it is set to HTTPS only. Sign in from a device on the same network, and allow plain http for the knob's address when the browser asks
404 at the return page's addressThe website does not serve google.html there (yet). Host a copy yourself meanwhile, and enter its address in both places (see the return page)
not this knob's sign-in: sign in againStart the sign-in again from the knob's page, and finish it in the same tab
Signed out every weekThe app is in Testing: publish it, then sign in once more
Google refused the sign-in right after GoogleUsually the client secret was mistyped. Paste it again and Save the client. If it is lost, add a new secret to the client in Google Cloud
the starred contacts were refusedEnable the Google People API in the same project as the client, wait a few minutes, press Sync now
0 favourites from GoogleNo starred contact has a phone number. Star some in contacts.google.com, then press Sync now
Favourites added by hand have goneA sync replaces the list. Star those contacts in Google instead, or sign out to keep a list of your own
The client disappeared from Google CloudGoogle deletes OAuth clients unused for six months. A knob that syncs every six hours keeps its client in use, but one left switched off for that long does not. Restore it within 30 days under Deleted credentials, or make a new one and sign in again

Limits worth knowing

  • Starred group: up to 200 members are read; up to 40 favourites are kept.
  • Batches: names and numbers are fetched 20 people at a time. A reply too long to read in full fails the sync, and the old list stays.
  • Google's limits:
    • 100 live refresh tokens per Google account per client. Signing in a 101st time quietly retires the oldest.
    • An unverified app may serve 100 users in total.
    • Neither matters for one owner's knobs.