Skip to content
Visor Developers

Wi-Fi Direct

Wi-Fi is the second link to the glasses, next to Bluetooth. Either the phone owns a Wi-Fi Direct group and the glasses join it, or the glasses host a network of their own and the phone joins it. The same session then runs over TCP. Bulk media and live video use it, because Bluetooth is too slow for either.

Why Wi-Fi

Bluetooth carries about 120 to 165 KB/s of media. Over Wi-Fi Direct the same media moves about 10 MB/s. A 4 Mbit/s HEVC stream does not fit in the Bluetooth figure of about 1 Mbit/s, which is unconfirmed. See Throughput.

Service 10 messages

Service 10 is ATC LinkManager2. It moves the glasses onto a network. It needs a scoped link, which the session gets at EndLinkSetup. The join carries a v4 UUID of its own as well. See Link setup and AirShield.

Type Name Direction Use
0x1000 CreateSoftApNetwork phone to glasses The glasses host a network. See The glasses' own network
0x1001 SoftApNetworkInfo glasses to phone Status, address, credentials and port
0x1002 DestroySoftApNetwork phone to glasses Closes the glasses' network, empty body
0x1004 SetLinkMessage not known Field 1 picks the carrier: L2CAP or TCP
0x1010 JoinAccessPoint phone to glasses Puts the glasses on the phone's group
0x1011 JoinAccessPointResult glasses to phone Status, address and port
0x1012 LeaveAccessPoint phone to glasses Sent before the group goes, empty body
0x1015 SwitchLink both {1 target}: moves only the receiving side. See Switching links without a reset
0x101c SetWifiProperties phone to glasses See SetWifiProperties
0x101d SetWifiPropertiesResult glasses to phone Status, 0 is OK
0x1020 SwitchTransportLink both {1 target}: moves the session to the named link. See Switching links without a reset

JoinWifiDirectGroup (0x100b) fails with status 3 (JOIN_FAILED). The phone uses JoinAccessPoint instead.

LinkHealthUpdate (0x100a) holds 1 a UUID, 2 the RSSI and 3 an event: LOW_BATTERY or THERMAL_THROTTLE.

The other types, by name:

Type Name
0x1006 WIFI_DIRECT_NETWORK_INFO
0x1007, 0x1008 DESTROY_WIFI_DIRECT_NETWORK, and its result
0x1009 SetSessionInfo
0x100b to 0x100e JOIN_WIFI_DIRECT_GROUP, LEAVE_WIFI_DIRECT_GROUP, each with a result
0x1016 END_WIFI_SESSION
0x1017 to 0x1019 SOFT_AP_CLIENT_CONNECTED, SOFT_AP_CLIENT_DISCONNECTED, SOFT_AP_CLIENT_ADDRESS_UPDATED
0x101a, 0x101b, 0x101e MANIFEST_FILE_TRANSFER_DATA, _DONE, _COMPLETE
0x101f SET_MANIFEST_AUTHORITY_KEY
0x1021 CREATE_WIFI_DIRECT_NETWORK_RESULT
0x1022, 0x1023 SET_BTC_CONNECTION_MODE, and its result
0x1024 to 0x1031 NAN and Wi-Fi Aware
0x2000 ENABLE_EC_AUTH

No service 10 message sets a power, latency or throughput mode.

An unknown type gets answer 0xd001. A known type with a body that will not parse gets 0xd010.

SetWifiProperties

Field Content
1 Country code
2 Enable DHCP NACK
3 Accept a SoftAP-assigned IP
4 Enable MCS 10 and 11
5 Enable management retry
6 Enable wakelock timeout

The glasses answer with SetWifiPropertiesResult (0x101d). Sent with only a country code, it does not change the bursts in which the glasses send live video. See Frame timing.

JoinAccessPoint fields

The phone sends the join over the Bluetooth session.

Field Type Content Rule
1 string SSID The group name the phone chose
2 string Passphrase 20 characters
3 bytes, 16 UUID Must be version 4: byte 6 high nibble 0x4, byte 8 high bits 10
4 varint Bandwidth 0 unspecified, 1 20 MHz, 2 40 MHz, 3 80 MHz

A join without a valid v4 UUID gets status 2 and never reaches the radio. The rule is the same one EndLinkSetup needs.

Field 4 is sent but has no effect on a join. The glasses use the bandwidth only for a SoftAP of their own. A join that asked for 20 MHz still brought the group up at 80 MHz.

JoinAccessPointResult fields

Field Type Content Notes
1 varint Status 1 SUCCESS, 2 INTERNAL_ERROR, 3 JOIN_FAILED, 4 UNAVAILABLE, 5 BAD_INPUT, 6 TIMED_OUT, 7 CANCELLED, 8 BUSY, 9 WIFI_NOT_ENABLED, 10 THROTTLED_THERMAL, 11 THROTTLED_LOW_BATTERY
2 message, repeated Addresses Each NetworkAddress holds the address as text in field 3
3 varint Port Chosen by the glasses for each join

