充电桩 WiFi 局域网配网(Android/Kotlin)流程、指令及实例说明文档

文档概述

本文档基于 Kotlin 语言,详细阐述 Android App 为充电桩实现 WiFi 局域网配网的完整流程、指令规范及可直接复用的代码实例。核心原理与之前一致:充电桩进入配网模式后创建临时 WiFi 热点,App 接入该热点后通过 UDP 广播发送目标 WiFi 参数,充电桩接收后连接目标 WiFi 并反馈结果。

Kotlin 适配特性:采用协程(Coroutine)处理异步通信、空安全(Null Safety)避免空指针、扩展函数简化 WiFi 信息获取,符合 Android Jetpack 最佳实践。

一、配网前提条件

1. 硬件条件

  • 充电桩:支持 WiFi 模块,长按配网按键进入配网模式(指示灯快闪),创建临时热点(如ChargingPile_XXXX);
  • 手机:Android 8.0+,已连接充电桩临时热点,开启 WiFi 和位置服务(Android 10 + 读取 SSID 必需)。

2. 软件权限(Kotlin 申请方式)

App 需在AndroidManifest.xml声明以下权限,并在运行时申请危险权限:

xml

复制代码
<!-- 基础权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- Android 10+ 读取SSID必需 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- UDP广播必需 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

二、配网完整流程

预览

查看代码

充电桩进入配网模式

手机连接充电桩临时热点

App申请权限并获取目标WiFi参数

App通过协程封装UDP配网指令并广播

充电桩解析指令并尝试连接目标WiFi

连接成功?

充电桩发送失败反馈

充电桩保存WiFi参数并退出配网模式

充电桩发送成功反馈

App解析反馈并提示失败

App解析反馈并提示成功

复制代码
flowchart TD
    A[充电桩进入配网模式] --> B[手机连接充电桩临时热点]
    B --> C[App申请权限并获取目标WiFi参数]
    C --> D[App通过协程封装UDP配网指令并广播]
    D --> E[充电桩解析指令并尝试连接目标WiFi]
    E --> F{连接成功?}
    F -- 否 --> G[充电桩发送失败反馈]
    F -- 是 --> H[充电桩保存WiFi参数并退出配网模式]
    H --> I[充电桩发送成功反馈]
    G --> J[App解析反馈并提示失败]
    I --> K[App解析反馈并提示成功]

充电桩进入配网模式

手机连接充电桩临时热点

App申请权限并获取目标WiFi参数

App通过协程封装UDP配网指令并广播

充电桩解析指令并尝试连接目标WiFi

连接成功?

充电桩发送失败反馈

充电桩保存WiFi参数并退出配网模式

充电桩发送成功反馈

App解析反馈并提示失败

App解析反馈并提示成功

流程分步说明

  1. 充电桩配网模式启动:用户长按充电桩配网键 3-5 秒,指示灯快闪即进入配网模式;
  2. 手机网络连接:用户在 WiFi 设置中连接充电桩临时热点;
  3. 权限与参数获取:App 引导用户授权位置 / 网络权限,自动读取当前连接的 WiFi SSID(可选),或让用户手动输入目标 WiFi 的 SSID 和密码;
  4. UDP 指令发送:App 通过 Kotlin 协程封装 JSON 格式配网指令,以 UDP 广播形式发送至局域网;
  5. 充电桩处理:充电桩监听指定 UDP 端口,解析指令后尝试连接目标 WiFi;
  6. 结果反馈:充电桩将连接结果封装为 JSON 指令,通过 UDP 反馈给 App;
  7. App 结果展示:App 解析反馈指令,通过 Toast / 弹窗提示用户配网成功 / 失败及原因。

三、配网指令详细说明

1. 通用规则

  • 传输协议:UDP 广播(优先,无需知道充电桩 IP),广播地址255.255.255.255,端口6666
  • 数据格式:JSON(UTF-8 编码),敏感字段(密码)建议 AES-128 加密(示例中暂不加密);
  • 超时规则:App 监听反馈超时时间 15 秒,充电桩连接 WiFi 超时时间 10 秒。

2. 核心指令定义

(1)配网指令(App → 充电桩)

表格

字段名 类型 必选 说明
cmd_type String 固定值:wifi_config
device_id String 充电桩 SN / 设备 ID
wifi_ssid String 目标 WiFi SSID
wifi_pwd String 目标 WiFi 密码(空 = 无密码)
timestamp Long 发送时间戳(毫秒)

示例 JSON

json

复制代码
{
  "cmd_type":"wifi_config",
  "device_id":"CP20240318001",
  "wifi_ssid":"Home_WiFi",
  "wifi_pwd":"12345678",
  "timestamp":1710768000000
}
(2)反馈指令(充电桩 → App)

表格

字段名 类型 必选 说明
cmd_type String 固定值:wifi_config_result
device_id String 充电桩 SN / 设备 ID
result_code Int 结果码:0 = 成功;1 = 密码错误;2=SSID 不存在;3 = 解析失败;4 = 超时
result_msg String 结果描述(如 "配网成功""密码错误")
timestamp Long 反馈时间戳(毫秒)

成功示例

json

复制代码
{
  "cmd_type":"wifi_config_result",
  "device_id":"CP20240318001",
  "result_code":0,
  "result_msg":"配网成功,已连接Home_WiFi",
  "timestamp":1710768010000
}

四、Kotlin 核心代码实例

1. 依赖配置(build.gradle)

添加 Gson(JSON 解析)和协程依赖:

gradle

复制代码
dependencies {
    // Gson解析JSON
    implementation 'com.google.code.gson:gson:2.10.1'
    // Kotlin协程
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
    // 权限申请(可选,简化权限处理)
    implementation 'com.permissionx.guolindev:permissionx:1.7.1'
}

2. 工具类:WiFi 信息扩展函数

kotlin

复制代码
import android.content.Context
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.os.Build

/**
 * Context扩展函数:获取WiFi管理器
 */
fun Context.getWifiManager(): WifiManager {
    return applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
}

/**
 * 获取当前连接的WiFi SSID(去除双引号,处理空安全)
 */
fun Context.getCurrentWifiSsid(): String? {
    val wifiManager = getWifiManager()
    if (!wifiManager.isWifiEnabled) return null
    
    val wifiInfo: WifiInfo = wifiManager.connectionInfo ?: return null
    val ssid = wifiInfo.ssid ?: return null
    
    // 移除Android默认给SSID加的双引号
    return if (ssid.startsWith("\"") && ssid.endsWith("\"")) {
        ssid.substring(1, ssid.length - 1)
    } else ssid
}

/**
 * 转义JSON特殊字符(避免解析失败)
 */
fun String.escapeJsonChars(): String {
    return this.replace("\\", "\\\\")
        .replace("\"", "\\\"")
        .replace("\n", "\\n")
        .replace("\r", "\\r")
}

3. 核心类:WiFi 配网管理器

kotlin

复制代码
import android.content.Context
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress

/**
 * 充电桩WiFi配网管理器(基于Kotlin协程)
 */
class ChargingPileWifiConfigManager(private val context: Context) {
    // 常量定义
    private companion object {
        const val UDP_BROADCAST_IP = "255.255.255.255"
        const val UDP_PORT = 6666
        const val LISTEN_TIMEOUT = 15000 // 监听反馈超时时间(15秒)
        val gson = Gson()
    }

    // 配网结果回调
    interface OnConfigResultListener {
        fun onSuccess(message: String)
        fun onFailed(errorCode: Int, errorMsg: String)
    }

    /**
     * 发送配网指令
     * @param targetSsid 目标WiFi SSID
     * @param targetPwd 目标WiFi密码
     * @param deviceId 充电桩设备ID
     * @param listener 结果回调
     */
    fun sendConfigCommand(
        targetSsid: String,
        targetPwd: String,
        deviceId: String,
        listener: OnConfigResultListener
    ) {
        // 协程处理异步操作(IO线程)
        GlobalScope.launch(Dispatchers.IO) {
            try {
                // 1. 封装配网指令
                val configCmd = ConfigCommand(
                    cmd_type = "wifi_config",
                    device_id = deviceId,
                    wifi_ssid = targetSsid.escapeJsonChars(),
                    wifi_pwd = targetPwd.escapeJsonChars(),
                    timestamp = System.currentTimeMillis()
                )
                val cmdJson = gson.toJson(configCmd)
                val cmdBytes = cmdJson.toByteArray(Charsets.UTF_8)

                // 2. 创建UDP Socket并发送广播
                val socket = DatagramSocket(UDP_PORT)
                socket.broadcast = true // 开启广播模式
                socket.soTimeout = LISTEN_TIMEOUT

                val broadcastAddress = InetAddress.getByName(UDP_BROADCAST_IP)
                val packet = DatagramPacket(
                    cmdBytes,
                    cmdBytes.size,
                    broadcastAddress,
                    UDP_PORT
                )
                socket.send(packet)

                // 3. 监听充电桩反馈
                val buffer = ByteArray(1024)
                val resultPacket = DatagramPacket(buffer, buffer.size)
                socket.receive(resultPacket)

                // 4. 解析反馈结果
                val resultJson = String(
                    resultPacket.data,
                    0,
                    resultPacket.length,
                    Charsets.UTF_8
                )
                val result = gson.fromJson(resultJson, ConfigResult::class.java)

                // 5. 回调结果(切换到主线程)
                withContext(Dispatchers.Main) {
                    if (result.result_code == 0) {
                        listener.onSuccess(result.result_msg)
                    } else {
                        listener.onFailed(result.result_code, result.result_msg)
                    }
                }
                socket.close()

            } catch (e: Exception) {
                // 异常处理(切换到主线程)
                withContext(Dispatchers.Main) {
                    listener.onFailed(999, "配网失败:${e.message ?: "未知错误"}")
                }
            }
        }
    }

    // 配网指令数据类(对应JSON结构)
    data class ConfigCommand(
        val cmd_type: String,
        val device_id: String,
        val wifi_ssid: String,
        val wifi_pwd: String,
        val timestamp: Long
    )

    // 配网结果数据类(对应JSON结构)
    data class ConfigResult(
        val cmd_type: String,
        val device_id: String,
        val result_code: Int,
        val result_msg: String,
        val timestamp: Long
    )
}

4. 页面示例:配网 Activity

kotlin

复制代码
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.permissionx.guolindev.PermissionX
import kotlinx.android.synthetic.main.activity_config.*

class WifiConfigActivity : AppCompatActivity() {
    private lateinit var configManager: ChargingPileWifiConfigManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_config)

        // 初始化配网管理器
        configManager = ChargingPileWifiConfigManager(this)

        // 自动填充当前连接的SSID
        tvCurrentSsid.text = "当前连接WiFi:${getCurrentWifiSsid() ?: "未检测到"}"
        etSsid.setText(getCurrentWifiSsid())

        // 配网按钮点击事件
        btnConfig.setOnClickListener {
            val ssid = etSsid.text.toString().trim()
            val pwd = etPwd.text.toString().trim()
            val deviceId = etDeviceId.text.toString().trim()

            // 校验输入
            if (ssid.isEmpty()) {
                Toast.makeText(this, "请输入WiFi名称", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            if (deviceId.isEmpty()) {
                Toast.makeText(this, "请输入充电桩设备ID", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            // 申请权限
            requestPermissions {
                // 权限通过后发送配网指令
                configManager.sendConfigCommand(ssid, pwd, deviceId, object :
                    ChargingPileWifiConfigManager.OnConfigResultListener {
                    override fun onSuccess(message: String) {
                        Toast.makeText(this@WifiConfigActivity, message, Toast.LENGTH_SHORT).show()
                        // 配网成功,跳转结果页
                        // startActivity(Intent(this@WifiConfigActivity, SuccessActivity::class.java))
                    }

                    override fun onFailed(errorCode: Int, errorMsg: String) {
                        Toast.makeText(this@WifiConfigActivity, "失败:$errorMsg", Toast.LENGTH_SHORT).show()
                    }
                })
            }
        }
    }

    /**
     * 申请必要权限(位置+网络)
     */
    private fun requestPermissions(onGranted: () -> Unit) {
        PermissionX.init(this)
            .permissions(
                android.Manifest.permission.ACCESS_COARSE_LOCATION,
                android.Manifest.permission.ACCESS_FINE_LOCATION,
                android.Manifest.permission.INTERNET
            )
            .onExplainRequestReason { scope, deniedList ->
                scope.showRequestReasonDialog(deniedList, "需要位置权限读取WiFi名称,网络权限发送配网指令", "确定", "取消")
            }
            .onForwardToSettings { scope, deniedList ->
                scope.showForwardToSettingsDialog(deniedList, "请在设置中开启权限", "去设置", "取消")
            }
            .request { allGranted, _, _ ->
                if (allGranted) {
                    onGranted()
                } else {
                    Toast.makeText(this, "权限申请失败,无法进行配网", Toast.LENGTH_SHORT).show()
                }
            }
    }
}

5. 布局文件(activity_config.xml)

xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="20dp">

    <TextView
        android:id="@+id/tvCurrentSsid"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:marginBottom="10dp"
        android:text="当前连接WiFi:未检测到" />

    <EditText
        android:id="@+id/etDeviceId"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入充电桩设备ID"
        android:marginBottom="10dp" />

    <EditText
        android:id="@+id/etSsid"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入目标WiFi名称"
        android:marginBottom="10dp" />

    <EditText
        android:id="@+id/etPwd"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入目标WiFi密码(无密码留空)"
        android:inputType="textPassword"
        android:marginBottom="20dp" />

    <Button
        android:id="@+id/btnConfig"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始配网" />

</LinearLayout>

五、测试与验证步骤

  1. 环境准备
    • 充电桩进入配网模式,创建临时热点;
    • 测试手机连接该热点;
    • 安装 App 并授予位置、网络权限。
  2. 操作步骤
    • 输入充电桩设备 ID、目标 WiFi SSID 和密码;
    • 点击 "开始配网" 按钮;
    • 观察 App 提示(成功 / 失败),同时查看充电桩指示灯(常亮 = 配网成功,快闪 = 失败)。
  3. 验证结果
    • 配网成功后,充电桩会自动连接目标 WiFi,可通过路由器管理页面查看设备是否上线;
    • App 可再次发送心跳指令,验证充电桩是否在线。

六、常见异常及处理方案

表格

异常场景 错误码 Kotlin 侧处理方式
WiFi 密码错误 1 清空密码输入框,提示用户 "密码错误,请重新输入"
SSID 不存在 2 校验 SSID 格式,提示用户 "未找到该 WiFi,请检查名称或确保 WiFi 已开启"
UDP 发送失败 999 检查手机是否连接充电桩热点,通过wifiManager.isWifiEnabled校验 WiFi 是否开启
监听反馈超时 999 提示用户 "未收到充电桩响应,请确认设备处于配网模式,或重试"
权限未授予 - 使用 PermissionX 引导用户授权,未授权时禁用配网按钮
JSON 解析失败 999 校验指令 JSON 格式,通过escapeJsonChars处理特殊字符

七、总结

核心要点

  1. Kotlin 实现配网的核心是协程 + UDP 广播:协程替代传统 Thread 处理异步通信,避免主线程阻塞;UDP 广播无需提前知道充电桩 IP,适配局域网内多设备场景;
  2. 指令规范:JSON 格式需包含cmd_type(指令类型)、device_id(设备标识)、result_code(结果码)等核心字段,特殊字符需转义;
  3. 权限处理:Android 10 + 必须申请位置权限才能读取 WiFi SSID,需引导用户授权并处理权限拒绝场景。

Kotlin 开发注意事项

  1. 空安全:所有可能为空的字段(如 SSID、反馈结果)需添加?,避免空指针异常;
  2. 协程作用域:建议使用lifecycleScope替代GlobalScope,避免内存泄漏;
  3. 加密传输:正式环境需对 WiFi 密码进行 AES 加密,可扩展ConfigCommand添加加密逻辑;
  4. 兼容性:适配 Android 12 + 的 WiFi 权限变更(如NEARBY_WIFI_DEVICES权限)。
相关推荐
weixin_456321642 小时前
Java架构设计:Redis持久化方案整合实战
java·开发语言·redis
2401_879503412 小时前
C++与FPGA协同设计
开发语言·c++·算法
asom222 小时前
DDD(领域驱动设计) 核心概念详解
java·开发语言·数据库·spring boot
没有了遇见3 小时前
Android 项目架构之<用户信息模块>
android
oem1103 小时前
C++中的访问者模式变体
开发语言·c++·算法
SuperEugene3 小时前
JS/TS 编码规范实战:Vue 场景变量 / 函数 / 类型标注避坑|编码语法规范篇
开发语言·javascript·vue.js
暮冬-  Gentle°3 小时前
C++中的工厂方法模式
开发语言·c++·算法
乱世军军4 小时前
把 Python 3.13 降级到 3.11
开发语言·python
本喵是FW4 小时前
C语言手记2
c语言·开发语言