Разработка сканера Wi-Fi сетей для Sailfish OS

    Введение


    Порой, при решении рабочих задач, возникает необходимость в информации об окружающих сетях Wi-Fi: канал, мощность, тип шифрования и др. И если для популярных мобильных ОС Android и iOS утилит много, то для Sailfish OS — только одна. Сегодня, на примере данной утилиты, будет разобрано получение информации об окружающих сетях Wi-Fi и её отображение двумя способами: списком и графически.

    До изучения материала желательно иметь базовое представление о разработке под Sailfish OS и об утилите wpa_cli.

    Получение информации о сетях Wi-Fi


    Выделяется два основных способа получить информацию об окружающих устройство сетях Wi-Fi: использовать утилиту wpa_cli или недокументированный элемент TechnologyModel из модуля MeeGo.Connman.

    Первый способ — решение «в лоб». Использование утилиты wpa_cli в Sailfish OS ничем не отличается от другого Linux-дистрибутива:

    wpa_cli scan && wpa_cli scan_results
    # wpa_cli scan
    Selected interface 'wlan0'
    OK
    # wpa_cli scan_results
    Selected interface 'wlan0'
    bssid / frequency / signal level / flags / ssid
    10:bf:48:4b:2b:f4 2412 -46 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] Asd_496283
    d4:21:22:33:ec:46 2417 -57 [WPA2-PSK-CCMP][WPS][ESS] MGTS_243
    78:94:b4:99:1c:41 2462 -59 [WPA2-PSK-CCMP][WPS][ESS] MGTS_GPON_8959
    78:96:82:64:ea:fd 2427 -62 [WPA2-PSK-CCMP][WPS][ESS] Onlime248
    90:f6:52:66:20:92 2412 -41 [WPA2-PSK-CCMP][ESS] Hearthstone
    14:cc:20:32:e7:04 2437 -65 [WPA2-PSK-CCMP][WPS][ESS] ViVa239
    00:0e:8f:2f:ff:3c 2412 -67 [WPA-PSK-TKIP][WPA2-PSK-CCMP][WPS][ESS] Smart_box - 297
    d4:6e:0e:b0:17:16 2462 -73 [WPA2-PSK-CCMP+TKIP][WPS][ESS] MGTS_GPON_8959
    94:4a:0c:ce:93:05 2462 -71 [WPA2-PSK-CCMP][WPS][ESS] MGTS_GPON_7870
    e8:94:f6:fa:43:86 2417 -77 [WPA2-PSK-CCMP][ESS] Home236
    be:85:56:e2:9a:fc 2427 -74 [WPA2-PSK-CCMP][WPS][ESS] DIRECT-HR-BRAVIA
    c0:a0:bb:1d:4c:58 2412 -74 [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][WPS][ESS] dlink-4C58
    40:3d:ec:31:ca:fb 2432 -86 [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS] TV kinescope
    fc:2d:5e:45:db:35 2437 -82 [WPA2-PSK-CCMP+TKIP][WPS][ESS] Matthew
    00:0e:8f:6e:47:ba 2412 -65 [WPA-PSK-TKIP][WPA2-PSK-CCMP][WPS][ESS] Genya
    c0:a0:bb:81:c7:4a 2447 -83 [WPA2-PSK-CCMP][ESS] mrnext-245
    d8:fe:e3:f9:26:45 2437 -66 [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS] NBN


    Данный подход имеет две основные проблемы. Во-первых, для его применения требуются права суперпользователя. Во-вторых, необходимо реализовывать чтение результата выполнения консольной команды.

    Первая проблема решается запросом пароля у пользователя при запуске программы и дальнейшим использованием модуля QProcess:

    Взаимодействие с wpa_cli
    /**
     * Метод вызывает сканирование с помощью утилиты wpa_cli.
     * @param password - пароль для получения доступа root.
     */
    void WpaCliHelper::callWpaCli(QString password) {
        // Создание объекта процесса
        QProcess process;
        // Запуск сканирования сетей Wi-Fi под пользователем root
        process.start(QString("/bin/bash -c \"echo %1 | devel-su wpa_cli scan\"").arg((password)));
        // Ожидание завершения процесса
        if (!process.waitForFinished()) {
            // Если произошла ошибка, то выход
            emit gotScanError();
            return;
        }
        // Чтение результата работы команды
        mWifiInfo = process.readAll();
        // Если произошла ошибка авторизации, то выход
        if (mWifiInfo.contains("Auth failed")) {
            emit gotAuthError();
            return;
        }
        // Запрос результатов сканирования под пользователем root
        process.start(QString("/bin/bash -c \"echo %1 | devel-su wpa_cli scan_results\"").arg((password)));
        // Ожидание завершения процесса
        if (!process.waitForFinished()) {
            // Если произошла ошибка, то выход
            emit gotResultError();
            return;
        }
        // Чтение результата работы команды
        mWifiInfo = process.readAll();
        // Сигнал об успешном завершении работы
        emit calledWpaCli();
    }


    Для чтения результатов сканирования также достаточно одной функции:

    Получение результата работы wpa_cli
    /**
     * Метод разбирает результат сканирования сетей утилитой wpa_cli.
     * @param info - вывод утилиты wpa_cli.
     */
    void WifiInfoParser::parseInfo(QString info) {
        // Удаление предыдущей информации о сетях
        wifiInfo.clear();
        // Разделение общего вывода по строкам
        QStringList networks = info.split('\n');
        // Обработка отсутствия информации о сетях
        if (networks.length() == 3) {
            networkCount = 0;
            emit parsed(PARSE_COMPLETED_WITH_NO_NETWORKS);
        }
        // Сохранение количества окружающих сетей Wi-Fi
        networkCount = networks.length() - 3;
        for (int i = 0; i < networkCount; i++) {
            // Разделение каждой строки на поля
            QStringList data = networks.at(i+2).split('\t');
            // Сохранение информации о сети
            wifiInfo << QVariant::fromValue(
                            (QStringList() << QString::number(calculateChannel(data[1].toInt())) // Номер канала
                                           << data[2] // Уровень сигнала
                                           << data[4] // Имя сети
                                           << data[0])); // BSSID сети
        }
        // Сигнал о завершении разбора вывода
        emit parsed(PARSE_COMPLETED_CORRECTLY);
    }


    Однако, запрос прав суперпользователя при запуске приложения может смутить обычного пользователя, и, поэтому, необходимо избегать использование функций, требующих root-доступ. Поэтому, в рамках задачи получения информации об окружающих сетях Wi-Fi рекомендуется использовать модуль MeeGo.Connman, к сожалению не представленный в стандартной документации.

    Для его использования необходимо добавить следующую строчку:

    import MeeGo.Connman 0.2

    Данный модуль предоставляет необходимый для поставленной задачи элемент TechnologyModel, позволяющий всего в несколько строк в QML-файле получить структурированную информацию об окружающих сетях Wi-Fi:

    TechnologyModel {
        id: networksList // Идентификатор для обращения
        name: "wifi" // Имя интерфейса для сканирования
    }

    Однако, обновлять информацию необходимо вручную. Лучше всего это делать с помощью компонента Timer:

    Timer {
        id: updateTimer // Идентификатор для обращения
        interval: 2000 // Срабатывание каждые две секунды
        running: true // Таймер запущен
        repeat: true // Таймер запускается повторно после срабатывания
        triggeredOnStart: true // Таймер срабатывает перед первым запуском
    
        onTriggered: networksList.requestScan() // Обновление списка окружающих сетей Wi-Fi
    }

    Теперь вся необходимая информация представлена в структурированном виде, а процесс её получения не требует пароля администратора. Соответственно, можно переходить к процессу отображения полученных данных.

    Стоит заметить, что такой же интерфейс доступен и для приложений, реализуемых на C++. Пример его использования опубликован в репозитории модуля.

    Отображение данных списком


    Описанная в предыдущем разделе модель возвращает список элементов типа NetworkService, который предоставляет всю необходимую информацию об окружающих сетях. Из всего многообразия наибольший интерес для сканера представляют поля, описанные в таблице 1.

    Таблица 1. Поля, используемые в сканере сетей Wi-Fi.
    Имя поля Тип данных Описание
    name QString Имя сети
    frequency quint16 Частота сигнала
    strength uint Сила сигнала
    bssid QString BSSID
    security QStringList Тип шифрования

    Теперь, зная способ хранения информации о сетях и элемент для отрисовки списков, можно читабельно отобразить полученную информацию (рисунок 1):

    Код отображения в виде списка
    SilicaListView { // Начало списка сетей
        id: wifiInfoList // Идентификатор для обращения
    
        anchors.fill: parent // Заполнение всего экрана
    
        model: networksList // Модель с информацией об окружающих сетях
    
        delegate: Item { // Начало элемента списка
            width: parent.width // Ширина экрана
            height: Theme.itemSizeHuge // Одна из стандартных рекомендуемых высот элемента списка
    
            Column { // Расположение элементов в столбец
                anchors {
                    fill: parent // Заполнение всего зарезервированного пространства
                    leftMargin: Theme.horizontalPageMargin // Стандартный рекомендуемый отступ
                    rightMargin: Theme.horizontalPageMargin // Стандартный рекомендуемый отступ
                    topMargin: Theme.paddingLarge // Стандартный рекомендуемый отступ
                }
    
                Row { // Расположение элементов в строку
                    // Ширина столбца с учётом отступов
                    anchors.right: parent.right
                    anchors.left: parent.left
    
                    height: childrenRect.height // Высота зависит от вложенных элементов
    
                    Label { // Имя сети
                        width: parent.width / 2 // Ширина в половину столбца
                        horizontalAlignment: Text.AlignLeft // Выравнивание текста по левому краю
                        font.bold: true // Полужирное начертание текста
                        text: modelData.name // Имя сети
                        truncationMode: TruncationMode.Fade // Частичное скрытие длинного текста
                    }
    
                    Label { // Канал, используемый сетью
                        width: parent.width / 4 // Ширина в четверть столбца
                        horizontalAlignment: Text.AlignRight // Выравнивание текста по правому краю
                        text: (calculateChannel(modelData.frequency) + 1) + " ch." // Канал сети
                    }
    
                    Label { // Мощность сигнала сети
                        width: parent.width / 4 // Ширина в четверть столбца
                        horizontalAlignment: Text.AlignRight // Выравнивание текста по правому краю
                        text: (modelData.strength - 120) + " dBm" // Мощность сигнала сети
                    }
                }
    
                Item { // Дополнительная информация
                    // Ширина столбца с учётом отступов
                    anchors.right: parent.right
                    anchors.left: parent.left
    
                    height: childrenRect.height // Высота зависит от вложенных элементов
    
                    Label { // BSSID сети
                        anchors.left: parent.left // Расположение слева
                        text: "bssid: " + modelData.bssid // BSSID сети
                    }
    
                    Label { // Шифрование сети
                        anchors.right: parent.right // Расположение справа
                        text: modelData.security.join("/") // Шифрование сети
                    }
                }
    
                ProgressBar { // Визуальное отображение мощности сигнала сети
                    width: parent.width // Ширина столбца с учётом отступов
                    minimumValue: 0 // Минимальное возможное значение
                    maximumValue: 100 // Максимальное возможное значение
                    value: modelData.strength // Мощность сигнала сети
                }
            }
        }
    
        VerticalScrollDecorator {} // Ползунок вертикальной прокрутки
    }


    Для определения номера канала по частоте сигнала используется стандартная таблица разделения каналов по частоте и простая функция, построенная на её основе:

    /**
     * Функция рассчитывает номер канала сети по её частоте.
     * @param frequency - частота сети Wi-Fi
     * @return Номер канала для выбранной сети Wi-Fi
     */
    function calculateChannel(frequency) {
        var channel = (frequency - 2412) / 5;
        return channel > 12 ? 13 : channel;
    }

    Рисунок 1. Отображение окружающих сетей Wi-Fi в виде списка.
    Рисунок 1. Отображение окружающих сетей Wi-Fi в виде списка.

    Также необходимо осуществлять проверку на возможность получения списка сетей и на количество элементов в нём. За это отвечают поля powered и count, соответственно. В случае, если сетевой интерфейс выключен или вокруг отсутствуют сети, пользователю необходимо отобразить соответствующее сообщение (рисунок 2). Для этого используется элемент ViewPlaceholder:

    ViewPlaceholder {
        enabled: !networksList.powered // Отображается если сетевой интерфейс выключен
        text: qsTr("Please, turn WiFi on") // Текст сообщения пользователю
    }
    
    ViewPlaceholder {
        enabled: networksList.powered && networksList.count === 0 // Отображается если вокруг нет сетей
        text: qsTr("There are no WiFi networks") // Текст сообщения пользователю
    }

    Рисунок 2. Сообщение о необходимости включить Wi-Fi.
    Рисунок 2. Сообщение о необходимости включить Wi-Fi.

    Графическое отображение данных


    Цель графического отображения информации о сетях Wi-Fi — визуально показать перекрытия между ними. Следовательно, потребуются только значения имени сети, силы и частоты её сигнала.

    Для начала требуется подготовить экран для рисования. Для этого используется элемент Canvas, на котором выполняется отрисовка графика при поступлении сигнала onPaint. Также, с помощью элемента Connections, определяется перерисовка графика при изменении информации об окружающих сетях Wi-Fi. Как и в случае со списком, задаётся возможность отображения сообщения пользователю в случае невозможности получить необходимые данные.

    Код графического отображения
    Page {
    
        ViewPlaceholder { // Сообщение пользователю о необходимости включить Wi-Fi
            enabled: !networksList.powered
            text: qsTr("Please, turn WiFi on")
        }
    
        ViewPlaceholder { // Сообщение пользователю об отсутствии вокруг сетей Wi-Fi
            enabled: networksList.powered && networksList.count === 0
            text: qsTr("There are no WiFi networks")
        }
    
        SilicaFlickable { // Контейнер элементов экрана
            anchors.fill: parent
    
            Canvas { // Поле для рисования
                id: graph // Идентификатор для обращения
                anchors { // Заполнение всего доступного пространства с учётом стандартных отступов
                    fill: parent
                    leftMargin: Theme.horizontalPageMargin
                    rightMargin: Theme.horizontalPageMargin
                    topMargin: Theme.paddingLarge
                    bottomMargin: Theme.paddingLarge
                }
    
                onPaint: drawGraph() // Обработка сигнала рисования
            }
        }
    
        Connections { // Обработка внешних сигналов
            target: networksList // Отправитель сигналов - модель с информацией об окружающих сетях
            onScanRequestFinished: graph.requestPaint() // Запуск отрисовки после каждого сканирования
        }
    
        onOrientationChanged: graph.requestPaint() // Запуск отрисовки при повороте экрана
    }


    Функцию отрисовки графика можно разделить на три части. Сначала инициализируется и очищается поле для рисования. Потом вычисляются координаты осей и сетки в соответствии с экраном. И, наконец, выполняется отрисовка.

    function drawGraph() {
        var context = graph.getContext("2d"); // Инициализация двумерного пространства
        context.clearRect(0, 0, graph.width, graph.height); // Очистка поля для рисования
        context.lineWidth = 3; // Толщина линии в три пикселя
        context.strokeStyle = "gray"; // Серый цвет линий
        context.fillStyle = "gray"; // Серый цвет заливки
        context.font = "12pt sans-serif"; // Шрифт для текста
    
        // Расчёт и отрисовка осей координат и сетки
        var channels = calculateChannelsPositions(graph.width);
        var levels = calculateSignalLevelsPositions(graph.height)
        drawAxes(context, channels, levels);
    
        // Расчёт и отрисовка графиков сетей Wi-Fi
        if (networksList.count === 0) return;
        drawWifiFigures(context, graph.width, graph.height, channels);
    }

    Координаты осей и сетки рассчитываются равномерным разделением пространства экрана между каналами и возможными уровнями сигнала. Здесь также используется приведённая выше таблица разделения каналов Wi-Fi по частоте.

    // Массив со значащими значениями частоты сигнала Wi-Fi
    property variant channelsInfo: [11, 16, 21, 26, 31, 36, 41, 46, 51, 56, 61, 66, 71, 83]
    
    /**
     * Функция рассчитывает координаты осей каналов в соответствии с шириной экрана.
     * @:param: width - ширина поля для рисования
     * @:return: channels - координаты осей каналов в пикселях
     */
    function calculateChannelsPositions(width) {
        var channels = [];
        var step = (width - Theme.horizontalPageMargin) / 94;
        for (var index in channelsInfo) {
            channels[index] = channelsInfo[index] * step + Theme.horizontalPageMargin;
        }
        return channels;
    }
    
    /**
     * Функция рассчитывает координаты осей мощности сигнала в соответствии с высотой экрана.
     * @:param: height - высота поля для рисования
     * @:return: levels - координаты осей мощности сигнала в пикселях
     */
    function calculateSignalLevelsPositions(height) {
        var levels = [];
        var step = (height - Theme.paddingLarge) / 10;
        for (var index = 0; index < 10; ++index) {
            levels[index] = index * step + Theme.paddingLarge;
        }
        return levels;
    }

    После получения значений координат в пикселях, становится возможным отрисовать оси и сетку. На данном этапе основная работа ложится на функции moveTo и lineTo для перемещения кисти и рисования прямого отрезка, соответственно.

    Код отрисовки координатной сетки
    /**
     * Функция отображает оси координат.
     * @:param: context - контекст для рисования
     */
    function drawGraphBounds(context) {
        context.beginPath();
        context.moveTo(2 * Theme.horizontalPageMargin, Theme.paddingLarge);
        context.lineTo(graph.width - Theme.horizontalPageMargin, Theme.paddingLarge);
        context.lineTo(graph.width - Theme.horizontalPageMargin, graph.height - Theme.paddingLarge);
        context.lineTo(2 * Theme.horizontalPageMargin, graph.height - Theme.paddingLarge);
        context.closePath();
        context.stroke();
    }
    
    /**
     * Функция отображает ось канала.
     * @:param: context - контекст для рисования
     * @:param: channelX - координата для отображения
     */
    function drawChannelAxe(context, channelX) {
        context.beginPath();
        context.moveTo(channelX, Theme.paddingLarge);
        context.lineTo(channelX, graph.height - Theme.paddingLarge);
        context.closePath();
        context.stroke();
    }
    
    /**
     * Функция отображает номер канала.
     * @:param: context - контекст для рисования
     * @:param: channelIndex - индекс канала
     * @:param: channelX - координата для отображения
     */
    function drawChannelNumber(context, channelIndex, channelX) {
        var text = parseInt(channelIndex) + 1;
        var textWidth = context.measureText(text).width;
        context.fillText(text, channelX - (textWidth / 2), graph.height);
    }
    
    /**
     * Функция запускает рисование осей каналов.
     * @:param: context - контекст для рисования
     * @:param: channels - список координат для отображения
     */
    function drawChannelsAxes(context, channels) {
        context.lineWidth = 1;
        for (var channelIndex in channels) {
            drawChannelAxe(context, channels[channelIndex]);
            drawChannelNumber(context, channelIndex, channels[channelIndex]);
        }
    }
    
    /**
     * Функция отображает ось мощности сигнала.
     * @:param: context - контекст для рисования
     * @:param: signalLevelY - координата для отображения
     */
    function drawSignalLevelAxe(context, signalLevelY) {
        context.beginPath();
        context.moveTo(2 * Theme.horizontalPageMargin, signalLevelY);
        context.lineTo(graph.width - Theme.horizontalPageMargin, signalLevelY);
        context.closePath();
        context.stroke();
    }
    
    /**
     * Функция отображает значение мощности сигнала.
     * @:param: context - контекст для рисования
     * @:param: signalLevel - мощность сигнала
     * @:param: signalLevelY - координата для отображения
     */
    function drawSignalLevel(context, signalLevel, signalLevelY) {
        if (signalLevel === '0') return;
        var text = '-' + signalLevel + '0';
        var textWidth = context.measureText(text).width;
        context.fillText(text, Theme.horizontalPageMargin - (textWidth / 2), signalLevelY);
    }
    
    /**
     * Функция запускает рисование осей мощностей сигнала.
     * @:param: context - контекст для рисования
     * @:param: levels - список координат для отображения
     */
    function drawSignalLevelsAxes(context, levels) {
        for (var levelIndex in levels) {
            drawSignalLevelAxe(context, levels[levelIndex]);
            drawSignalLevel(context, levelIndex, levels[levelIndex]);
        }
    }
    
    /**
     * Функция запускает рисование осей координат и координатной сетки.
     * @:param: context - контекст для рисования
     * @:param: channels - список координат каналов
     * @:param: levels - список координат уровней мощности сигнала
     */
    function drawAxes(context, channels, levels) {
        drawGraphBounds(context);
        drawChannelsAxes(context, channels);
        drawSignalLevelsAxes(context, levels);
    }


    После отрисовки координатной сетки можно переходить к отображению графиков окружающих сетей. Для этого будут использоваться кривые Безье:

    Код отрисовки графиков сетей
    // Цвета линий графика
    property var strokeColors: ["rgb(255,   0,   0)",
                                "rgb(128, 128,   0)",
                                "rgb(255, 255,   0)",
                                "rgb(  0, 128,   0)",
                                "rgb(  0, 255,   0)",
                                "rgb(  0, 128, 128)",
                                "rgb(  0, 255, 255)",
                                "rgb(  0,   0, 128)",
                                "rgb(  0,   0, 255)",
                                "rgb(128,   0, 128)",
                                "rgb(255,   0, 255)",
                                "rgb(128,   0,   0)"]
    // Цвета заливки графика (с прозрачностью)
    property var fillColors: ["rgba(255,   0,   0, 0.33)",
                              "rgba(128, 128,   0, 0.33)",
                              "rgba(255, 255,   0, 0.33)",
                              "rgba(  0, 128,   0, 0.33)",
                              "rgba(  0, 255,   0, 0.33)",
                              "rgba(  0, 128, 128, 0.33)",
                              "rgba(  0, 255, 255, 0.33)",
                              "rgba(  0,   0, 128, 0.33)",
                              "rgba(  0,   0, 255, 0.33)",
                              "rgba(128,   0, 128, 0.33)",
                              "rgba(255,   0, 255, 0.33)",
                              "rgba(128,   0,   0, 0.33)"]
    
    /**
     * Функция рассчитывает y-координату по значению мощности сигнала.
     * @:param: height - высота поля для рисования
     * @:param: level - мощность сигнала сети
     * @:return: y-координата для графика сети
     */
    function calculateCurrentSignalLevelPosition(height, level) {
        return (height - Theme.paddingLarge) / 100 * Math.abs(level) + Theme.paddingLarge;
    }
    
    /**
     * Функция рассчитывает x-координаты границ для канала сети.
     * @:param: width - ширина поля для рисования
     * @:param: channel - номер канала сети
     * @:return: x-координаты границ для графика сети
     */
    function calculateBoundsPositionForChannel(width, channel) {
        var step = (width - Theme.horizontalPageMargin) / 94;
        var left = (channelsInfo[channel] - 11) * step + Theme.horizontalPageMargin;
        var right = (channelsInfo[channel] + 11) * step + Theme.horizontalPageMargin;
        return [left, right];
    }
    
    /**
     * Функция рассчитывает опорную точку для кривой Безье.
     * http://codetheory.in/calculate-control-point-to-make-your-canvas-curve-hit-a-specific-point/
     * @:param: channelCoord - координата канала сети
     * @:param: levelPosition - координата уровня мощности сигнала
     * @:param: bounds - координаты границ покрытия сети
     * @:return: Координаты опорной точки
     */
    function calculateCurrentPoint(channelCoord, levelPosition, bounds) {
        var cpx = 2 * channelCoord - (bounds[0] + bounds[1]) / 2;
        var cpy = 2 * levelPosition - (graph.height + graph.height - (2 * Theme.paddingLarge)) / 2;
        return { x: cpx, y: cpy };
    }
    
    /**
     * Функция отрисовывает график сети Wi-Fi с помощью кривой Безье.
     * @:param: context - контекст для рисования (канвас)
     * @:param: channelCoord - координата канала
     * @:param: levelPosition - координата мощности сигнала
     * @:param: bounds - границы графика сигнала
     */
    function drawWifiFigure(context, channelCoord, levelPosition, bounds) {
        var cp = calculateCurrentPoint(channelCoord, levelPosition, bounds);
        context.beginPath();
        context.moveTo(bounds[0], graph.height - Theme.paddingLarge);
        context.quadraticCurveTo(cp.x, cp.y, bounds[1], graph.height - Theme.paddingLarge);
        context.closePath();
        context.stroke();
        context.fill();
    }
    
    /**
     * Функция отображает имя сети над её графиком.
     * @:param: context - контекст для рисования (канвас)
     * @:param: wifiInfo - структура с информацией о сети Wi-Fi
     * @:param: channels - список каналов
     * @:param: levelPosition - координата мощности сигнала
     */
    function drawWifiName(context, wifiInfo, channels, levelPosition) {
        var textWidth = context.measureText(wifiInfo.name).width;
        context.fillText(wifiInfo.name,
                         channels[calculateChannel(wifiInfo.frequency)] - (textWidth / 2),
                         levelPosition - Theme.paddingSmall);
    }
    
    /**
     * Функция отрисовывает графики всех окружающих сетей.
     * @:param: context - контекст для рисования (канвас)
     * @:param: width - ширина поля для рисования
     * @:param: height - высота поля для рисования
     * @:param: channels - список каналов
     */
    function drawWifiFigures(context, width, height, channels) {
        context.lineWidth = 2;
        for (var networkIndex = 0; networkIndex < networksList.count; ++networkIndex) {
            var levelPosition = calculateCurrentSignalLevelPosition(height, (networksList.get(networkIndex).strength - 120))
            var bounds = calculateBoundsPositionForChannel(width, calculateChannel(networksList.get(networkIndex).frequency))
            context.strokeStyle = strokeColors[networkIndex % strokeColors.length];
            context.fillStyle = fillColors[networkIndex % fillColors.length];
            drawWifiFigure(context, channels[calculateChannel(networksList.get(networkIndex).frequency)], levelPosition, bounds);
            context.fillStyle = context.strokeStyle;
            drawWifiName(context, networksList.get(networkIndex), channels, levelPosition);
        }
    }


    На этом создание графика сетей заканчивается. Результат представлен на рисунке 3.

    Рисунок 3. Графическое отображение сетей Wi-Fi.
    Рисунок 3. Графическое отображение сетей Wi-Fi.

    Заключение


    В данной статье были показаны два способа получения информации об окружающих сетях Wi-Fi и два способа её отображения: списком и графиком. Полный код готового приложения, как всегда, доступен на GitHub.

    Возникающие по ходу разработки вопросы и идеи всегда можно обсудить в Telegram-чате и в группе ВКонтакте.
    • +13
    • 3,7k
    • 4
    Поделиться публикацией
    Комментарии 4
    • НЛО прилетело и опубликовало эту надпись здесь
        0
        Спасибо за статью, очень доходчиво.
        Правильно ли я понимаю, что как раз ConnMan и используется в собственном WiFi менеджере панели управления Sailfish OS?
        Есть ли при работе с TechnologyModel или NetworkService возможность работать с кешированными сетями? В каком виде хранится кеш и кому он доступен?

          0
          1) Да, в собственном менеджере Wi-Fi также используется ConnMan.
          2) Для работы с кешированными сетями используется SavedServiceModel.
          0
          К слову, на ios кто-нибудь встречал подобное? Неужели возможности айоси в плане разработки подобного функционала урезаны по этой части?

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое