跳到主要内容

定位模块(GPS/RTK/UWB)

📋 模块概述

定位模块提供多种定位方式,包括GPS卫星定位、网络定位、RTK高精度定位、NTRIP差分数据等。

核心类:

  • android.znhaas.util.NativeLocationService - 原生GPS定位
  • android.znhaas.util.NtripManager - NTRIP差分数据管理
  • android.znhaas.util.NtripClient - NTRIP客户端
  • android.znhaas.util.RtcmManager - RTCM数据处理

🎯 主要功能

GPS定位

  • ✅ GPS卫星定位
  • ✅ 网络定位(基站+WiFi)
  • ✅ 自动降级(GPS→网络→离线)
  • ✅ 位置缓存(5秒有效期)
  • ✅ 线程安全(AtomicReference)
  • ✅ 定位质量评估

RTK高精度定位

  • ✅ NTRIP协议支持
  • ✅ RTCM差分数据接收
  • ✅ 高精度定位(厘米级)
  • ✅ 自动重连
  • ✅ 数据流管理

UWB超宽带室内定位

  • ✅ UWB三维定位(经纬度+相对坐标)
  • ✅ UWB一维定位(基站距离测量)
  • ✅ 多种工作模式(关闭/经纬度/相对坐标/混合)
  • ✅ 串口数据解析
  • ✅ 实时数据上报(MQTT)
  • ✅ 数据有效期管理(20秒)

📚 GPS定位API

本节提供GPS定位的快速使用指南。

快速使用

// 获取实例
val locationService = NativeLocationService.getInstance(context)

// 初始化
locationService.initLocationService(interval = 5000, minDistance = 10f)

// 启动定位
locationService.startLocation()

// 获取位置
val location = locationService.getLastLocation()
Log.d(TAG, "纬度: ${location.latitude}, 经度: ${location.longitude}")

// 停止定位
locationService.stopLocation()

📚 RTK/NTRIP API

1. NTRIP管理器

初始化

val ntripManager = NtripManager(context)

连接NTRIP服务器

ntripManager.connect(
host = "ntrip.server.com",
port = 2101,
mountPoint = "MOUNT01",
username = "user",
password = "pass"
)

断开连接

ntripManager.disconnect()

数据监听

ntripManager.setDataListener { rtcmData ->
Log.d(TAG, "收到RTCM数据: ${rtcmData.size} 字节")
processRtcmData(rtcmData)
}

2. NTRIP客户端

底层NTRIP客户端实现:

val ntripClient = NtripClient()

// 连接
ntripClient.connect(
host = "ntrip.server.com",
port = 2101,
mountPoint = "MOUNT01",
username = "user",
password = "pass"
)

// 发送GGA数据(位置信息)
val ggaData = buildGgaString(latitude, longitude, altitude)
ntripClient.sendGga(ggaData)

// 接收RTCM数据
ntripClient.setDataCallback { data ->
handleRtcmData(data)
}

// 断开
ntripClient.disconnect()

3. RTCM数据管理

val rtcmManager = RtcmManager()

// 处理RTCM数据
rtcmManager.processRtcmData(rtcmData)

// 获取差分修正
val correction = rtcmManager.getCorrection()

🔧 完整RTK定位示例

class RtkLocationActivity : AppCompatActivity() {

private lateinit var locationService: NativeLocationService
private lateinit var ntripManager: NtripManager

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

// 初始化GPS定位
locationService = NativeLocationService.getInstance(this)
locationService.initLocationService(interval = 1000, minDistance = 1f)
locationService.startLocation()

// 初始化NTRIP
ntripManager = NtripManager(this)
setupNtrip()

// 定时更新位置和发送GGA
startLocationUpdate()
}