A usable result has status 1, an address and a port above zero.

After a Wi-Fi firmware restart the join answers INTERNAL_ERROR for 15 to 45 s. See The 80 MHz firmware hang.

Who hosts the network

There are two ways:

  • The phone owns a Wi-Fi Direct group and the glasses join it with JoinAccessPoint. The phone's group-owner address is usually 192.168.49.1. The glasses report their own address in the join result.
  • The glasses host a network of their own with CreateSoftApNetwork, and the phone joins it as a client.

The glasses' own network

The phone sends CreateSoftApNetwork (0x1000) over the Bluetooth session:

Field Type Content
3 varint Band, 1 for 2.4 GHz
4 varint Channel, such as 6
6 string Country code
7 bytes, 16 UUID, version 4 as for the join
8 message Credentials: 1 SSID, 2 password
9 varint Bandwidth, 1 for 20 MHz

The glasses answer with SoftApNetworkInfo (0x1001):

Field Type Content
1 varint Status: 1 SUCCESS, 2 INTERNAL_ERROR, 3 START_AP_FAILED, 4 GET_IP_FAILED
2 message, repeated Addresses. Each NetworkAddress holds the address as text in field 3, which can end in a prefix length after a slash
3 message Credentials: 1 SSID, 2 password
4 varint Port. Absent means 20203

This is the one mode where the glasses keep the bandwidth they are asked for. A 2.4 GHz network at 20 MHz carries 1080p at 30 fps.

The network has no internet. The phone joins it as a client, opens TCP to the address and port, and runs the normal session. A phone that cannot use two Wi-Fi channels at once leaves its own network to join. On the phone's own group, such a phone takes turns between the two channels instead, and a moving pair can lose the link.

DestroySoftApNetwork (0x1002) closes the network. The glasses also close it on their own soon after the session ends.

Switch from Bluetooth to Wi-Fi

The phone starts the group, asks for the join over Bluetooth, connects TCP, and only then pauses Bluetooth. This opens a new session on TCP, and the glasses reset every DataX channel. To keep the channels, switch links instead: see Switching links without a reset.

sequenceDiagram
    participant Phone
    participant Glasses
    Note over Phone: group up with SSID and passphrase
    Phone->>Glasses: 0x1010 JoinAccessPoint over Bluetooth
    Glasses-->>Phone: 0x1011 status 1, address, port
    Phone->>Glasses: TCP connect to address and port
    Phone->>Glasses: AirShield handshake, trust, EndLinkSetup
    Glasses-->>Phone: EndLinkSetup, services ready
    Note over Phone: Bluetooth session paused
    Note over Glasses: leaves Bluetooth once on Wi-Fi

The steps in order:

  1. The phone starts the group with its SSID and passphrase.
  2. The phone sends JoinAccessPoint over Bluetooth. Once the glasses are on Wi-Fi they leave Bluetooth, and a later request on Bluetooth never arrives.
  3. The glasses answer with JoinAccessPointResult. A usable result has status 1, an address and a port.
  4. The phone opens TCP to the address and port. The socket carries raw SDUs with no length prefix, framed as on RFCOMM. See Transport and framing.
  5. The phone runs the normal session over TCP: AirShield, trust, EndLinkSetup. The glasses answer with EndLinkSetup and services ready.
  6. Only then does the phone pause the Bluetooth session.

On the glasses' own network, steps 1 to 3 are CreateSoftApNetwork, SoftApNetworkInfo and the phone's join.

Going back to Bluetooth:

  1. The phone sends LeaveAccessPoint over Wi-Fi, before the group goes. On the glasses' network it sends DestroySoftApNetwork instead.
  2. The phone stops the TCP session and removes the group, or leaves the glasses' network.
  3. The phone resumes the Bluetooth session.

A session can run on two links at once and move its traffic from one to the other: Bluetooth to Wi-Fi and back. Every DataX channel and every stream stays open, so a motion stream or a download carries on across the move. This is how Meta's app moves to Wi-Fi for a lease.

sequenceDiagram
    participant Phone
    participant Glasses
    Phone->>Glasses: 0x1010 JoinAccessPoint over Bluetooth
    Glasses-->>Phone: 0x1011 status 1, address, port
    Phone->>Glasses: TCP: AirShield handshake and trust
    Phone->>Glasses: TCP: EndLinkSetup, state READY
    Glasses-->>Phone: TCP: EndLinkSetup, not answered
    Phone->>Glasses: Bluetooth: 0x1020 SwitchTransportLink on a new channel
    Glasses-->>Phone: Bluetooth: 0x1020 SwitchTransportLink
    Note over Phone,Glasses: traffic on TCP from now on, Bluetooth kept
  1. The glasses join the network as for any Wi-Fi job, and the phone connects TCP. The Bluetooth session stays up.
  2. On the TCP link the phone runs the AirShield handshake and trust as usual, then sends EndLinkSetup with state 0, READY. The glasses answer with their own EndLinkSetup. The phone does not acknowledge it.
  3. The glasses add the TCP link to the phone's session as a switchable link. They file it under the link UUIDs of the two EndLinkSetups, field 3. See Link setup and AirShield.
  4. On the Bluetooth link the phone sends SwitchTransportLink, type 0x1020, {1 target}: its own link UUID for the TCP link. It goes on a new svc10 channel, and then the phone sends nothing more.
  5. The glasses move their receiving side to the TCP link, flush the Bluetooth one, answer with a SwitchTransportLink on Bluetooth that names their link UUID, and send on TCP from then on.
  6. Once their switch arrives, the phone reads TCP and sends on TCP.

The way back is the same switch, named with the phone's link UUID for the Bluetooth link and sent on TCP. The phone then closes TCP. Both moves take about a second at most.

After the switch the TCP link is the glasses' main link. When it goes, for example when the glasses leave the network, they close the Bluetooth link too ("disconnecting inactive links") and the companion is disconnected: nothing falls back. A TCP socket to glasses that left the network hears no close, so the phone watches for it itself: Bluetooth failing while on TCP, or no frame for some seconds while it asks something the glasses always answer, such as GET_MANIFEST_VERSION. It then opens the session again over Bluetooth, which takes about 2 s.

SwitchLink, type 0x1015, moves only the glasses' receiving side, and the glasses answer it with nothing. A SwitchLink that names a link they do not know makes them create an empty placeholder and receive from it, which cuts the session off.

One session at a time

The glasses hold one AirShield session per peer across all their links. A new session, one whose EndLinkSetup says MAIN, closes the old one. A link whose EndLinkSetup says READY joins the session instead, as above.

An RFCOMM session that reaches READY while an LE session is ready closes the LE session within 50 ms. Each retry repeats this. A Wi-Fi session closes the LE session. An LE reconnect then closes the Wi-Fi session in the middle of a transfer.

So the phone pauses the Bluetooth session for the whole Wi-Fi job. The LE GATT link stays up. Only the session is paused.

A Live AI session is the exception. It runs over the Bluetooth session and does not pause it. See Live stream.

Throughput

Path Rate
Media over Wi-Fi Direct, 1.1 MB file 10,036 KB/s
Media over Wi-Fi Direct, 20.8 MB video 10,480 KB/s, about 2 s
Same media over Bluetooth Classic 120 to 165 KB/s
1080p at 4 Mbit/s HEVC on a 2.4 GHz group Runs at 30 fps
1080p HEVC on the glasses' own network Runs at 30 fps
Bluetooth link for video About 1 Mbit/s, unconfirmed

The rate does not fall as files get bigger. The 20 MB transfer ran slightly faster per byte than the 1 MB one.

The 80 MHz firmware hang

On 5 GHz the video stream stops every time. The fault is in the glasses' Wi-Fi firmware.

  • The group follows the phone's own Wi-Fi. On 5180 MHz the group runs at 80 MHz. Each join logs "Configured bandwidth is 80000000".
  • The firmware logs "Received firmware hang event", then RDDM, then a subsystem restart. The phone sees the stream stall at that same moment.
  • On 5 GHz the stream stops 6 to 15 s after it starts, every time.
  • Right after the restart, JoinAccessPoint answers INTERNAL_ERROR for 15 to 45 s. See Rejoining a new group.
  • A bulk transfer at 5 GHz did not hit the hang. The fault is in the video stream, not in throughput alone.
  • On a 2.4 GHz group (20 MHz), 1080p at 4 Mbit/s runs clean.
  • Setting field 4 of the join to 20 MHz does not prevent the hang.

Rejoining a new group

A new group from the phone keeps the same SSID and the phone's BSSID. The glasses then retry the channel they remember for that network, while the new group may sit on another one. Their join fails ("num_bss 0") for 22 to 35 s, until a full scan finds the group. JoinAccessPoint has no frequency field, so the phone keeps each new group on the frequency of the last one. JoinWifiDirectGroup does have one, field 7.

Reference code: Wifi.kt

Open questions

  • Unconfirmed: whether 5 GHz at 20 or 40 MHz avoids the hang.
  • Unconfirmed: whether a long bulk transfer at 5 GHz can hit the hang.
  • Unconfirmed: whether LeaveAccessPoint is needed for the next join to succeed.
  • Unconfirmed: whether the INTERNAL_ERROR after a firmware restart is the same channel retry as a rejoin, or a second cause.
  • The effect of SetSessionInfo (0x1009) on a join is unknown. The phone does not send it.
  • Unconfirmed: whether the group-owner address is 192.168.49.1 on every phone.
  • Whether the glasses move the session to a ready Wi-Fi link by themselves, for example when Bluetooth weakens.

To use this from an app, without the protocol: Camera and captures.