EdgexAgent/device-gps-go/run/driver/gpsdriver.go

1035 lines
27 KiB
Go
Raw Normal View History

2025-07-10 20:30:06 +08:00
// -*- Mode: Go; indent-tabs-mode: t -*-
//
// Copyright (C) 2018 Canonical Ltd
// Copyright (C) 2018-2019 IOTech Ltd
// Copyright (C) 2021 Jiangxing Intelligence Ltd
// Copyright (C) 2022 HCL Technologies Ltd
//
// SPDX-License-Identifier: Apache-2.0
// Package driver this package provides an UART implementation of
// ProtocolDriver interface.
//
// CONTRIBUTORS COMPANY
//===============================================================
// 1. Sathya Durai HCL Technologies
// 2. Sudhamani Bijivemula HCL Technologies
// 3. Vediyappan Villali HCL Technologies
// 4. Vijay Annamalaisamy HCL Technologies
//
//
package driver
import (
errorDefault "errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/edgexfoundry/device-sdk-go/v4/pkg/interfaces"
dsModels "github.com/edgexfoundry/device-sdk-go/v4/pkg/models"
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
"github.com/spf13/cast"
)
type Driver struct {
sdk interfaces.DeviceServiceSDK
lc logger.LoggingClient
asyncCh chan<- *dsModels.AsyncValues
deviceCh chan<- []dsModels.DiscoveredDevice
gpsDevice *LCX6XZ // GPS设备实例
}
// Initialize performs protocol-specific initialization for the device
// service.
func (s *Driver) Initialize(sdk interfaces.DeviceServiceSDK) error {
s.sdk = sdk
s.lc = sdk.LoggingClient()
s.asyncCh = sdk.AsyncValuesChannel()
s.deviceCh = sdk.DiscoveredDeviceChannel()
return nil
}
// Start runs device service startup tasks after the SDK has been completely
// initialized. This allows device service to safely use DeviceServiceSDK
// interface features in this function call
func (s *Driver) Start() error {
// 获取 UART 配置信息
// 通过结构体字段访问 Protocols
var deviceLocation string
var baudRate int
var ReadTimeout int
uartConfig, err := s.sdk.GetDeviceByName("GPS-Device-01")
if err != nil {
s.lc.Errorf("加载服务配置失败!")
}
for i, protocol := range uartConfig.Protocols {
deviceLocation = fmt.Sprintf("%v", protocol["deviceLocation"])
baudRate, _ = cast.ToIntE(protocol["baudRate"])
ReadTimeout, _ = cast.ToIntE(protocol["ReadTimeout"])
s.lc.Debugf("Driver.HandleReadCommands(): protocol = %v, device location = %v, baud rate = %v readTimeout=%v dataBits %v ",
i, deviceLocation, baudRate, ReadTimeout)
}
s.lc.Info("🚀 初始化GPS设备服务")
// 初始化GPS设备
gpsDevice, err := InitLCX6XZ(deviceLocation, baudRate, ReadTimeout)
if err != nil {
s.lc.Errorf("❌ GPS设备初始化失败: %v", err)
return err
}
s.gpsDevice = gpsDevice
s.lc.Info("✅ GPS设备初始化成功")
return nil
}
// HandleReadCommands triggers a protocol Read operation for the specified device.
func (s *Driver) HandleReadCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []dsModels.CommandRequest) (res []*dsModels.CommandValue, err error) {
s.lc.Debugf("📖 处理设备 %s 的读取命令", deviceName)
if s.gpsDevice == nil {
return nil, fmt.Errorf("GPS设备未初始化")
}
res = make([]*dsModels.CommandValue, 0, len(reqs))
for _, req := range reqs {
s.lc.Debugf("处理资源: %s", req.DeviceResourceName)
var cv *dsModels.CommandValue
switch req.DeviceResourceName {
case "latitude":
cv = s.getLatitude(req)
s.lc.Debugf("latitude: %v", cv)
case "longitude":
cv = s.getLongitude(req)
case "altitude":
cv = s.getAltitude(req)
case "speed":
cv = s.getSpeed(req)
case "course":
cv = s.getCourse(req)
case "utc_time":
cv = s.getUTCTime(req)
case "fix_quality":
cv = s.getFixQuality(req)
case "satellites_used":
cv = s.getSatellitesUsed(req)
case "hdop":
cv = s.getHDOP(req)
case "gps_status":
cv = s.getGPSStatus(req)
case "get_output_rates":
cv = s.getOutputRates(req)
default:
s.lc.Warnf("未知的资源名称: %s", req.DeviceResourceName)
continue
}
if cv != nil {
res = append(res, cv)
}
}
return res, nil
}
// HandleWriteCommands passes a slice of CommandRequest struct each representing
// a ResourceOperation for a specific device resource.
// Since the commands are actuation commands, params provide parameters for the individual
// command.
func (s *Driver) HandleWriteCommands(deviceName string, protocols map[string]models.ProtocolProperties, reqs []dsModels.CommandRequest,
params []*dsModels.CommandValue) error {
s.lc.Debugf("✍️ 处理设备 %s 的写入命令", deviceName)
if s.gpsDevice == nil {
return fmt.Errorf("GPS设备未初始化")
}
for i, req := range reqs {
s.lc.Debugf("处理写入资源: %s", req.DeviceResourceName)
switch req.DeviceResourceName {
case "set_output_rate":
err := s.setOutputRate(req, params[i])
if err != nil {
s.lc.Errorf("设置输出速率失败: %v", err)
return err
}
case "set_all_rates":
err := s.setAllOutputRates(req, params[i])
if err != nil {
s.lc.Errorf("批量设置输出速率失败: %v", err)
return err
}
default:
s.lc.Warnf("未知的写入资源名称: %s", req.DeviceResourceName)
return fmt.Errorf("不支持的写入操作: %s", req.DeviceResourceName)
}
}
return nil
}
// Discover triggers protocol specific device discovery, asynchronously writes
// the results to the channel which is passed to the implementation via
// ProtocolDriver.Initialize()
func (s *Driver) Discover() error {
return fmt.Errorf("Discover function is yet to be implemented!")
}
// ValidateDevice triggers device's protocol properties validation, returns error
// if validation failed and the incoming device will not be added into EdgeX
func (s *Driver) ValidateDevice(device models.Device) error {
protocol, ok := device.Protocols["UART"]
if !ok {
return errorDefault.New("Missing 'UART' protocols")
}
deviceLocation, ok := protocol["deviceLocation"]
if !ok {
return errorDefault.New("Missing 'deviceLocation' information")
} else if deviceLocation == "" {
return errorDefault.New("deviceLocation must not empty")
}
baudRate, ok := protocol["baudRate"]
if !ok {
return errorDefault.New("Missing 'baudRate' information")
} else if baudRate == "" {
return errorDefault.New("baudRate must not empty")
}
return nil
}
// Stop the protocol-specific DS code to shutdown gracefully, or
// if the force parameter is 'true', immediately. The driver is responsible
// for closing any in-use channels, including the channel used to send async
// readings (if supported).
func (s *Driver) Stop(force bool) error {
// Then Logging Client might not be initialized
if s.lc != nil {
s.lc.Debugf(fmt.Sprintf("Driver.Stop called: force=%v", force))
}
return nil
}
// AddDevice is a callback function that is invoked
// when a new Device associated with this Device Service is added
func (s *Driver) AddDevice(deviceName string, protocols map[string]models.ProtocolProperties, adminState models.AdminState) error {
s.lc.Debugf(fmt.Sprintf("a new Device is added: %s", deviceName))
return nil
}
// UpdateDevice is a callback function that is invoked
// when a Device associated with this Device Service is updated
func (s *Driver) UpdateDevice(deviceName string, protocols map[string]models.ProtocolProperties, adminState models.AdminState) error {
s.lc.Debugf(fmt.Sprintf("Device %s is updated", deviceName))
return nil
}
// RemoveDevice is a callback function that is invoked
// when a Device associated with this Device Service is removed
func (s *Driver) RemoveDevice(deviceName string, protocols map[string]models.ProtocolProperties) error {
s.lc.Debugf(fmt.Sprintf("Device %s is removed", deviceName))
return nil
}
// GPS数据读取辅助方法
// getLatitude 获取纬度
func (s *Driver) getLatitude(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil || s.gpsDevice.NMEA_RMC == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
lat := s.cleanString(string(s.gpsDevice.NMEA_RMC.Lat[:]))
ns := s.cleanString(string(s.gpsDevice.NMEA_RMC.N_S[:]))
if lat == "" || ns == "" {
return nil
}
// 转换为十进制度数格式
latValue, isValid := s.convertDMSToDecimalWithValidation(lat, ns)
if !isValid {
return nil
}
// 格式化为易读格式
formattedLat := s.formatCoordinate(latValue, true, ns)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedLat)
return cv
}
// getLongitude 获取经度
func (s *Driver) getLongitude(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil || s.gpsDevice.NMEA_RMC == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
lon := s.cleanString(string(s.gpsDevice.NMEA_RMC.Lon[:]))
ew := s.cleanString(string(s.gpsDevice.NMEA_RMC.E_W[:]))
if lon == "" || ew == "" {
return nil
}
// 转换为十进制度数格式
lonValue, isValid := s.convertDMSToDecimalWithValidation(lon, ew)
if !isValid {
return nil
}
// 格式化为易读格式
formattedLon := s.formatCoordinate(lonValue, false, ew)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedLon)
return cv
}
// getAltitude 获取海拔高度
func (s *Driver) getAltitude(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil || s.gpsDevice.NMEA_GGA == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
altStr := s.cleanString(string(s.gpsDevice.NMEA_GGA.Alt[:]))
if altStr == "" {
return nil
}
// 解析海拔高度
altitude := s.parseFloat(altStr)
// 格式化为易读格式
formattedAlt := s.formatAltitude(altitude)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedAlt)
return cv
}
// getSpeed 获取速度
func (s *Driver) getSpeed(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
var speedKmh float64
var hasValidData bool
// 优先从VTG获取km/h速度
if s.gpsDevice.NMEA_VTG != nil {
sogkStr := s.cleanString(string(s.gpsDevice.NMEA_VTG.SOGK[:]))
if sogkStr != "" {
speedKmh = s.parseFloat(sogkStr)
hasValidData = true
}
}
// 如果VTG中没有从RMC获取节速度并转换
if !hasValidData && s.gpsDevice.NMEA_RMC != nil {
sogStr := s.cleanString(string(s.gpsDevice.NMEA_RMC.SOG[:]))
if sogStr != "" {
sog := s.parseFloat(sogStr)
speedKmh = sog * 1.852 // 1节 = 1.852 km/h
hasValidData = true
}
}
// 只有在没有任何有效数据时才返回nil
if !hasValidData {
return nil
}
// 格式化为易读格式
formattedSpeed := s.formatSpeed(speedKmh)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedSpeed)
return cv
}
// getCourse 获取航向
func (s *Driver) getCourse(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
var course float64
var hasValidData bool
// 优先从VTG获取航向
if s.gpsDevice.NMEA_VTG != nil {
cogtStr := s.cleanString(string(s.gpsDevice.NMEA_VTG.COGT[:]))
if cogtStr != "" {
course = s.parseFloat(cogtStr)
hasValidData = true
}
}
// 如果VTG中没有从RMC获取
if !hasValidData && s.gpsDevice.NMEA_RMC != nil {
cogStr := s.cleanString(string(s.gpsDevice.NMEA_RMC.COG[:]))
if cogStr != "" {
course = s.parseFloat(cogStr)
hasValidData = true
}
}
// 只有在没有任何有效数据时才返回nil
if !hasValidData {
return nil
}
// 格式化为易读格式
formattedCourse := s.formatCourse(course)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedCourse)
return cv
}
// getUTCTime 获取UTC时间
func (s *Driver) getUTCTime(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil || s.gpsDevice.NMEA_RMC == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
utcStr := s.cleanString(string(s.gpsDevice.NMEA_RMC.UTC[:]))
if utcStr == "" {
return nil
}
// 将UTC时间格式化为易读格式
formattedTime := s.formatUTCTime(utcStr)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedTime)
return cv
}
// getFixQuality 获取定位质量
func (s *Driver) getFixQuality(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
var quality int32
// 优先从GGA获取详细的定位质量
if s.gpsDevice.NMEA_GGA != nil {
qualityStr := s.cleanString(string(s.gpsDevice.NMEA_GGA.Quality[:]))
if qualityStr != "" {
quality = int32(s.parseFloat(qualityStr))
}
}
// 如果GGA中没有从RMC状态推断
if quality == 0 && s.gpsDevice.NMEA_RMC != nil {
status := s.cleanString(string(s.gpsDevice.NMEA_RMC.Status[:]))
if status == "A" {
quality = 1 // 有效定位
}
}
// 格式化为易读格式
formattedQuality := s.formatFixQuality(quality)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedQuality)
return cv
}
// getSatellitesUsed 获取使用的卫星数
func (s *Driver) getSatellitesUsed(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil || s.gpsDevice.NMEA_GGA == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
satStr := s.cleanString(string(s.gpsDevice.NMEA_GGA.NumSatUsed[:]))
if satStr == "" {
return nil
}
// 解析卫星数
satCount := int32(s.parseFloat(satStr))
// 格式化为易读格式
formattedSatCount := s.formatSatelliteCount(satCount)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedSatCount)
return cv
}
// getHDOP 获取水平精度因子
func (s *Driver) getHDOP(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil {
return nil
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
var hdopStr string
// 优先从GGA获取HDOP
if s.gpsDevice.NMEA_GGA != nil {
hdopStr = s.cleanString(string(s.gpsDevice.NMEA_GGA.HDOP[:]))
}
// 如果GGA中没有尝试从GSA获取
if hdopStr == "" && s.gpsDevice.NMEA_GSA != nil {
hdopStr = s.cleanString(string(s.gpsDevice.NMEA_GSA.HDOP[:]))
}
if hdopStr == "" {
return nil
}
// 解析HDOP值
hdop := s.parseFloat(hdopStr)
// 格式化为易读格式
formattedHDOP := s.formatHDOP(hdop)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", formattedHDOP)
return cv
}
// getGPSStatus 获取GPS状态
func (s *Driver) getGPSStatus(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil || s.gpsDevice.NMEA_RMC == nil {
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", "DISCONNECTED")
return cv
}
s.gpsDevice.mutex.Lock()
defer s.gpsDevice.mutex.Unlock()
status := s.cleanString(string(s.gpsDevice.NMEA_RMC.Status[:]))
var gpsStatus string
if status == "A" {
gpsStatus = "ACTIVE"
} else {
gpsStatus = "WARNING"
}
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", gpsStatus)
return cv
}
// getOutputRates 获取所有NMEA消息输出速率
func (s *Driver) getOutputRates(req dsModels.CommandRequest) *dsModels.CommandValue {
if s.gpsDevice == nil {
return nil
}
s.lc.Info("开始查询所有NMEA消息输出速率")
// 查询支持的NMEA类型及其输出速率
nmeaTypes := []struct {
sid NMEA_SUB_ID
name string
}{
{NMEA_GGA_SID, "GGA"},
{NMEA_GLL_SID, "GLL"},
{NMEA_GSA_SID, "GSA"},
{NMEA_GRS_SID, "GRS"},
{NMEA_GSV_SID, "GSV"},
{NMEA_RMC_SID, "RMC"},
{NMEA_VTG_SID, "VTG"},
{NMEA_ZDA_SID, "ZDA"},
{NMEA_GST_SID, "GST"},
}
var rateInfos []string
for _, nmea := range nmeaTypes {
// 发送查询命令
err := GetNMEAOutputRate(s.gpsDevice, nmea.sid)
if err != nil {
s.lc.Errorf("查询%s输出速率失败: %v", nmea.name, err)
rateInfos = append(rateInfos, fmt.Sprintf("%s: 查询失败", nmea.name))
continue
}
// 等待响应
time.Sleep(200 * time.Millisecond)
// 从设备存储的查询结果中获取实际输出速率
s.gpsDevice.mutex.Lock()
if rate, exists := s.gpsDevice.OutputRates[nmea.sid]; exists {
var rateDesc string
switch rate {
case 0:
rateDesc = "禁用"
case 1:
rateDesc = "1Hz"
case 5:
rateDesc = "5Hz"
case 10:
rateDesc = "10Hz"
default:
rateDesc = fmt.Sprintf("%dHz", rate)
}
rateInfos = append(rateInfos, fmt.Sprintf("%s: %s", nmea.name, rateDesc))
} else {
rateInfos = append(rateInfos, fmt.Sprintf("%s: 未知", nmea.name))
}
s.gpsDevice.mutex.Unlock()
s.lc.Debugf("已查询%s输出速率", nmea.name)
}
rateInfo := strings.Join(rateInfos, ", ")
s.lc.Infof("查询完成,输出速率: %s", rateInfo)
cv, _ := dsModels.NewCommandValue(req.DeviceResourceName, "String", rateInfo)
return cv
}
// setOutputRate 设置单个NMEA消息的输出速率
func (s *Driver) setOutputRate(req dsModels.CommandRequest, param *dsModels.CommandValue) error {
// 参数格式: "GGA:1" 或 "RMC:5"
configStr, ok := param.Value.(string)
if !ok {
return fmt.Errorf("参数值必须是字符串格式")
}
// 解析配置字符串
parts := strings.Split(configStr, ":")
if len(parts) != 2 {
return fmt.Errorf("无效的配置格式,应为 NMEA_TYPE:RATE")
}
nmeaType := strings.TrimSpace(strings.ToUpper(parts[0]))
rateStr := strings.TrimSpace(parts[1])
rateVal, err := strconv.ParseUint(rateStr, 10, 8)
if err != nil {
return fmt.Errorf("无效的速率值: %s", rateStr)
}
rate := uint8(rateVal)
// 转换NMEA类型为子ID
subID, err := s.getNMEASubID(nmeaType)
if err != nil {
return fmt.Errorf("不支持的NMEA类型: %s", nmeaType)
}
s.lc.Infof("设置%s消息输出速率为%d", nmeaType, rate)
return SetNMEAOutputRate(s.gpsDevice, subID, rate)
}
// setAllOutputRates 批量设置所有NMEA消息的输出速率
func (s *Driver) setAllOutputRates(req dsModels.CommandRequest, param *dsModels.CommandValue) error {
if param == nil {
return fmt.Errorf("参数值为空")
}
// 参数格式: "GGA:1,RMC:1,GSV:5,VTG:1,GSA:1"
configStr, ok := param.Value.(string)
if !ok {
return fmt.Errorf("参数值必须是字符串格式")
}
s.lc.Infof("开始批量设置输出速率: %s", configStr)
// 解析配置字符串
configs, err := s.parseMultipleRateConfig(configStr)
if err != nil {
return fmt.Errorf("解析配置字符串失败: %v", err)
}
// 逐个设置输出速率
var errors []string
for nmeaType, rate := range configs {
// 转换NMEA类型为子ID
subID, err := s.getNMEASubID(nmeaType)
if err != nil {
errors = append(errors, fmt.Sprintf("%s: %v", nmeaType, err))
continue
}
// 发送设置命令
err = SetNMEAOutputRate(s.gpsDevice, subID, rate)
if err != nil {
errors = append(errors, fmt.Sprintf("%s: %v", nmeaType, err))
continue
}
s.lc.Infof("成功设置%s输出速率为%d", nmeaType, rate)
// 在设置之间添加延迟,避免设备处理不过来
time.Sleep(100 * time.Millisecond)
}
if len(errors) > 0 {
return fmt.Errorf("部分设置失败: %s", strings.Join(errors, "; "))
}
s.lc.Infof("批量设置输出速率完成")
return nil
}
// parseMultipleRateConfig 解析多个输出速率配置字符串
func (s *Driver) parseMultipleRateConfig(configStr string) (map[string]uint8, error) {
if configStr == "" {
return nil, fmt.Errorf("配置字符串为空")
}
configs := make(map[string]uint8)
// 支持逗号分隔的配置项
items := strings.Split(configStr, ",")
for _, item := range items {
item = strings.TrimSpace(item)
if item == "" {
continue
}
// 支持冒号或等号分隔
var parts []string
if strings.Contains(item, ":") {
parts = strings.Split(item, ":")
} else if strings.Contains(item, "=") {
parts = strings.Split(item, "=")
} else {
return nil, fmt.Errorf("无效的配置项格式: %s", item)
}
if len(parts) != 2 {
return nil, fmt.Errorf("无效的配置项格式: %s", item)
}
nmeaType := strings.TrimSpace(strings.ToUpper(parts[0]))
rateStr := strings.TrimSpace(parts[1])
rate, err := strconv.ParseUint(rateStr, 10, 8)
if err != nil {
return nil, fmt.Errorf("无效的速率值: %s", rateStr)
}
configs[nmeaType] = uint8(rate)
}
if len(configs) == 0 {
return nil, fmt.Errorf("未找到有效的配置项")
}
return configs, nil
}
// getNMEASubID 将NMEA类型字符串转换为子ID
func (s *Driver) getNMEASubID(nmeaType string) (NMEA_SUB_ID, error) {
switch strings.ToUpper(nmeaType) {
case "GGA":
return NMEA_GGA_SID, nil
case "GLL":
return NMEA_GLL_SID, nil
case "GSA":
return NMEA_GSA_SID, nil
case "GRS":
return NMEA_GRS_SID, nil
case "GSV":
return NMEA_GSV_SID, nil
case "RMC":
return NMEA_RMC_SID, nil
case "VTG":
return NMEA_VTG_SID, nil
case "ZDA":
return NMEA_ZDA_SID, nil
case "GST":
return NMEA_GST_SID, nil
default:
return 0, fmt.Errorf("未知的NMEA类型: %s", nmeaType)
}
}
// 工具函数
// convertDMSToDecimalWithValidation 将度分秒格式转换为十进制度数,并返回是否有效
func (s *Driver) convertDMSToDecimalWithValidation(dmsStr, direction string) (float64, bool) {
if dmsStr == "" || direction == "" {
return 0.0, false
}
// 清理字符串
dmsStr = s.cleanString(dmsStr)
if len(dmsStr) < 4 {
return 0.0, false
}
// 解析度分格式 (ddmm.mmmm 或 dddmm.mmmm)
var degrees, minutes float64
var err error
if strings.Contains(dmsStr, ".") {
// 查找小数点位置
dotIndex := strings.Index(dmsStr, ".")
if dotIndex >= 4 {
// 经度格式 dddmm.mmmm
degrees, err = strconv.ParseFloat(dmsStr[:dotIndex-2], 64)
if err != nil {
return 0.0, false
}
minutes, err = strconv.ParseFloat(dmsStr[dotIndex-2:], 64)
if err != nil {
return 0.0, false
}
} else if dotIndex >= 3 {
// 纬度格式 ddmm.mmmm
degrees, err = strconv.ParseFloat(dmsStr[:dotIndex-2], 64)
if err != nil {
return 0.0, false
}
minutes, err = strconv.ParseFloat(dmsStr[dotIndex-2:], 64)
if err != nil {
return 0.0, false
}
} else {
return 0.0, false
}
} else {
return 0.0, false
}
decimal := degrees + minutes/60.0
// 根据方向调整符号
if direction == "S" || direction == "W" {
decimal = -decimal
}
return decimal, true
}
// convertDMSToDecimal 将度分秒格式转换为十进制度数(保持向后兼容)
func (s *Driver) convertDMSToDecimal(dmsStr, direction string) float64 {
result, _ := s.convertDMSToDecimalWithValidation(dmsStr, direction)
return result
}
// parseFloat 解析浮点数字符串
func (s *Driver) parseFloat(str string) float64 {
str = s.cleanString(str)
if str == "" {
return 0.0
}
val, err := strconv.ParseFloat(str, 64)
if err != nil {
return 0.0
}
return val
}
// cleanString 清理字符串,移除空字节和多余空格
func (s *Driver) cleanString(str string) string {
// 移除空字节
cleaned := strings.ReplaceAll(str, "\x00", "")
// 移除前后空格
cleaned = strings.TrimSpace(cleaned)
return cleaned
}
// formatUTCTime 将UTC时间字符串格式化为易读格式
// 输入格式: HHMMSS.sss (例如: 123456.00)
// 输出格式: HH:MM:SS.sss (例如: 12:34:56.00)
func (s *Driver) formatUTCTime(utcStr string) string {
if len(utcStr) < 6 {
return utcStr // 如果格式不正确,返回原始字符串
}
// 解析时分秒
hour := utcStr[0:2]
minute := utcStr[2:4]
second := utcStr[4:]
// 格式化为 HH:MM:SS.sss
return fmt.Sprintf("%s:%s:%s", hour, minute, second)
}
// formatCoordinate 格式化坐标为易读格式
// 输入: 十进制度数 (例如: 39.969056)
// 输出: 度分秒格式 (例如: 39°58'08.6"N)
func (s *Driver) formatCoordinate(decimal float64, isLatitude bool, direction string) string {
if decimal == 0.0 {
return "0°00'00.0\""
}
// 取绝对值进行计算
absDecimal := decimal
if absDecimal < 0 {
absDecimal = -absDecimal
}
// 计算度分秒
degrees := int(absDecimal)
minutes := (absDecimal - float64(degrees)) * 60
minutesInt := int(minutes)
seconds := (minutes - float64(minutesInt)) * 60
// 格式化输出
return fmt.Sprintf("%d°%02d'%04.1f\"%s", degrees, minutesInt, seconds, direction)
}
// formatSpeed 格式化速度为易读格式
func (s *Driver) formatSpeed(speedKmh float64) string {
return fmt.Sprintf("%.2f km/h", speedKmh)
}
// formatCourse 格式化航向为易读格式
func (s *Driver) formatCourse(course float64) string {
// 添加方向描述
var direction string
switch {
case course >= 0 && course < 22.5:
direction = "北"
case course >= 22.5 && course < 67.5:
direction = "东北"
case course >= 67.5 && course < 112.5:
direction = "东"
case course >= 112.5 && course < 157.5:
direction = "东南"
case course >= 157.5 && course < 202.5:
direction = "南"
case course >= 202.5 && course < 247.5:
direction = "西南"
case course >= 247.5 && course < 292.5:
direction = "西"
case course >= 292.5 && course < 337.5:
direction = "西北"
default:
direction = "北"
}
return fmt.Sprintf("%.1f° (%s)", course, direction)
}
// formatAltitude 格式化海拔高度为易读格式
func (s *Driver) formatAltitude(altitude float64) string {
return fmt.Sprintf("%.1f 米", altitude)
}
// formatFixQuality 格式化定位质量为易读格式
func (s *Driver) formatFixQuality(quality int32) string {
switch quality {
case 0:
return "无定位"
case 1:
return "GPS定位"
case 2:
return "差分GPS定位"
case 3:
return "PPS定位"
case 4:
return "RTK定位"
case 5:
return "浮点RTK"
case 6:
return "推算定位"
case 7:
return "手动输入"
case 8:
return "模拟定位"
default:
return fmt.Sprintf("未知质量(%d)", quality)
}
}
// formatSatelliteCount 格式化卫星数量为易读格式
func (s *Driver) formatSatelliteCount(count int32) string {
return fmt.Sprintf("%d 颗卫星", count)
}
// formatHDOP 格式化水平精度因子为易读格式
func (s *Driver) formatHDOP(hdop float64) string {
var quality string
switch {
case hdop <= 1:
quality = "优秀"
case hdop <= 2:
quality = "良好"
case hdop <= 5:
quality = "中等"
case hdop <= 10:
quality = "一般"
case hdop <= 20:
quality = "较差"
default:
quality = "很差"
}
return fmt.Sprintf("%.2f (%s)", hdop, quality)
}
// 公共格式化方法,供外部调用
// FormatUTCTime 公共方法格式化UTC时间
func (s *Driver) FormatUTCTime(utcStr string) string {
return s.formatUTCTime(utcStr)
}
// FormatCoordinate 公共方法:格式化坐标
func (s *Driver) FormatCoordinate(decimal float64, isLatitude bool, direction string) string {
return s.formatCoordinate(decimal, isLatitude, direction)
}
// FormatSpeed 公共方法:格式化速度
func (s *Driver) FormatSpeed(speedKmh float64) string {
return s.formatSpeed(speedKmh)
}
// FormatCourse 公共方法:格式化航向
func (s *Driver) FormatCourse(course float64) string {
return s.formatCourse(course)
}
// FormatAltitude 公共方法:格式化海拔
func (s *Driver) FormatAltitude(altitude float64) string {
return s.formatAltitude(altitude)
}
// FormatFixQuality 公共方法:格式化定位质量
func (s *Driver) FormatFixQuality(quality int32) string {
return s.formatFixQuality(quality)
}
// FormatSatelliteCount 公共方法:格式化卫星数量
func (s *Driver) FormatSatelliteCount(count int32) string {
return s.formatSatelliteCount(count)
}
// FormatHDOP 公共方法格式化HDOP
func (s *Driver) FormatHDOP(hdop float64) string {
return s.formatHDOP(hdop)
}