private fun setupNtrip() {
// 连接NTRIP服务器
ntripManager.connect(
host = "ntrip.example.com",
port = 2101,
mountPoint = "RTCM32",
username = "user",
password = "password"
)

// 监听RTCM数据
ntripManager.setDataListener { rtcmData ->
Log.d(TAG, "收到RTCM差分数据: ${rtcmData.size} 字节")
// RTCM数据可以发送给RTK模块进行高精度定位
sendRtcmToRtkModule(rtcmData)
}

// 监听连接状态
ntripManager.setConnectionListener { isConnected ->
runOnUiThread {
if (isConnected) {
statusText.text = "NTRIP已连接"
statusText.setTextColor(Color.GREEN)
} else {
statusText.text = "NTRIP未连接"
statusText.setTextColor(Color.RED)
}
}
}
}

private fun startLocationUpdate() {
Handler(Looper.getMainLooper()).postDelayed(object : Runnable {
override fun run() {
updateLocation()
Handler(Looper.getMainLooper()).postDelayed(this, 1000)
}
}, 0)
}

private fun updateLocation() {
val location = locationService.getLastLocation()

if (location.fixLevel > 0) {
// 更新UI
latitudeText.text = String.format("%.8f", location.latitude)
longitudeText.text = String.format("%.8f", location.longitude)
accuracyText.text = "${location.accuracy}m"

// 发送GGA数据到NTRIP服务器
sendGgaToNtrip(location.latitude, location.longitude, 0.0)
}
}

private fun sendGgaToNtrip(lat: Double, lon: Double, alt: Double) {
val ggaString = buildGgaString(lat, lon, alt)
ntripManager.sendGga(ggaString)
}

private fun buildGgaString(lat: Double, lon: Double, alt: Double): String {
// 构建NMEA GGA格式字符串
// $GPGGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
val time = SimpleDateFormat("HHmmss.SS", Locale.US).format(Date())
val latDeg = lat.toInt()
val latMin = (lat - latDeg) * 60
val latDir = if (lat >= 0) "N" else "S"
val lonDeg = lon.toInt()
val lonMin = (lon - lonDeg) * 60
val lonDir = if (lon >= 0) "E" else "W"

val gga = String.format(
"\$GPGGA,%s,%02d%07.4f,%s,%03d%07.4f,%s,1,08,1.0,%.1f,M,0.0,M,,*",
time,
Math.abs(latDeg), Math.abs(latMin), latDir,
Math.abs(lonDeg), Math.abs(lonMin), lonDir,
alt
)

// 计算校验和
val checksum = calculateNmeaChecksum(gga)
return "$gga${checksum.toString(16).toUpperCase()}\r\n"
}

private fun calculateNmeaChecksum(sentence: String): Int {
var checksum = 0
for (char in sentence.substring(1, sentence.indexOf('*'))) {
checksum = checksum xor char.toInt()
}
return checksum
}

private fun sendRtcmToRtkModule(rtcmData: ByteArray) {
// 将RTCM数据发送给RTK模块(如通过串口)
// serialPort.send(rtcmData)
}

override fun onDestroy() {
super.onDestroy()
locationService.stopLocation()
ntripManager.disconnect()
}
}

⚠️ 注意事项

GPS定位

权限要求

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

定位精度影响因素

  • 🏙️ 建筑物遮挡
  • 🌤️ 天气条件
  • 📡 基站密度
  • 🔋 省电模式

RTK定位

网络要求

<uses-permission android:name="android.permission.INTERNET" />

NTRIP服务器

  • 需要有效的账号密码
  • 选择附近的挂载点(MountPoint)
  • 定期发送GGA数据保持连接

GGA数据

  • 每秒发送一次
  • 包含当前GPS位置
  • 用于服务器选择最佳差分数据

RTCM数据

  • 二进制格式
  • 需要RTK模块解析
  • 提供厘米级精度修正

📡 UWB超宽带室内定位

UWB定位概述

UWB(Ultra-Wideband,超宽带)是一种高精度的室内定位技术,通过测量无线电信号在基站和标签之间的飞行时间(ToF)来计算精确位置。H07应用支持通过串口接收UWB定位设备的数据,并通过MQTT上报到云端。

定位精度: 厘米级至分米级(10-50cm)
适用场景: 室内定位、人员追踪、资产管理
数据来源: 外接UWB定位设备(通过串口通信)

UWB工作模式

H07支持多种UWB工作模式,可根据实际需求灵活配置:

enum class UwbMode(val displayName: String) {
OFF("关闭"), // 不使用UWB定位
GPS("经纬度"), // 仅上报GPS经纬度坐标
RELATIVE("相对坐标"), // 仅上报UWB相对坐标(X,Y,Z)
GPS_RELATIVE("经纬度+相对坐标") // 同时上报经纬度和相对坐标
}
模式上报内容使用场景
OFF不上报UWB数据关闭UWB定位功能
GPSlatitude, longitude室内外混合定位,只需要经纬度
RELATIVExCoord, yCoord, zCoord, floorId纯室内定位,使用建筑物相对坐标系
GPS_RELATIVE经纬度 + 相对坐标完整定位信息,支持多种地图显示

UWB数据格式

1. UWB三维定位数据

UWB设备通过串口发送三维定位数据,包含经纬度和相对坐标:

data class UwbResponse(
override val type: String = "uwb",
var dataBytes: String, // 原始数据(十六进制)
var longitude: Double? = null, // 经度
var latitude: Double? = null, // 纬度
var longitudeDirection: String, // 经度方向(E/W)
var latitudeDirection: String, // 纬度方向(N/S)
var stationCount: Int, // 基站数量
var floorId: Int? = null, // 楼层ID
var xCoord: Int? = null, // X坐标(cm)
var yCoord: Int? = null, // Y坐标(cm)
var zCoord: Int? = null, // Z坐标(高度,cm)
var coordinateSign: UwbCoordinateType? // 坐标类型
) : BaseResponse

数据上报格式(MQTT):

{
"latitude": "39.9042",
"longitude": "116.4074",
"locationType": 7,
"floor": 3,
"uwbPos": "1250,3460,280,3",
"uwbRange": "2A5B,125,3C7D,346"
}

字段说明:

  • locationType: 7 表示UWB定位
  • floor: 楼层ID
  • uwbPos: UWB位置 X,Y,Z,FloorID (单位:cm)
  • uwbRange: UWB一维距离数据 基站ID1,距离1,基站ID2,距离2,... (单位:cm)

2. UWB一维定位数据

UWB一维定位只测量标签到各基站的距离,不计算绝对坐标:

data class Uwb1DResponse(
override val type: String = "uwb1d",
var dataBytes: String, // 原始数据
var stationDistances: List<StationDistance> // 基站距离列表
) : BaseResponse

data class StationDistance(
var stationId: String, // 基站短ID(十六进制,如"2A5B")
var distance: Int // 距离(cm)
)

串口数据格式:

04 + 基站ID1(2字节) + 距离1(2字节,cm) + 基站ID2(2字节) + 距离2(2字节,cm) + ...

示例:

04 2A5B 007D 3C7D 015A
^^^^ ^^^^ ^^^^ ^^^^
基站1 125cm 基站2 346cm

MQTT上报格式:

uwbRange: "2A5B,125,3C7D,346,5E9F,289"

UWB数据流

UWB定位设备 (外设)

│ 串口通信 (/dev/ttyS*)

SerialPortProtocol.parseResponse()

├─→ UwbResponse (三维定位)
│ ├─ latitude, longitude
│ └─ xCoord, yCoord, zCoord

└─→ Uwb1DResponse (一维距离)
└─ List<StationDistance>


MainActivity.handleSerialPortData()

├─ updateUwbPosition() // 更新三维坐标
└─ updateUwb1DData() // 更新一维距离


MqttTimerManager (定时上报,每10秒)

├─ 检查数据有效期 (20秒)
├─ 选择定位数据源 (UWB优先)
└─ 构造上报消息


MQTT Publish → device/{deviceId}/location

UWB数据使用

class MainActivity : AppCompatActivity() {

/**
* 处理串口UWB数据
*/
private fun handleUwbData(uwbResponse: UwbResponse) {
Log.d(TAG, "收到UWB三维定位数据:")
Log.d(TAG, " 经纬度: ${uwbResponse.latitude}, ${uwbResponse.longitude}")
Log.d(TAG, " 相对坐标: X=${uwbResponse.xCoord}, Y=${uwbResponse.yCoord}, Z=${uwbResponse.zCoord}")
Log.d(TAG, " 楼层: ${uwbResponse.floorId}")
Log.d(TAG, " 基站数: ${uwbResponse.stationCount}")

// 更新UWB位置到MQTT定时上报管理器
mqttTimerManager?.updateUwbPosition(
uwbResponse.latitude,
uwbResponse.longitude,
uwbResponse.floorId,
uwbResponse.xCoord,
uwbResponse.yCoord,
uwbResponse.zCoord
)
}

/**
* 处理串口UWB一维数据
*/
private fun handleUwb1DData(uwb1DResponse: Uwb1DResponse) {
Log.d(TAG, "收到UWB一维定位数据: ${uwb1DResponse.stationDistances.size}个基站")

uwb1DResponse.stationDistances.forEach { station ->
Log.d(TAG, " 基站${station.stationId}: ${station.distance}cm")
}

// 更新一维距离数据
mqttTimerManager?.updateUwb1DData(uwb1DResponse.stationDistances)
}
}

UWB定位管理器 (MqttTimerManager)

class MqttTimerManager {

// UWB数据有效期: 20秒
private const val UWB_DATA_VALIDITY_MS = 20000L

// UWB三维定位数据
private var uwbLatitude: Double? = null
private var uwbLongitude: Double? = null
private var uwbFloorId: Int? = null
private var uwbXCoord: Int? = null
private var uwbYCoord: Int? = null
private var uwbZCoord: Int? = null
private var lastUwbUpdateTime = 0L

// UWB一维定位数据
private var uwb1DStationDistances: List<StationDistance>? = null
private var lastUwb1DUpdateTime = 0L

/**
* 更新UWB三维位置
*/
fun updateUwbPosition(
lat: Double?,
lon: Double?,
floorId: Int?,
xCoord: Int?,
yCoord: Int?,
zCoord: Int?
) {
uwbLatitude = lat
uwbLongitude = lon
uwbFloorId = floorId
uwbXCoord = xCoord
uwbYCoord = yCoord
uwbZCoord = zCoord
lastUwbUpdateTime = System.currentTimeMillis()

Log.d(TAG, "UWB位置已更新")
}

/**
* 更新UWB一维距离数据
*/
fun updateUwb1DData(stationDistances: List<StationDistance>) {
uwb1DStationDistances = stationDistances
lastUwb1DUpdateTime = System.currentTimeMillis()

Log.d(TAG, "UWB一维数据已更新: ${stationDistances.size}个基站")
}

/**
* 生成定位上报消息
*/
private fun generateLocationMessage(): LocationUpMessage {
val timestamp = System.currentTimeMillis()

// 检查UWB数据是否有效
val isUwbValid = (timestamp - lastUwbUpdateTime) < UWB_DATA_VALIDITY_MS &&
(uwbLatitude != null || uwbXCoord != null)

// 检查UWB一维数据是否有效
val isUwb1DValid = (timestamp - lastUwb1DUpdateTime) < UWB1D_DATA_VALIDITY_MS &&
uwb1DStationDistances != null &&
uwb1DStationDistances!!.isNotEmpty()

// 构造uwbRange字符串: "基站ID1,距离1,基站ID2,距离2,..."
val uwbRangeString = if (isUwb1DValid) {
uwb1DStationDistances!!.joinToString(",") { "${it.stationId},${it.distance}" }
} else {
null
}

return if (isUwbValid) {
// 使用UWB定位数据
LocationUpMessage(
latitude = uwbLatitude.toString(),
longitude = uwbLongitude.toString(),
locationType = 7, // 7 = UWB定位
floor = uwbFloorId,
uwbPos = "${uwbXCoord},${uwbYCoord},${uwbZCoord},${uwbFloorId}",
uwbRange = uwbRangeString
)
} else {
// 降级到GPS定位
val location = locationService.getLastLocation()
LocationUpMessage(
latitude = location.latitude.toString(),
longitude = location.longitude.toString(),
locationType = 1, // 1 = GPS定位
uwbRange = uwbRangeString // 即使没有UWB坐标,也可能有距离数据
)
}
}
}

UWB定位优先级

H07应用支持多种定位源,按以下优先级选择:

1. UWB定位 (最高优先级,室内高精度)
└─ 数据有效期: 20秒
└─ locationType = 7

2. RTK差分定位 (高精度,室外)
└─ fixLevel = 4 (固定解)
└─ locationType = 4

3. GPS单点定位 (标准精度,室外)
└─ locationType = 1

4. 网络定位 (低精度,备用)
└─ 基站 + WiFi
└─ locationType = 6

5. 离线定位 (最后备用)
└─ 使用缓存位置

配置UWB模式

在应用设置界面配置UWB工作模式:

// 读取UWB模式
val sharedPreferences = getSharedPreferences("settings", Context.MODE_PRIVATE)
val uwbModeStr = sharedPreferences.getString("uwbMode", UwbMode.OFF.name) ?: UwbMode.OFF.name
val uwbMode = UwbMode.valueOf(uwbModeStr)

// 保存UWB模式
sharedPreferences.edit().apply {
putString("uwbMode", UwbMode.GPS_RELATIVE.name)
apply()
}

UWB应用场景

室内人员定位

  • 🏢 仓库/工厂: 追踪工人位置,确保安全
  • 🏥 医院: 定位医护人员,优化调度
  • 🏗️ 地下施工: GPS不可用,UWB提供精确定位

资产追踪

  • 📦 物品定位: 追踪贵重设备位置
  • 🚗 车辆管理: 停车场车辆定位
  • 🏷️ 工具管理: 追踪工具使用和位置

安全监控

  • 🚨 电子围栏: 检测越界报警
  • ⏱️ 滞留检测: 检测异常滞留
  • 📊 轨迹分析: 分析人员移动路径

UWB技术特点

特性UWBGPS蓝牙WiFi
定位精度10-50cm5-10m2-5m5-15m
室内可用✅ 优秀❌ 不可用✅ 可用✅ 可用
穿透能力🟡 中等❌ 弱🟡 中等🟡 中等
功耗🟢 低🔴 高🟢 低🟡 中
成本🟡 中🟢 低🟢 低🟢 低
延迟🟢 小于10ms🔴 大于1s🟡 100ms🟡 100ms
多人容量🟢 数百🟢 无限🟡 数十🟡 数十

注意事项

1. 数据有效期

  • UWB数据有效期为20秒
  • 超过有效期自动降级到GPS定位
  • 确保UWB设备持续发送数据

2. 串口通信

  • UWB设备通过串口连接
  • 需要配置正确的串口参数(波特率、数据位等)
  • 检查串口权限和设备节点

3. 基站部署

  • UWB定位需要至少3个基站实现2D定位
  • 至少4个基站实现3D定位
  • 基站布局影响定位精度

4. 坐标系转换

  • UWB相对坐标系需要与实际地图坐标系对齐
  • 经纬度转换需要精确的坐标系标定
  • 楼层ID需要与建筑物信息匹配

5. 数据格式

  • uwbPos: X,Y,Z,FloorID (单位:cm)
  • uwbRange: 基站ID,距离 对,用逗号分隔
  • 所有距离单位为厘米(cm)

🔗 相关资源

  • 源码:

    • app/src/main/java/android/znhaas/util/NativeLocationService.kt
    • app/src/main/java/android/znhaas/util/NtripManager.kt
    • app/src/main/java/android/znhaas/util/NtripClient.kt
    • app/src/main/java/android/znhaas/util/RtcmManager.kt
  • 相关文档:

    • 本文档包含完整的定位功能说明

📚 扩展阅读