定位模块(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定位功能 |
| GPS | latitude, longitude | 室内外混合定位,只需要经纬度 |
| RELATIVE | xCoord, 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: 楼层IDuwbPos: 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技术特点
| 特性 | UWB | GPS | 蓝牙 | WiFi |
|---|---|---|---|---|
| 定位精度 | 10-50cm | 5-10m | 2-5m | 5-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.ktapp/src/main/java/android/znhaas/util/NtripManager.ktapp/src/main/java/android/znhaas/util/NtripClient.ktapp/src/main/java/android/znhaas/util/RtcmManager.kt
-
相关文档:
- 本文档包含完整的定位功能说明