Giter Club home page Giter Club logo

python-wifi-connect's Introduction

Description

An API for controlling Wi-Fi connections on Balena devices.

It does not contain a user interface, instead it provides API endpoints to send requests to interact with the device. Any user interface of your choice can be built to interact with the API. If you develop a user interface that is open source, please do let me know so I can provide people links.

Example user interface

Maintained on a separate repo: https://github.com/balena-labs-research/starter-Interface. Screenshot 2021-11-10 at 19 24 28

Get started

On launch, the app will detect if you already have a Wi-Fi connection. If you do, it will sleep and wait for a command. If you don’t, it will launch a hotspot and wait for a connection from you. Once connected, you can take further actions using the endpoints listed below.

By default, the Wi-Fi SSID is: Python Wi-Fi Connect

You can set your own default Wi-Fi SSID and a Wi-Fi password for your hotspot using the environment variables in the docker-compose.yml file.

Enjoy and please do feel free to feedback experiences and issues.

Automatic connections

You can specify a Wi-Fi connection you would like your device to try and connect to the first time it loads by using the environment variables in the docker-compose.yml file. Once this connection is established, the device will stay connected after reboots until you use the forget endpoint. If the network is not available, the hotspot will start instead.

PWC_AC_SSID: "network-name" # The SSID of the network you would like to try and auto-connect.
PWC_AC_USERNAME: "username" # Optional, for enterprise networks
PWC_AC_PASSWORD: "your-password" # Optional, the password associated with the Wi-Fi network. Must be 8 characters or more.

Securing the API

By default, the API is exposed so your user interface can interact directly. In other words, anyone can go to http://your-device:9090/v1/connect to send commands to your device.

If you would prefer to only allow access from your backend, change the PWC_HOST environment variable to 127.0.0.1. Then ensure your backend container is connected to the host network so it matches the API docker-compose.yml file in this repo:

network_mode: "host"

Users will then be unable to access the API http://your-device:9090/v1/connect. Your backend container on the device, however, can reach the API using http://127.0.0.1:9090/v1/connect. This is useful if your user interface has a login process, and you only want users to be able to interact with Wi-Fi after logging in.

Alternatively, if you would rather have your backend use specified ports instead of the host network, you can change the PWC_HOST environment variable to 172.17.0.1 and access the API from http://172.17.0.1:9090/v1/connect. On some devices, the default 172.17.0.1 address can not be guaranteed. You can therefore set PWC_HOST to bridge and it will detect the default Balena Engine bridge on startup and listen on that IP. You wil then need to identify the IP in your other container in order to communicate. The following command in a shell script will get you the IP:

ip route | awk '/default / { print $3 }'

Changing the default network interface

By default, the first available Wi-Fi network interface available will be used. For the vast majority of cases there is only one Wi-Fi network interface and therefore this is no issue. Similarly, if you plug in a Wi-Fi dongle to a device without its own built-in Wi-Fi, the Wi-Fi dongle will be used by default.

If however, you have a device with built in Wi-Fi and a Wi-Fi dongle, you will have a device with two network interfaces. For these instances, or on other occasions where you have a complex network interface setup, you can specify which network interface you would like Python Wi-Fi Connect to use by setting the environment variable shown in the docker-compose.yml file:

PWC_INTERFACE: "wlan0"

To allow for automatic detection, remove the variable from your docker-compose.yml file.

This setting can also be controlled using the /set_interface endpoint.

LED Indicator

Some devices - such as the Raspberry Pi series - have an LED that can be controlled. When your device is connected to Wi-Fi, Python Wi-Fi Connect turns the LED on. When disconnected or in Hotspot mode, it turns the LED off.

If you need to disable this feature to allow the LED to be used for other purposes, change the environment variable in the docker-compose.yml file to: PWC_LED: "OFF".

Endpoints

Connect to a nearby Wi-Fi access point. Once connected the device will automatically connect to the same network on next boot until you call the /forget endpoint.

POST

{
    "ssid": "BT-Media-543", // Name of the Wi-Fi network you want to connect to.
    "conn_type": "WPA2", // Can be identified from the list_access_points endpoint.
    "username": "username", // Optional for enterprise networks.
    "password": "example-password" // Optional. Minimum 8 characters

}

Response status 202

Requests are returned immediately and then the process is executed. Otherwise users would be disconnected before they were able to receive the returned response.

{
    "message": "accepted"
}

Check whether your device is connected to a Wi-Fi hotspot and whether there is internet access.

GET

Response status 200

{
    "wifi": true,
    "internet": true
}

Disconnect from an access point and forget the connection so it will not automatically reconnect on next launch of your device.

When passing "all_networks": false this endpoint will only touch Wi-Fi connections set up using this app. If you pass "all_networks": true it will remove all Wi-Fi connections from the device. This is useful if you have set up a Wi-Fi connection with another app and need to clear out connections to allow Python-WiFi-Connect to manage connections.

POST

{
    "all_networks": false
}

Response status 202

Requests are returned immediately and then the process is executed. Otherwise users would be disconnected before they were able to receive the returned response.

{
    "message": "accepted"
}

Fetch list of nearby Wi-Fi networks for passing to the connect endpoint.

GET

Response status 200

{
    "ssids": [
        {
            "ssid": "VM123934", // SSID of the device
            "conn_type": "WPA2", // Security type.
            "strength": 100 // Signal strength from 0 – 100, with 100 being strongest
        },
        {
            "ssid": "BT Media",
            "conn_type": "ENTERPRISE",
            "strength": 70
        },
        {
            "ssid": "Althaea-2-no-password",
            "security": "NONE",
            "strength": 65
        },
        {
            "ssid": "TELUS9052-Hidden",
            "security": "HIDDEN",
            "strength": 10
        }
    ],
    "iw_compatible": true // Whether your device supports refreshing
    // of the nearby networks using IW (True = it does support it).
    // When this is false, your device may need to be restarted to refresh
    // the networks list. When it is True, you may be able to refresh the
    // links by calling the list_access_points endpoint again. Useful for
    // enabling or disabling a refresh button on a user interface.
}

Check whether the API is available. Accessing this path will not log anything in the console.

GET

Response status 200

{
    "message": "ok"
}

Allows setting the hotspot password. Using this endpoint will store the passed string in a file and will override the environment variable password. Ensure the ./db folder is mounted as a volume for this change to be persistent.

POST

{
    "password": "new-password" // Minimum of 8 characters
}

Response status 200

{
    "message": "ok"
}

Allows setting the hotspot SSID. Using this endpoint will store the passed string in a file and will override any environment variable ssid. Ensure the ./db folder is mounted as a volume for this change to be persistent.

POST

{
    "ssid": "new SSID"
}

Response status 200

{
    "message": "ok"
}

By default the Wi-Fi network interface is auto-detected. If you need to specify a network interface, you can do so using this endpoint.

Changing the setting will only last until the next restart of the container, when it will resort back to the setting set by the environment variable in the container or detect the interface automatically if there is no environment variable in the container.

POST

{
    "interface": "wlan0"
}

Response status 200

{
    "message": "ok"
}

python-wifi-connect's People

Contributors

maggie44 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

python-wifi-connect's Issues

Can't access API

I'm trying to get this working with the starter-interface, but it seems nothing is running on port 9090. You can check the docker-compose file here.

Logs from the python-wifi-connect container:

[2023-03-05 17:53:59] - [INFO] - Checking for previously configured Wi-Fi connections...
command failed: Resource busy (-16)
[2023-03-05 17:54:12] - [WARNING] - IW resource busy. Retrying...
[2023-03-05 17:54:12] - [WARNING] - IW is unable to complete the request. This can happen on some devices and is usually nothing to worry about.
[2023-03-05 17:54:13] - [INFO] - Adding connection of type HOTSPOT
[2023-03-05 17:54:15] - [INFO] - Connection active.
[2023-03-05 17:54:15] - [INFO] - Listening on 172.17.0.1 port 9090

Payload from starter-interface:

