Golang实现通过蓝牙配置Linux系统WI-FI

2023-04-1909:33:49编程语言入门到精通Comments1,881 views字数 7471阅读模式

背景和使用场景

在物联网项目中需要通过手机应用初始化设备的网络连接,物联网终端使用的是Linux操作系统,配置为单应用启动模式,没有提供图形桌面,为了让普通用户方便的初始化设备,需要使用手机蓝牙连接设备配置无线网络连接。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

手机应用开发蓝牙连接功能,通过近场蓝牙连接设备,配置WI-FI的SSID和密码,手机端对应蓝牙功能本文先不做介绍,可以通过nRF Connect 或 LightBlue 应用测试本文的实现。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

为什么选择Golang来实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

设备端IOT客户端代码由Nodejs来实现,蓝牙服务也由Nodejs编写,由于bleno库已经不再维护,bleno的第三方库依赖和编译对Python和Nodejs的版本有诸多不便,所以转向使用Golang来实现,选择使用tinygo的bluetooth模块。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

如果对Nodejs感兴趣也可以参考Bluetooth Nodejs实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

操作系统环境安装文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

在 Linux(Debian或Ubuntu)安装BlueZ文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

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

查看并启动Bluetooth服务文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

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

代码实现

功能需求文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

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

定义发送和接收的数据格式文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

//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 来实现文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

Nodejs 本地服务(localhost:3002/文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

  1. getLocalIPAddresses:返回设备IP信息
  2. internetHealthyCheck:查询设备是否已经连接网络
  3. setupNewWifi: 配置无线网络
  4. resetTerminal: 通过Shell救援重置设备

配置本地API服务路径文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

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在线生成工具或代码生成文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

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文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

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

主函数文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

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)
  }()
  <-c
}

func must(action string, err error) {
  if err != nil {
    panic("failed to " + action + ": " + err.Error())
  }
}

如下代码确保服务一直保持运行状态文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

  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)
  }()
  <-c

通过调用本地HTTP API调用与Nodejs主服务交互设备状态和设置网络参数。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

func getLocalIPAddresses() (DeviceIPAddress, error) {
  res, err := http.Get(getLocalIPAddressURL)
  if err != nil {
    log.Println(err)
    return DeviceIPAddress{}, err
  }

  defer res.Body.Close()
  rbody, _ := ioutil.ReadAll(res.Body)
  ipaddresses := DeviceIPAddress{}

  err = json.Unmarshal(rbody, &ipaddresses)
  if err != nil {
    log.Println(err)
    return DeviceIPAddress{}, err
  }
  return ipaddresses, nil
}

func internetHealthyCheck() (bool, error) {
  res, err := http.Get(internetHealthyCheckURL)
  if err != nil {
    log.Println(err)
    return false, err
  }
  defer res.Body.Close()
  rbody, _ := ioutil.ReadAll(res.Body)
  networkStatus := NetworkStatus{}
  err = json.Unmarshal(rbody, &networkStatus)
  if err != nil {
    log.Println(err)
    return false, err
  }
  return networkStatus.Success, nil
}

func setupNewWifi(wifiConfig []byte) (bool, error) {
  request, _ := http.NewRequest("POST", setupNewWifiURL, bytes.NewBuffer(wifiConfig))
  request.Header.Set("Content-Type", "application/json; charset=UTF-8")

  client := &http.Client{}
  res, err := client.Do(request)

  if err != nil {
    log.Println(err)
    return false, err
  }
  defer res.Body.Close()
  rbody, _ := ioutil.ReadAll(res.Body)
  wifiSettingStatus := WifiSettingStatus{}
  err = json.Unmarshal(rbody, &wifiSettingStatus)
  if err != nil {
    log.Println(err)
    return false, err
  }
  log.Println(wifiSettingStatus)
  return wifiSettingStatus.Success, nil
}

func resetTerminal(resetVersion []byte) (bool, error) {
  request, _ := http.NewRequest("POST", factoryResetURL, bytes.NewBuffer(resetVersion))
  request.Header.Set("Content-Type", "application/json; charset=UTF-8")

  client := &http.Client{}
  res, err := client.Do(request)

  if err != nil {
    log.Println(err)
    return false, err
  }
  defer res.Body.Close()
  return true, nil
}

编译程序文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

GOOS=linux GOARCH=arm64 go build -o bin/kimable

将蓝牙服务拷贝至设备端,配置为本地服务,确保服务在系统重启自行启动autostart文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

创建服务文件 /etc/systemd/system/kimacloud-ble.service文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

[Unit]
Description=Kimacloud Ble Service
Documentation=https://kimacloud.com/
After=pm2-root.service
​
[Install]
WantedBy=multi-user.target
​
[Service]
ExecStart=/home/player/kimable
WorkingDirectory=/home/player
User=root
Restart=always
RestartSec=5
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n

注册服务并确保服务正确启动文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

systemctl enable kimacloud-ble.service
systemctl start kimacloud-ble.service
systemctl status kimacloud-ble.service
journalctl -f -u kimacloud-ble.service
​
systemctl daemon-reload

完整代码请访问Github文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/35725.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/35725.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定