Golang实现通过蓝牙配置Linux系统WIFI

2023年 7月 11日 22.6k 0

背景和使用场景

在物联网项目中需要通过手机应用初始化设备的网络连接,物联网终端使用的是Linux操作系统,配置为单应用启动模式,没有提供图形桌面,为了让普通用户方便的初始化设备,需要使用手机蓝牙连接设备配置无线网络连接。

手机应用开发蓝牙连接功能,通过近场蓝牙连接设备,配置WI-FI的SSID和密码,手机端对应蓝牙功能本文先不做介绍,可以通过nRF Connect 或 LightBlue 应用测试本文的实现。

为什么选择Golang来实现

设备端IOT客户端代码由Nodejs来实现,蓝牙服务也由Nodejs编写,由于bleno库已经不再维护,bleno的第三方库依赖和编译对Python和Nodejs的版本有诸多不便,所以转向使用Golang来实现,选择使用tinygo的bluetooth模块。

如果对Nodejs感兴趣也可以参考Bluetooth Nodejs实现

操作系统环境安装

在 Linux(Debian或Ubuntu)安装BlueZ

sudo apt update
sudo apt-get install bluetooth bluez bluez-tools rfkill

查看并启动Bluetooth服务

sudo systemctl enable bluetooth.service
sudo systemctl start bluetooth.service
sudo hciconfig hci0 up
rfkill unblock bluetooth

代码实现

功能需求

  • 通过手机应用中设备搜索,查询主机提供的蓝牙发现服务,查询到目标设备,手动建立蓝牙连接
  • 手机应用连接后,蓝牙服务返回设备IP地址和连接状态信息
  • 手机端应用提交一个Form,输入SSID和Password,发送回设备蓝牙服务,设备修改WI-FI配置,连接新的无线网络
  • 自动或手动刷新设备网络连接状态,有线或无线网络连接

Golang实现通过蓝牙配置Linux系统WI-FI搜索发现蓝牙服务Golang实现通过蓝牙配置Linux系统WI-FI刷新查看设备网络状态,设置无线网络参数

定义发送和接收的数据格式

//DeviceIPAddress model
type DeviceIPAddress struct {
  Eth0 IPModel `json:"eth0"`
  Wifi IPModel `json:"wifi"`
}
​
//IPModel for ip address
type IPModel struct {
  IP   string `json:"ip"`
  Mac  string `json:"mac"`
  Name string `json:"name"`
}
​
//WIFIConfig data send to config device wifi
type WIFIConfig struct {
  Ssid     string `json:"ssid"`
  Password string `json:"password"`
}
​
//NetworkStatus is the network health staus
type NetworkStatus struct {
  Success bool   `json:"success"`
  Message string `json:"message"`
}
​
//WifiSettingStatus is the network health staus
type WifiSettingStatus struct {
  Success bool `json:"success"`
}

Golang代码只实现Bluetooth服务的发现,数据的发送和接收,譬如主机网络地址,连接状态,Wi-Fi配置信息都交由本地Nodejs通过Localhost Http API 来实现

Nodejs 本地服务(http://localhost:3002/)

  • getLocalIPAddresses:返回设备IP信息
  • internetHealthyCheck:查询设备是否已经连接网络
  • setupNewWifi: 配置无线网络
  • resetTerminal: 通过Shell救援重置设备
  • 配置本地API服务路径

    var localAPIHost = "http://127.0.0.1:3002/"
    var getLocalIPAddressURL = localAPIHost + "getLocalIPAddress"
    var internetHealthyCheckURL = localAPIHost + "internetHealthyCheck"
    var setupNewWifiURL = localAPIHost + "setupNewWifi"
    var factoryResetURL = localAPIHost + "factoryResetForBle"
    ​
    //设备名称保存在主机本地txt文件中
    var deviceBleFile = "/application/signage-device-application/db/device.txt"
    ​
    

    定义服务UUID,可使用UUID在线生成工具或代码生成

    var (
      serviceUUID, _       = bluetooth.ParseUUID("d6cb1959-8010-43bd-8ef7-48dbd249b984")
      refreshUUID, _       = bluetooth.ParseUUID("c537baa5-6201-4275-ab14-da353bde3dc3")
      statusUUID, _        = bluetooth.ParseUUID("f9e9e098-77d4-4db3-a08f-8321c493431b")
      ipUUID, _            = bluetooth.ParseUUID("2d75504c-b822-44b3-bb81-65d7b6cbdae1")
      settingUUID, _       = bluetooth.ParseUUID("493ebfb0-b690-4ae8-a77a-329619c6f613")
      resetTerminalUUID, _ = bluetooth.ParseUUID("2d75504c-b822-44b3-bb81-65d7b6cbdae3")
    )
    

    主机蓝牙服务提供3个可写的和2个只读的characteristic

      //可写
      var refreshChar bluetooth.Characteristic
      var resetTerminalChar bluetooth.Characteristic
      var settingChar bluetooth.Characteristic
      //只读
      var statusChar bluetooth.Characteristic
      var ipChar bluetooth.Characteristic
    

    主函数

    func main() {
    file, err := ioutil.ReadFile(deviceBleFile)
    if err != nil {
    log.Println(err)
    }
    bleName := string(file)
    adapter := bluetooth.DefaultAdapter
    must("enable BLE stack", adapter.Enable())
    adv := adapter.DefaultAdvertisement()
    must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
    LocalName: bleName, // kimacloud sevice
    ServiceUUIDs: []bluetooth.UUID{serviceUUID},
    }))
    must("start adv", adv.Start())
    var refreshChar bluetooth.Characteristic
    var statusChar bluetooth.Characteristic
    var ipChar bluetooth.Characteristic
    var resetTerminalChar bluetooth.Characteristic
    var settingChar bluetooth.Characteristic
    must("add service", adapter.AddService(&bluetooth.Service{
    UUID: serviceUUID,
    Characteristics: []bluetooth.CharacteristicConfig{
    {
    Handle: &refreshChar,
    UUID: refreshUUID,
    Flags: bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
    WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
    ipaddresses, _ := getLocalIPAddresses()
    ipString, _ := json.Marshal(ipaddresses)
    ipChar.Write(ipString)
    netState, _ := internetHealthyCheck()
    if netState {
    statusChar.Write([]byte("online"))
    } else {
    statusChar.Write([]byte("offline"))
    }

    },
    },
    {
    Handle: &settingChar,
    UUID: settingUUID,
    Flags: bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
    WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
    setupNewWifi(value)
    ipaddresses, _ := getLocalIPAddresses()
    ipString, _ := json.Marshal(ipaddresses)
    log.Println(ipString)
    netState, _ := internetHealthyCheck()
    if netState {
    statusChar.Write([]byte("online"))
    } else {
    statusChar.Write([]byte("offline"))
    }

    },
    },
    {
    Handle: &resetTerminalChar,
    UUID: resetTerminalUUID,
    Flags: bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
    WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
    log.Println("reset terminal")
    resetTerminal(value)
    },
    },
    {
    Handle: &statusChar,
    UUID: statusUUID,
    Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission,
    },
    {
    Handle: &ipChar,
    UUID: ipUUID,
    Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission,
    },
    },
    }))
    println("advertising...")
    ipaddresses, _ := getLocalIPAddresses()
    ipString, _ := json.Marshal(ipaddresses)
    log.Println(ipString)
    ipChar.Write(ipString)
    address, _ := adapter.Address()

    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt)
    go func() {
    println("Kimacloud Bluetooth Service /", address.MAC.String())
    time.Sleep(1 * time.Second)
    }()

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论