diff --git a/docs/dev/reference/changelog.md b/docs/dev/reference/changelog.md index f07d764588..960502d859 100644 --- a/docs/dev/reference/changelog.md +++ b/docs/dev/reference/changelog.md @@ -58,6 +58,13 @@ You can now use folders on machines and in fragments to organize resources. {{% /changelog %}} +{{% changelog color="added" title="Bluetooth provisioning" date="2025-07-28" %}} + +Added support for Bluetooth Low Energy (BLE) provisioning, allowing devices to be set up over Bluetooth connection. +For an example implementation, see the [Flutter Provisioning package](https://github.com/viamrobotics/viam_flutter_provisioning/). + +{{% /changelog %}} + {{% changelog color="added" title="Annual billing support for subscription billing model" date="2025-07-23" %}} You can now configure annual billing alongside monthly billing options for your organizations. diff --git a/docs/manage/fleet/provision/end-user-setup.md b/docs/manage/fleet/provision/end-user-setup.md index c745223537..14c3d50195 100644 --- a/docs/manage/fleet/provision/end-user-setup.md +++ b/docs/manage/fleet/provision/end-user-setup.md @@ -55,15 +55,17 @@ If you have already created a machine, select it. If you have not yet created a machine, click on **Add new smart machine** and give your machine a name. {{% /tablestep %}} {{% tablestep number=3 %}} -**Follow the instructions in the app** +**Turn on your machine and follow the app instructions** Turn on the smart machine you are attempting to connect to. Then leave the app and navigate to your mobile device's WiFi settings and connect to the WiFi hotspot your machine has created. You may need to wait a short time for your machine to boot and create its WiFi hotspot. + Your machine's WiFi hotspot name will begin with `viam-setup-`. Unless you have been given other instructions, the WiFi password for this hotspot network is `viamsetup`. -Once you are connected to your machine's WiFi hotspot return to the Viam mobile app. +Return to the Viam mobile app once connected. + {{% /tablestep %}} {{% tablestep number=4 %}} **Provide the network information for the machine** @@ -72,6 +74,7 @@ In the mobile app, you will be prompted to provide the network information for t The machine will now disable the hotspot network and attempt to connect using the provided network information. If the machine cannot establish a connection using the provided network information, the machine will create the hotspot again and prompt you to re-enter the network information until a connection is successfully established. + {{% /tablestep %}} {{% tablestep number=5 %}} **Wait for machine to complete setup** @@ -135,6 +138,62 @@ Note that any features that require internet access will not function if the con {{% /tablestep %}} {{< /table >}} +## Set up your machine using a custom Flutter app + +If you are building your own app to provide provisioning functionality you have three options for provisioning: + + +| Provisioning method | Description | Notes | Package | +| ------------------- | ----------- | ----- | ------- | +| **Bluetooth with WiFi** | Ask the user to connect to the machine over Bluetooth. The user then provides network credentials for an internet-connected WiFi network, through which machine setup can then occur. | Recommended, if available. | [Example](https://github.com/viamrobotics/viam_flutter_provisioning/) | +| **WiFi** | Ask the user to connect to the machine's temporary WiFi network. The user then provides network credentials for an internet-connected WiFi network, through which machine setup can then occur. | Slower than Bluetooth with WiFi but faster than Bluetooth tethering. | [Example](https://github.com/viamrobotics/viam_flutter_hotspot_provisioning_widget) | + | **Bluetooth tethering** | Ask the user to connect to the machine over Bluetooth. The user shares their mobile device's internet with the machine over Bluetooth. | Slowest | [Example](https://github.com/viamrobotics/viam_flutter_bluetooth_provisioning_widget/) | + +You can support any number of these options. + +## Troubleshooting + +### Bluetooth connection issues + +If you're having trouble with Bluetooth provisioning: + +1. **Check Bluetooth permissions**: Ensure the app used for provisioning has [Bluetooth permissions enabled](https://github.com/viamrobotics/viam_flutter_bluetooth_provisioning_widget?tab=readme-ov-file#platform-requirements) on your device. + +1. **Verify Bluetooth is enabled**: Make sure Bluetooth is turned on in your mobile device settings. + +1. **Restart Bluetooth**: Try turning Bluetooth off and on again on your mobile device. + +1. **Remove and re-add**: If you've already connected to the machine over Bluetooth, remove or forget the device from your mobile device's settings, and re-add it. + +If you can open a terminal on the machine: + +1. Check if Bluetooth is available: + + ```sh {class="command-line" data-prompt="$"} + bluetoothctl list + ``` + +1. Restart Bluetooth service: + + ```sh {class="command-line" data-prompt="$"} + sudo systemctl restart bluetooth + ``` + +1. **Configuration check**: If you set up provisioning, verify that `disable_bt_provisioning` is set to `false` in your configuration. + +### WiFi connection issues + +If you cannot connect to your machine's temporary WiFi hotspot, confirm you are using the correct password. +The default password, unless changed by the manufacturer, is `viamsetup`, + +If your machine cannot connect to your permanent office or home WiFi network: + +1. **Check network credentials**: Verify that the WiFi network name (SSID) and password are correct. + +1. **Check network compatibility**: Ensure your WiFi network is compatible with your machine's WiFi band frequency (2.4ghz vs 5ghz). + +1. **Try a different network**: If possible, try connecting to a different WiFi network to isolate the issue. + ## Next Steps You can now use your machine. diff --git a/docs/manage/fleet/provision/setup.md b/docs/manage/fleet/provision/setup.md index 6b048fb947..744998e418 100644 --- a/docs/manage/fleet/provision/setup.md +++ b/docs/manage/fleet/provision/setup.md @@ -34,10 +34,10 @@ To parse the readings and provide tailored guidance to a ship's captain, the com By having the end customer set up the machine, the company: - eliminates per-device setup and individualization at the factory -- allows for tailored configurations per customer as needed - allows customer to provide their own WiFi credentials +- allows for tailored configurations per customer as needed -This guide will show you how to install and configure `viam-agent`. +This guide shows you how to install and configure `viam-agent`. ## Prerequisites @@ -60,9 +60,12 @@ For Bullseye, the installation of `viam-agent` changes the network configuration {{< /alert >}} -## Decide on the provisioning method +## Choose provisioning methods + +You can let your end users complete machine setup over WiFi or Bluetooth: -You can choose to let your end users complete machine setup by using a {{< glossary_tooltip term_id="captive-web-portal" text="captive web portal" >}} or a mobile app. +- **WiFi Hotspot Provisioning**: When the device boots, it creates a temporary WiFi hotspot that users connect to for setup either by using a {{< glossary_tooltip term_id="captive-web-portal" text="captive web portal" >}} or a mobile app. The WiFi hotspot is open to anyone. Be aware that if the manufacturer (and/or end user) has not set a custom password for the machine then a default password (either the built-in "viamsetup", or a new default set by a manufacturer in viam-defaults.json) may be in use, which is a security risk. +- **Bluetooth Low Energy (BLE) Provisioning**: When device boots, it enables Bluetooth with the configured `-` as the device name and a user connects to it using a mobile app. If you choose to have a mobile app experience, you can use the [Viam mobile app](/manage/troubleshoot/teleoperate/default-interface/#viam-mobile-app) or create your own custom mobile app using the [Flutter SDK](https://flutter.viam.dev/viam_protos.provisioning.provisioning/ProvisioningServiceClient-class.html) or the [TypeScript SDK](https://github.com/viamrobotics/viam-typescript-sdk/blob/main/src/app/provisioning-client.ts) to connect to `viam-agent` and provision your machines. @@ -74,100 +77,10 @@ If you do not yet have a fragment, follow the steps to [Create a configuration f If you are not using Flutter or TypeScript and would like to use provisioning, please [contact us](mailto:support@viam.com). {{< /alert >}} -If you choose to use the captive web portal, you can optionally create a machine in advance and provide its machine cloud credentials file at /etc/viam.json. - -You can get the machine cloud credentials by clicking the copy icon next to **Machine cloud credentials** in the part status dropdown to the right of your machine's name on the top of the page. - -{{}} - -{{% expand "Want to create a machine and obtain its machine cloud credentials programmatically?" %}} - -You can use the [Fleet Management API](/dev/reference/apis/fleet/) to create machines, and obtain their machine cloud credentials: - -```python {class="line-numbers linkable-line-numbers"} -import asyncio -import requests - -from viam.rpc.dial import DialOptions, Credentials -from viam.app.viam_client import ViamClient -from viam.app.app_client import APIKeyAuthorization - -# Replace "" (including brackets) with your API key -API_KEY = "" -# Replace "" (including brackets) with your API key ID -API_KEY_ID = "" -# The id of the location to create the machine in -LOCATION_ID = "" -# The name for the machine to create -MACHINE_NAME = "" - - -async def connect() -> ViamClient: - dial_options = DialOptions( - credentials=Credentials( - type="api-key", - payload=API_KEY, - ), - auth_entity=API_KEY_ID - ) - return await ViamClient.create_from_dial_options(dial_options) - - -async def main(): - - # Make a ViamClient - viam_client = await connect() - # Instantiate an AppClient called "cloud" - # to run fleet management API methods on - cloud = viam_client.app_client - new_machine_id = await cloud.new_robot( - name=MACHINE_NAME, location_id=LOCATION_ID) - print("Machine created: " + new_machine_id) - list_of_parts = await cloud.get_robot_parts( - robot_id=new_machine_id) - print("Part id: " + list_of_parts[0].id) - - org_list = await cloud.list_organizations() - print(org_list[0].id) - - auth = APIKeyAuthorization( - role="owner", - resource_type="robot", - resource_id=new_machine_id - ) - api_key, api_key_id = await cloud.create_key( - org_list[0].id, [auth], "test_provisioning_key") - print(api_key, api_key_id) - - headers = { - 'key_id': api_key_id, - 'key': api_key - } - params = { - "client": 'true', - "id": list_of_parts[0].id - } - res = requests.get( - 'https://app.viam.com/api/json1/config', - params=params, - headers=headers, - timeout=10 - ) - print(res.text) - - with open("viam.json", "w") as text_file: - text_file.write(res.text) - - viam_client.close() - -if __name__ == '__main__': - asyncio.run(main()) -``` - -{{% /expand%}} - ## Configure defaults +The defaults file allows you to configure the provisioning experience for the users setting up their machines. + {{< table >}} {{% tablestep number=1 %}} @@ -191,6 +104,9 @@ Create a defaults file called viam-defaults.json with the following "hotspot_prefix": "", # machine creates a hotspot during setup "disable_captive_portal_redirect": false, # set to true if using a mobile app "hotspot_password": "", # password for the hotspot + "disable_bt_provisioning": false, # set to true to disable Bluetooth provisioning + "disable_wifi_provisioning": false, # set to true to disable WiFi hotspot provisioning + "bluetooth_trust_all": false, # set to true to accept all Bluetooth pairing requests (which is only needed for bluetooth tethering) without requiring an unlock command from a mobile app. "turn_on_hotspot_if_wifi_has_no_internet": false, "offline_before_starting_hotspot_minutes": "3m30s", "user_idle_minutes": "2m30s", @@ -212,6 +128,9 @@ Create a defaults file called viam-defaults.json with the following "hotspot_prefix": "skywalker-setup", "disable_captive_portal_redirect": false, "hotspot_password": "skywalker123", + "disable_bt_provisioning": false, + "disable_wifi_provisioning": false, + "bluetooth_trust_all": false, "turn_on_hotspot_if_wifi_has_no_internet": false, "offline_before_starting_hotspot_minutes": "3m30s", "user_idle_minutes": "2m30s", @@ -235,11 +154,13 @@ It also configures timeouts to control how long `viam-agent` waits for a valid l | `model` | string | Optional | Purely informative. May be displayed on captive portal or provisioning app. Default: `"custom"`. | | `fragment_id` | string | Optional | The `fragment_id` of the fragment to configure machines with. Required when using the Viam mobile app for provisioning. The Viam mobile app uses the fragment to configure the machine. | | `hotspot_interface` | string | Optional | The interface to use for hotspot/provisioning/wifi management. Example: `"wlan0"`. Default: first discovered 802.11 device. | -| `hotspot_prefix` | string | Optional | `viam-agent` will prepend this to the hostname of the device and use the resulting string for the provisioning hotspot SSID. Default: `"viam-setup"`. | +| `hotspot_prefix` | string | Optional | `viam-agent` will prepend this to the hostname of the device and use the resulting string for the provisioning hotspot SSID. For Bluetooth provisioning the device name will be the hotspot prefix and the model (`-`). Default: `"viam-setup"`. | | `hotspot_password` | string | Optional | The Wifi password for the provisioning hotspot. Default: `"viamsetup"`. | | `disable_captive_portal_redirect` | boolean | Optional | By default, all DNS lookups are redirected to the "sign in" portal, which can cause mobile devices to automatically display the portal. When set to true, only DNS requests for domains ending in .setup, like `viam.setup` are redirected, preventing the portal from appearing unexpectedly, especially convenient when using a mobile app for provisioning. Default: `false`. | -| `turn_on_hotspot_if_wifi_has_no_internet` | boolean | Optional | When enabled, Wi-Fi connections without Internet access are considered offline. After `offline_before_starting_hotspot_minutes` minutes offline, your device will begin broadcasting a hotspot. This setting must be enabled for your device to attempt connecting to `additional_networks`. Default: `false`. | -| `offline_before_starting_hotspot_minutes` | integer | Optional | Amount of time the device will spend offline, in minutes, before the machine begins broadcasting a wireless hotspot. Default: `2`. | +| `disable_bt_provisioning` | boolean | Optional | When set to true, disables Bluetooth provisioning. The machine will not advertise Bluetooth services for provisioning. Default: `false`. | +| `disable_wifi_provisioning` | boolean | Optional | When set to true, disables WiFi hotspot provisioning. The machine will not create a WiFi hotspot for provisioning. Default: `false`. | +| `turn_on_hotspot_if_wifi_has_no_internet` | boolean | Optional | By default, the device connects to a single prioritized WiFi network (provided during provisioning) and is considered online even if the global internet is not reachable. When `turn_on_hotspot_if_wifi_has_no_internet` is true and the primary network lacks internet connectivity, the device will try all configured networks and only mark itself as online if it successfully connects to the internet. Default: `false`. | +| `offline_before_starting_hotspot_minutes` | integer | Optional | Will only enter provisioning mode (hotspot) after being disconnected longer than this time. It may be useful to increase this on flaky connections, or when part of a system where the device may start quickly, but the wifi/router may take longer to be available. Default: `2` (2 minutes). | | `user_idle_minutes` | integer | Optional | Amount of time before considering a user (using the captive web portal or provisioning app) idle, and resuming normal behavior. Used to avoid interrupting provisioning mode (for example for network tests/retries) when a user might be busy entering details. Default: `5` (5 minutes). | | `retry_connection_timeout_minutes` | integer | Optional | Provisioning mode will exit after this time, to allow other unmanaged (for example wired) or manually configured connections to be tried. Provisioning mode will restart if the connection/online status doesn't change. Default: `10` (10 minutes). | | `wifi_power_save` | boolean | Optional | Boolean, which, if set, will explicitly enable or disable power save for all WiFi connections managed by NetworkManager. If not set, the system default applies. Default: `NULL`. | @@ -258,7 +179,7 @@ If you know in advance which other networks a machine should be able to connect If that is not possible, you can add networks with the `additional_networks` field. `viam-agent` will then try to connect to each specified network in order of `priority` from highest to lowest. -The following configuration defines the connection information and credentials for two WiFi networks named `otherNetworkOne` and `otherNetworkTwo`: +The following configuration defines the connection information and credentials for two WiFi networks named `fallbackNetOne` and `fallbackNetTwo`: ```json {class="line-numbers linkable-line-numbers"} { @@ -270,6 +191,9 @@ The following configuration defines the connection information and credentials f "hotspot_prefix": "skywalker-setup", "disable_captive_portal_redirect": false, "hotspot_password": "skywalker123", + "disable_bt_provisioning": false, + "disable_wifi_provisioning": false, + "turn_on_hotspot_if_wifi_has_no_internet": false, "offline_before_starting_hotspot_minutes": "3m30s", "user_idle_minutes": "2m30s", "retry_connection_timeout_minutes": "15m", @@ -279,13 +203,13 @@ The following configuration defines the connection information and credentials f "testNet1": { "priority": 30, "psk": "myFirstPassword", - "ssid": "otherNetworkOne", + "ssid": "fallbackNetOne", "type": "wifi" }, "testNet2": { "priority": 10, "psk": "mySecondPassword", - "ssid": "otherNetworkTwo", + "ssid": "fallbackNetTwo", "type": "wifi" } } @@ -297,12 +221,107 @@ The following configuration defines the connection information and credentials f | ---------- | ------ | ----------- | | `type` | string | The type of the network. Options: `"wifi"`| | `ssid` | string | The network's SSID. | -| `psk` | string | The network password. | +| `psk` | string | The network password/pre-shared key. | | `priority` | int | Priority to choose the network with. Values between -999 and 999. Default: `0`. | {{% /tablestep %}} {{< /table >}} +## (Optional) Create a machine in advance + +If you provision devices using a captive web portal (instead of a mobile app), you need to create a machine in advance and provide its machine cloud credentials in the portal. +Alternately, you may directly write them to the file /etc/viam.json. + +You can get the machine cloud credentials by clicking the copy icon next to **Machine cloud credentials** in the part status dropdown to the right of your machine's name on the top of the page. + +{{}} + +{{% expand "Want to create a machine and obtain its machine cloud credentials programmatically?" %}} + +You can use the [Fleet Management API](/dev/reference/apis/fleet/) to create machines, and obtain their machine cloud credentials: + +```python {class="line-numbers linkable-line-numbers"} +import asyncio +import requests + +from viam.rpc.dial import DialOptions, Credentials +from viam.app.viam_client import ViamClient +from viam.app.app_client import APIKeyAuthorization + +# Replace "" (including brackets) with your API key +API_KEY = "" +# Replace "" (including brackets) with your API key ID +API_KEY_ID = "" +# The id of the location to create the machine in +LOCATION_ID = "" +# The name for the machine to create +MACHINE_NAME = "" + + +async def connect() -> ViamClient: + dial_options = DialOptions( + credentials=Credentials( + type="api-key", + payload=API_KEY, + ), + auth_entity=API_KEY_ID + ) + return await ViamClient.create_from_dial_options(dial_options) + + +async def main(): + + # Make a ViamClient + viam_client = await connect() + # Instantiate an AppClient called "cloud" + # to run fleet management API methods on + cloud = viam_client.app_client + new_machine_id = await cloud.new_robot( + name=MACHINE_NAME, location_id=LOCATION_ID) + print("Machine created: " + new_machine_id) + list_of_parts = await cloud.get_robot_parts( + robot_id=new_machine_id) + print("Part id: " + list_of_parts[0].id) + + org_list = await cloud.list_organizations() + print(org_list[0].id) + + auth = APIKeyAuthorization( + role="owner", + resource_type="robot", + resource_id=new_machine_id + ) + api_key, api_key_id = await cloud.create_key( + org_list[0].id, [auth], "test_provisioning_key") + print(api_key, api_key_id) + + headers = { + 'key_id': api_key_id, + 'key': api_key + } + params = { + "client": 'true', + "id": list_of_parts[0].id + } + res = requests.get( + 'https://app.viam.com/api/json1/config', + params=params, + headers=headers, + timeout=10 + ) + print(res.text) + + with open("viam.json", "w") as text_file: + text_file.write(res.text) + + viam_client.close() + +if __name__ == '__main__': + asyncio.run(main()) +``` + +{{% /expand%}} + ## Install `viam-agent` `viam-agent` is a self-updating service manager that maintains the lifecycle for several Viam services and keeps them updated. @@ -488,7 +507,7 @@ The following steps show you the end user experience using the mobile app or the For a guide you can give to end users for setting up their machine, see [Setup machine](/manage/fleet/provision/end-user-setup/). {{< tabs >}} -{{% tab name="Mobile app" min-height="703px" %}} +{{% tab name="Mobile app (WiFi)" min-height="703px" %}} {{