{
    "message": "\u001b[31mconnect ECONNREFUSED 127.0.0.1:9090\u001b[39m",
    "name": "Error",
    "stack": "Error: \u001b[31mconnect ECONNREFUSED 127.0.0.1:9090\u001b[39m\n    at AxiosError.from (/app/node_modules/axios/dist/node/axios.cjs:789:14)\n    at RedirectableRequest.handleRequestError (/app/node_modules/axios/dist/node/axios.cjs:2744:25)\n    at RedirectableRequest.emit (node:events:525:35)\n    at eventHandlers.<computed> (/app/node_modules/follow-redirects/index.js:14:24)\n    at ClientRequest.emit (node:events:513:28)\n    at Socket.socketErrorListener (node:_http_client:494:9)\n    at Socket.emit (node:events:513:28)\n    at emitErrorNT (node:internal/streams/destroy:151:8)\n    at emitErrorCloseNT (node:internal/streams/destroy:116:3)\n    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)",
    "config": {
      "transitional": {
        "silentJSONParsing": true,
        "forcedJSONParsing": true,
        "clarifyTimeoutError": false
      },
      "adapter": [
        "xhr",
        "http"
      ],
      "transformRequest": [
        null
      ],
      "transformResponse": [
        null
      ],
      "timeout": 30000,
      "xsrfCookieName": "XSRF-TOKEN",
      "xsrfHeaderName": "X-XSRF-TOKEN",
      "maxContentLength": -1,
      "maxBodyLength": -1,
      "env": {},
      "headers": {
        "Accept": "application/json, text/plain, */*",
        "User-Agent": "axios/1.2.3",
        "Accept-Encoding": "gzip, compress, deflate, br"
      },
      "baseURL": "http://127.0.0.1:9090/",
      "method": "get",
      "url": "v1/connection_status"
    },
    "code": "ECONNREFUSED",
    "status": null
  }```

Hotspot start up error 802-11-wireless-security.psk: property is invalid

So iset up this up with balena-starter-interface but I'm running in to this isse.

Setup:
OpenBalena
generic-amd64-2.115.1-v14.11.1.img

[2023-05-24T12:57:04.234Z] [2023-05-24 12:57:04] - [INFO] - Checking for previously configured Wi-Fi connections...
[2023-05-24T12:57:19.546Z] [2023-05-24 12:57:19] - [INFO] - Adding connection of type HOTSPOT
[2023-05-24T12:57:19.587Z] [2023-05-24 12:57:19] - [ERROR] - Connection failed.
[2023-05-24T12:57:19.588Z] Traceback (most recent call last):
[2023-05-24T12:57:19.588Z] File "/app/common/wifi.py", line 126, in connect
[2023-05-24T12:57:19.588Z] Pnm.Settings.AddConnection(conn_dict)
[2023-05-24T12:57:19.589Z] File "", line 4, in AddConnection
[2023-05-24T12:57:19.589Z] File "/root/.local/lib/python3.8/site-packages/dbus/proxies.py", line 141, in __call__
[2023-05-24T12:57:19.589Z] return self._connection.call_blocking(self._named_service,
[2023-05-24T12:57:19.590Z] File "/root/.local/lib/python3.8/site-packages/dbus/connection.py", line 652, in call_blocking
[2023-05-24T12:57:19.590Z] reply_message = self.send_message_with_reply_and_block(
[2023-05-24T12:57:19.590Z] dbus.exceptions.DBusException: org.freedesktop.NetworkManager.Settings.Connection.InvalidProperty: 802-11-wireless-security.psk: property is invalid
[2023-05-24T12:57:19.591Z] Traceback (most recent call last):
[2023-05-24T12:57:19.591Z] File "/app/common/wifi.py", line 126, in connect
[2023-05-24T12:57:19.591Z] Pnm.Settings.AddConnection(conn_dict)
[2023-05-24T12:57:19.591Z] File "", line 4, in AddConnection
[2023-05-24T12:57:19.592Z] File "/root/.local/lib/python3.8/site-packages/dbus/proxies.py", line 141, in __call__
[2023-05-24T12:57:19.592Z] return self._connection.call_blocking(self._named_service,
[2023-05-24T12:57:19.592Z] File "/root/.local/lib/python3.8/site-packages/dbus/connection.py", line 652, in call_blocking
[2023-05-24T12:57:19.592Z] reply_message = self.send_message_with_reply_and_block(
[2023-05-24T12:57:19.592Z] dbus.exceptions.DBusException: org.freedesktop.NetworkManager.Settings.Connection.InvalidProperty: 802-11-wireless-security.psk: property is invalid
[2023-05-24T12:57:19.593Z]
[2023-05-24T12:57:19.593Z] During handling of the above exception, another exception occurred:
[2023-05-24T12:57:19.593Z]
[2023-05-24T12:57:19.593Z] Traceback (most recent call last):
[2023-05-24T12:57:19.594Z] File "run.py", line 68, in
[2023-05-24T12:57:19.594Z] connect()
[2023-05-24T12:57:19.594Z] File "/app/common/wifi.py", line 166, in connect
[2023-05-24T12:57:19.594Z] raise WifiHotspotStartFailed
[2023-05-24T12:57:19.594Z] common.errors.WifiHotspotStartFailed

Unsupported DeviceType

This looks like a neat solution, but unfortunately I'm not able to start the container. When I try to do so I get the following message:

Traceback (most recent call last):
  File "run.py", line 30, in <module>
    device = get_device()
  File "/app/common/wifi.py", line 248, in get_device
    [(x.DeviceType, x) for x in Pnm.NetworkManager.GetDevices()]
  File "<string>", line 8, in GetDevices
  File "/root/.local/lib/python3.8/site-packages/NetworkManager.py", line 550, in to_python
    val = fixups.base_to_python(val)
  File "/root/.local/lib/python3.8/site-packages/NetworkManager.py", line 607, in base_to_python
    return [fixups.base_to_python(x) for x in val]
  File "/root/.local/lib/python3.8/site-packages/NetworkManager.py", line 607, in <listcomp>
    return [fixups.base_to_python(x) for x in val]
  File "/root/.local/lib/python3.8/site-packages/NetworkManager.py", line 620, in base_to_python
    return globals()[classname](val)
  File "/root/.local/lib/python3.8/site-packages/NetworkManager.py", line 348, in __new__
    klass = device_class(obj.Get('org.freedesktop.NetworkManager.Device', 'DeviceType', dbus_interface='org.freedesktop.DBus.Properties'))
  File "/root/.local/lib/python3.8/site-packages/NetworkManager.py", line 368, in device_class
    return {
KeyError: dbus.UInt32(30, variant_level=1)

I'm not very familiar with both NetworkManager and Python. Is this something that needs to be fixed in python-networkmanager? I've experienced a similar issue with wifi-connect for which I did find this issue. I've scanned my network using the nmcli on the host, and there are no WPA3 networks in the list.

Thanks for your effort and please let me know if there is anything I can check or try!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.