JSON-RPC
The VDK speaks JSON-RPC 2.0, so any language can use it, through two doors: Visor's adb socket, described here, and the network door, a WebSocket with the same messages.
Reaching the socket
Visor listens on the abstract Unix socket visor-kit while Developer mode is on, and lets in only adb's shell
user. Forward a port to it:
adb forward tcp:7333 localabstract:visor-kit
Then connect to 127.0.0.1:7333. Remove the forward when done: adb forward --remove tcp:7333. If nothing
answers and the connection closes at once, Visor is not running or its Developer mode is off. Developer mode opens
only this socket: apps never need it.
The shell holds every scope, and also has the shell.* methods: shell.apps, shell.grant, shell.revoke,
shell.simulate, shell.inject, and the topic shell.trace.
Framing
One JSON object per line, UTF-8, ending in \n, both ways. Requests carry an id you choose; answers carry the
same id. Several requests can be in flight at once.
-> {"jsonrpc":"2.0","id":1,"method":"kit.hello","params":{"protocol":1,"kit":"my-script 1.0"}}
<- {"jsonrpc":"2.0","id":1,"result":{"protocol":1,"visor":"2.4.0","granted":["state","camera","band","display"],"methods":[...],"topics":[...]}}
-> {"jsonrpc":"2.0","id":2,"method":"glasses.status"}
<- {"jsonrpc":"2.0","id":2,"result":{"paired":true,"link":"connected","battery":82,"worn":true}}
Say kit.hello first. Params are by name, as an object; a method without params takes {} or none.
Errors
<- {"jsonrpc":"2.0","id":3,"error":{"code":-32000,"message":"The glasses are not connected.","data":{"code":"not_connected","message":"The glasses are not connected."}}}
error.data is the VDK's Failure, with code, message, and sometimes scope and reason; see
Errors. error.code is -32601 for an unknown method, -32602 for params that do not fit,
-32700 for a line that is not JSON-RPC, and -32000 for every other failure.
Streams
JSON-RPC has no streams, so the VDK adds three methods and two notifications:
| Name | Params | Answer | |
|---|---|---|---|
| Method | kit.subscribe |
topic, params (optional) |
{"subscription": ID}, where ID is the request's own id |
| Method | kit.unsubscribe |
subscription |
{} |
| Method | kit.cancel |
id of a running call |
{} |
| Notification | kit.event |
subscription, event |
|
| Notification | kit.ended |
subscription, failure (absent when the stream simply ended) |
-> {"jsonrpc":"2.0","id":4,"method":"kit.subscribe","params":{"topic":"band.gestures"}}
<- {"jsonrpc":"2.0","id":4,"result":{"subscription":4}}
<- {"jsonrpc":"2.0","method":"kit.event","params":{"subscription":4,"event":{"finger":"index","action":"tap","derived":"tap","time":1793111111111}}}
The answer to kit.subscribe always comes before the subscription's first event.
Feeds
A feed, such as camera.live, streams binary records. Over JSON-RPC it is subscribed with kit.subscribe like a
topic, and each event is a record: its header, and its payload in Base64.
-> {"jsonrpc":"2.0","id":5,"method":"kit.subscribe","params":{"topic":"camera.live","params":{"quality":"low"}}}
<- {"jsonrpc":"2.0","id":5,"result":{"subscription":5}}
<- {"jsonrpc":"2.0","method":"kit.event","params":{"subscription":5,"event":{"header":{"kind":"video","codec":"video/hevc","ptsUs":0,"config":true,"width":360,"height":640},"payload":"AAAAAUAB..."}}}
A keyframe in Base64 runs to hundreds of kilobytes at full_hd: allow lines that long. The Python and TypeScript clients have
feed, which decodes the payload.
The contract
protocol/api/openrpc.json is an OpenRPC document of
every method, its params and its answer, with every type under components.schemas. The VDK's own additions:
x-scope: the scope a method or topic needs.x-since: the protocol version that added it.x-shell: answered only to the adb shell.x-topics: the topics, each with itsparamsand itsevent.x-feeds: the feeds, each with itsparamsand its record'sheader.
Generate a client from it, or read it as the reference. It is written by the build from the Kotlin contract, so it is always exact.
A client in Python
import json, socket, subprocess
subprocess.run(["adb", "forward", "tcp:7333", "localabstract:visor-kit"], check=True)
sock = socket.create_connection(("127.0.0.1", 7333))
lines = sock.makefile("r", encoding="utf-8")
def call(id, method, params=None):
sock.sendall((json.dumps({"jsonrpc": "2.0", "id": id, "method": method, "params": params or {}}) + "\n").encode())
while True:
message = json.loads(lines.readline())
if message.get("id") == id:
return message
call(1, "kit.hello", {"protocol": 1, "kit": "python-example"})
print(call(2, "glasses.status")["result"])
call(3, "kit.subscribe", {"topic": "band.gestures"})
for line in lines:
message = json.loads(line)
if message.get("method") == "kit.event":
print(message["params"]["event"])